AI 网关前端核心业务流程实现方法详解(Vue3 会话管理)

企业级 AI 网关的前端核心流程是一条闭环:登录鉴权、路由守卫放行、会话管理、模型选择、发送消息、SSE 流式接收、增量渲染、历史回溯。整条链路里最容易被忽视的是状态设计——流式消息是逐步到达的,会话里同时存在”正在生成”与”已结束”两种形态,状态结构没设计好,打字机效果和多轮对话一起上就会出竞态。我用 Vue3 + Pinia + Vue Router 把这套流程落地,下面按业务顺序拆实现。

一、核心业务流程全景

网关前端的完整业务动作可以压缩成七个步骤:

  1. 用户登录,后端签发令牌,前端写入 store 并持久化到本地存储;
  2. 路由守卫校验登录态,未登录统一跳转登录页;
  3. 进入对话工作台,按权限加载可用的模型列表与历史会话;
  4. 用户选择模型、创建或切换会话,消息数组切换到对应会话上下文;
  5. 发送消息,消息立即上屏并标记”发送中”,同时发起流式请求;
  6. SSE 通道按块接收回复,逐块追加到当前会话最后一条消息;
  7. 流结束或出错后更新状态,落库刷新会话列表。

七个步骤分属三层:路由层管准入,store 层管状态,组件层管渲染。下面分别看每层的实现。

二、路由守卫与登录态:把鉴权挡在页面之外

2.1 全局前置守卫的三段式检查

路由守卫在跳转前做三件事:查令牌、补用户信息、查权限。核心实现如下:

router.beforeEach(async (to) => {
  const userStore = useUserStore()
  if (to.meta.requiresAuth && !userStore.token) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
  if (userStore.token && !userStore.profileLoaded) {
    await userStore.fetchProfile() // 一次拉取,后续复用
  }
  if (to.meta.permission && !userStore.hasPermission(to.meta.permission)) {
    return '/403'
  }
})

登录态丢失分两种情况处理:未登录访问受保护页,守卫直接重定向;请求中途令牌过期,由 Axios 响应拦截器捕获业务码后统一登出。二者配合,页面里不需要散落一堆”if 没登录就跳转”的判断。

2.2 路由懒加载与权限菜单

所有业务页面用箭头函数动态导入,交给 Vite 做代码分割。菜单由后端按角色返回,前端用 meta.permission 做按钮级过滤,模型管理、监控大盘、密钥配置这些页面按权限显示或隐藏。

三、Pinia 状态设计:流式场景下怎么组织数据

网关前端状态多而杂,我按领域拆成三个 store:

Store 核心状态 职责
userStore token、用户信息、权限集合 登录登出、权限判断
chatStore 会话列表、当前会话、消息数组、流式状态 对话主流程、SSE 消息追加
gatewayStore 模型列表、网关配置、配额余额 模型选择、用量展示

消息对象是 chatStore 的骨干,结构上必须区分生成中与已完成:

const message = {
  id: 'msg_xxx',
  role: 'user' | 'assistant',
  content: '',
  status: 'pending' | 'streaming' | 'done' | 'error',
  meta: { model, tokens, duration }
}

流式阶段不新建消息对象,只在原对象上追加 content 字段、推进 status,组件按 status 决定是否渲染光标闪烁。这一步是打字机效果不卡顿的前提——频繁替换整个消息对象会触发整块 DOM 重绘。

四、消息发送与流式接收:状态机驱动

4.1 发送流程

发送消息走一个固定的动作序列:校验输入 → 更新会话标题 → 追加用户消息 → 创建空助手消息 → 发起流式连接。发送期间禁用发送按钮,防止重复提交;同一会话只允许一个进行中的流,切换会话时取消上一个流的连接。

4.2 接收流程

网关的对话接口是 SSE 流式输出,前端用 EventSource 或 fetch 流读取逐块数据,每收到一段就在消息对象上追加文本。关键节点如下:

  1. 建立连接后把助手消息置为 streaming;
  2. 收到内容块,追加到 message.content;
  3. 收到完成事件,状态置 done,关闭连接;
  4. 收到错误事件或连接断开,状态置 error,保留已生成部分并提示重试。

4.3 会话切换与竞态处理

切换会话本质是替换 chatStore 里的 currentSessionId,消息列表由 computed 按会话 id 过滤派生。竞态集中在两点:快速切换会话时上一个流的回调还在写旧消息,用 AbortController 在切换时主动中止;页面卸载时忘记关闭流,用 onUnmounted 兜底清理,避免内存泄漏。

五、请求层封装与错误处理

Axios 实例统一处理令牌注入与响应解包,后端返回固定信封结构,code 为 0 表示成功,其余归为业务错误。拦截器里做三件事:请求头带令牌;响应统一解包返回 data;401 与登录失效统一登出。页面请求代码因此保持很薄,只关心业务数据。

模型列表、会话列表这类接口的返回结构带分页与元信息,store 层用 getters 做二次加工,组件不直接触碰原始响应。图表页和列表页复用同一套请求封装,配 silentError 选项控制是否弹全局错误提示。

六、易错点与调优

第一,把 SSE 数据直接写进本地存储做历史,会造成刷新后消息一半在内存一半在存储的错乱,历史记录应以服务端落库为准。第二,权限校验只在前端做不设后端防线,前端过滤只是体验优化,后端接口必须独立鉴权。第三,多会话并发流式请求在低配浏览器上会占满连接数,HTTP/1.1 下同域连接有限,必要时把流式接口与普通接口分域部署。

常见问题(FAQ)

Q1:刷新页面后正在生成的消息怎么处理?

刷新即中断,恢复时从服务端拉历史,未完成消息标记为中断并允许重新生成。

Q2:路由守卫和接口拦截器都做鉴权,重复吗?

不重复。守卫管页面准入,拦截器管接口层令牌失效,两层职责互补。

Q3:流式消息为什么不能直接整条替换渲染?

整条替换触发全量 DOM 更新,打字机会卡顿;在原文案上追加并保留消息对象才能增量渲染。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部