当前位置: 首页 > news >正文

软件工程实践八:Web 前端项目实战(SSE、Axios 与代理)

适合人群:具备 Vue 基础,希望接入 AI 文本/图片与流式输出的开发者或技术爱好者。
建议先看:实践三(RESTful API 设计原则)、实践五(接口监控与限流)。
你将收获:SSE 两种实现、Axios 标准化封装、vite 代理配置与常见坑。

创建项目

npm create vue@latest

实现文本聊天页面

  • 目标:实现基础的文本输入、发送与消息列表展示。
  • 要点:输入框与发送按钮、消息时间顺序、滚动定位到最新消息。
  • 组件建议:ChatInput(输入区)、ChatList(消息区)。

实现图片

  • 目标:支持根据文本生成图片并展示缩略图。
  • 要点:提交参数(prompt、size、数量)、生成态 loading、失败重试、下载按钮。
  • 组件建议:ImageGenerateFormImageGrid

实现文本聊天SSE的方式

sse 前端实现方式

第一种 EventSource

const url = "/api/ai/chat/stream?platform=openai&model=gpt-4o&message=" +encodeURIComponent(message);const sse = new EventSource(url);const handleDelta = (event: MessageEvent) => {const data = (event as MessageEvent).data;};const handleDone = () => {sse.close();};// Some servers don't set a named event; treat default messages as deltasse.onmessage = handleDelta;sse.addEventListener("delta", handleDelta as EventListener);sse.addEventListener("done", handleDone as EventListener);sse.onerror = (err) => {console.error("SSE error", err);sse.close();};

第二种 使用自带的fetch

  const res = await fetch("/api/ai/chat/stream", {method: "POST",headers: {"Content-Type": "application/json",},body: JSON.stringify({ platform: "openai", model: "gpt-4o", message }),});if (!res.ok || !res.body) throw new Error(`请求失败: ${res.status}`);const reader = res.body.getReader();const decoder = new TextDecoder("utf-8");let buffer = "";while (true) {const { value, done } = await reader.read();if (done) break;buffer += decoder.decode(value, { stream: true });// Try to parse as SSE events: data: {json}\n\nlet sepIndex: number;while ((sepIndex = buffer.indexOf("\n\n")) !== -1) {const rawEvent = buffer.slice(0, sepIndex);buffer = buffer.slice(sepIndex + 2);const dataLines = rawEvent.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.replace(/^data:\s?/, "")).join("");console.log("dataLines", dataLines);if (!dataLines) continue;consumeChunk(dataLines, assistantId);}// Fallback: handle NDJSON lines if server doesn't use blank-line separators// let lineIndex: number;// while ((lineIndex = buffer.indexOf("\n")) !== -1) {//   const rawLine = buffer.slice(0, lineIndex).trim();//   buffer = buffer.slice(lineIndex + 1);//   if (!rawLine) continue;//   if (rawLine.startsWith("data:")) {//     consumeChunk(rawLine.replace(/^data:\s?/, ""), assistantId);//   } else {//     consumeChunk(rawLine, assistantId);//   }// }

增加对话框

  • 目标:多会话管理,支持创建、重命名、删除与切换。
  • 要点:侧边栏会话列表、高亮当前会话、未读/进行中状态提示。
  • 数据:本地 localStorage 或后端存储会话元信息与消息。

API 调用信息

  • 目标:在界面中透明展示平台、模型、剩余次数/有效期等关键信息。
  • 要点:统一从 /api/user/quota 拉取;错误时给出可读提示(如未登录/已过期)。
  • 展示:头部条或设置抽屉中展示,避免打断主要流程。

API升级

  • 目标:后端接口版本/平台能力变化时,前端具备平滑升级能力。
  • 要点:通过配置层映射平台与模型;在切换平台时热更新可用模型列表。
  • 回退:接口报错时回退到上一个可用版本并提示用户。

登录与注册

  • 目标:支持注册/登录/登出与 Token 管理。
  • 要点:表单校验、登录态持久化(短期 Token 或 HttpOnly Cookie)、过期刷新。
  • 交互:失败清晰提示(401 引导重新登录)、成功跳转到最近会话。

axios

/*** Axios HTTP 封装:内置拦截器、Token 注入与类型友好的请求助手。* baseURL 来自 VITE_API_BASE_URL,未配置则回退为 '/api'。*/
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'/*** 统一的接口返回结构(兼容常见服务约定):* - success:显式标记是否成功* - code:业务状态码(0/200 视为成功)* - data:实际数据载荷* - message/msg:人类可读的提示*/
export interface ApiResponse<T = any> {code?: number | stringdata: Tmessage?: stringmsg?: stringsuccess?: boolean
}/** 所有请求的默认基础路径。 */
const baseURL =  '/api'/** 预配置的 Axios 实例。 */
const http: AxiosInstance = axios.create({baseURL,timeout: 15000,withCredentials: false,
})/*** 请求拦截器:* - 从 localStorage.token 注入 Authorization Bearer 令牌(若请求头未显式设置)。*/
http.interceptors.request.use((config) => {const token = localStorage.getItem('token')if (token && config.headers) {const hasAuth =typeof config.headers.Authorization === 'string' && config.headers.Authorization.length > 0if (!hasAuth) {config.headers.Authorization = `Bearer ${token}`}}return config},(error) => Promise.reject(error),
)/** 从响应载荷中提取可读消息(若存在)。 */
function extractMessage(payload: unknown): string | undefined {if (!payload || typeof payload !== 'object') return undefinedconst obj = payload as Record<string, unknown>const candidates = ['message', 'msg', 'error', 'detail']for (const key of candidates) {const value = obj[key]if (typeof value === 'string' && value.trim().length > 0) {return value}}return undefined
}/** 将 HTTP 状态码转换为本地化错误信息。 */
function httpStatusMessage(status: number): string {switch (status) {case 400:return '请求参数错误'case 401:return '未授权或登录已过期'case 403:return '无权限访问'case 404:return '资源未找到'case 408:return '请求超时'case 500:return '服务器错误'case 502:return '网关错误'case 503:return '服务不可用'case 504:return '网关超时'default:return `请求错误(${status}`}
}/*** 响应拦截器:* - 当 success === true 或 code ∈ {0, 200} 时直接返回 data* - 若存在 code 且表示失败,则使用提取的 message 拒绝* - 统一网络/服务端错误为可读信息*/
http.interceptors.response.use((response: AxiosResponse<ApiResponse>) => {const { data, status } = responseif (status >= 200 && status < 300) {if (data && typeof data === 'object') {const code = data.code as number | string | undefinedconst success = data.successif (success === true) return (data.data as any) ?? (data as any)if (code === 0 || code === 200 || code === '0' || code === '200') {return (data.data as any) ?? (data as any)}if (typeof code === 'undefined') {return data as any}const message = extractMessage(data) || '请求失败'return Promise.reject(new Error(message))}return response.data as any}return Promise.reject(new Error('网络错误'))},(error) => {if (axios.isCancel(error)) return Promise.reject(error)if (error?.response) {const status = error.response.status as numberconst msg = (extractMessage(error.response.data) || httpStatusMessage(status)) ?? '请求出错'return Promise.reject(new Error(msg))}const message = error?.message || '网络异常,请检查您的网络连接'return Promise.reject(new Error(message))},
)/** 与 AxiosRequestConfig 等价,用于保留下方助手的范型。 */
type RequestConfig<T = any> = AxiosRequestConfig<T>/** 低级请求助手,保留范型返回类型。 */
export function request<T = any>(config: RequestConfig): Promise<T> {return http.request<any, T>(config)
}/** GET 快捷方法。 */
export const get = <T = any, R = T>(url: string, config?: RequestConfig) =>http.get<R>(url, config)/** POST 快捷方法。 */
export const post = <T = any, R = T>(url: string, data?: T, config?: RequestConfig) =>http.post<R>(url, data, config)/** PUT 快捷方法。 */
export const put = <T = any, R = T>(url: string,data?: T,config?: RequestConfig,
) => http.put<R>(url, data, config)/** DELETE 快捷方法。 */
export const del = <T = any, R = T>(url: string, config?: RequestConfig) =>http.delete<R>(url, config)export default http

代理

vite.config.ts

  server: {proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,// 将 /api 前缀转发到后端时保留或重写// 若后端实际无 /api 前缀,请取消注释下一行:rewrite: (path) => path.replace(/^\/api/, ''),},},},

基本功能目标

1、登录,注册

2、api购买记录,有效期

3、文本聊天,文本聊天sse模式

4、图片生成

5、多平台切换

6、api调用次数统计 分1,7,30天,不同平台,不同api类型

7、多窗口

中级功能目标

1、聊天内容保存

2、支持模型选择

3、AI回复内容,支持 文本复制,图片下载

4、免费用户,每天只能调用三次

5、api购买实现,plus一个月调用100次,pro一个月调用1000次

高级功能

1、图片支持size设置,生成图片个数支持,1,2,3,4

2、图片进行本地存储,异步执行


最佳实践与常见问题

  • SSE 断线重连:EventSource 自带重连,手写 fetch 流需在网络断开时恢复并合并内容。
  • 错误提示:服务端返回 429/5xx 时统一友好文案,区分鉴权类与系统类;必要时弹窗引导降载。
  • Token 存储:避免将长效 Token 存在 localStorage,优先短时 Token 或 HttpOnly Cookie。
  • 代理重写:vite 代理 rewrite 与服务端路由前缀保持一致,避免 404/跨域问题。
  • 请求退避:对热点接口失败建议指数退避,避免前端风暴放大后端压力。
  • 图片下载:使用 a[download] 或 Blob URL;注意 CORS 与缓存策略。

作者:xuan
个人博客:https://blog.ybyq.wang
欢迎访问我的博客,获取更多技术文章和教程。

http://www.dtcms.com/a/390619.html

相关文章:

  • 【常见集合】ArrayList与LinkedList
  • IPD流程实战:如何跨领域应用IPD思维?
  • Archery:开源、一站式的数据库 SQL 审核与运维平台
  • 北斗GNSS在地质灾害监测中的变形监测技术与应用解析
  • C语言题目:用“*”作为元素打印菱形
  • Redis的java客户端(SpringDataRedis)
  • Amazon Aurora DSQL:分布式无服务器数据库的下一场革命
  • TVS管频繁损坏,是参数错选还是布局出问题?-ASIM阿赛姆
  • 论文阅读:TEMPORAL GRAPH NETWORKS FOR DEEP LEARNING ON DYNAMIC GRAPHS
  • 医疗行业淘汰赛开始了?医疗器械售后维修是否会有影响?
  • 第二部分:VTK核心类详解(第40章 vtkIdList ID列表类)
  • Elasticsearch的自定义score评分
  • 【软考-系统架构设计师】架构权衡分析方法(ATAM)
  • 信息系统项目的成本管理
  • Python进阶指南7:排序算法和树
  • 深入理解 HashMap的数据结构
  • ArcGIS前后两期数据库对比工具
  • React18学习笔记(三) ReactRouter----React中的路由
  • [cesium] vue3 安装cesium方法
  • 埃文科技亮相华为全联接大会2025 联合鲲鹏发布AI使能平台解决方案 共筑AI产业新生态
  • Linux 桌面环境GNOME 49 释出
  • react/umi,浏览器tab设置
  • langchain-PipelinePromptTemplate
  • git 本地仓库与远程仓库链接
  • 绘想 - 百度推出的AI视频创作平台
  • 穿越像素的凝视:深度解析视频中的人物与动物识别算法技术
  • OpenHarmony 4.0 Release源码下载、编译及烧录
  • 大模型提示词Prompt工程:2-全攻略+最佳实践框架+原理解析+实战案例库+七招要诀
  • 大模型微调——Prompt-Tuning
  • code2prompt 快速生成项目 Markdown 文档(结合大模型进行问答)