前后端联调实战:从接口对接到边界问题修复的完整记录
项目背景
本项目是一个基于uni-app的智能学习平台前端,后端采用Spring Boot框架,使用Session + UserHolder的认证方式。前端需要对接后端提供的 RESTful API 和 SSE 流式接口,实现用户认证、智能问答、学习计划、错题本等核心功能。
一、基础设施搭建:请求层封装
1.1 响应格式适配
后端统一使用Result格式返回数据:
{"ok":1,"msg":null,"data":{}}其中ok: 1表示成功,ok: 0表示失败。前端原有的响应格式为{ code: 200, message, data },需要在响应拦截器中进行映射:
// utils/request.jssuccess:(res)=>{if(res.statusCode===200){constdata=res.data// 适配后端 Result 格式if(data.ok!==undefined){if(data.ok===1){resolve({code:200,message:'success',data:data.data})}else{uni.showToast({title:data.msg||'请求失败',icon:'none'})reject(newError(data.msg))}return}// 兼容前端原有格式if(data.code===200){resolve(data)}}}技术要点:
- 响应拦截器同时兼容后端
Result格式和前端原有格式,保证平滑过渡 - 业务错误统一通过
uni.showToast提示用户 - 401 状态码自动跳转登录页并清除本地 Token
1.2 Session 认证支持
后端使用 Session 认证而非 JWT,前端需要配置withCredentials: true确保请求自动携带 Cookie:
uni.request({url:BASE_URL+options.url,method:options.method||'GET',data:options.data||{},header:{'Content-Type':'application/json','Authorization':token?`Bearer${token}`:'','X-Platform':platform},withCredentials:true,// 关键配置// ...})1.3 SSE 流式请求封装
智能问答模块需要接收后端 SSE(Server-Sent Events)流式响应。使用uni.request的enableChunked: true实现:
exportfunctionsseRequest(url,data,callbacks){letaborted=falseuni.request({url:BASE_URL+url,method:'POST',data:data,enableChunked:true,// 启用分块传输header:{'Content-Type':'application/json','Accept':'text/event-stream'// 声明接受 SSE},success:(res)=>{if(aborted)returnconsttext=res.dataconstlines=text.split('\n')for(constlineoflines){if(line.startsWith('data: ')){constcontent=line.substring(6)if(content==='[DONE]'){callbacks.onComplete?.()return}callbacks.onMessage?.(content)}}}})return{abort:()=>{aborted=true}}}技术要点:
enableChunked: true启用 HTTP 分块传输,支持流式接收- SSE 格式解析:按行分割,提取
data:开头的消息 [DONE]标记表示流结束- 返回
abort方法用于取消请求(用户点击"停止生成"按钮时调用)
二、用户认证模块联调
2.1 登录接口对接
后端登录接口为POST /users/login,请求体为{ phone, password }。前端需要处理:
- 参数映射:前端输入框显示"手机号",实际发送使用
phone字段 - 手机号验证:正则
/^1[3-9]\d{9}$/验证格式 - Session 存储:后端创建 Session,前端通过 Cookie 自动维护
// api/index.jsexportfunctionlogin(data){constparams={phone:data.username,// 前端 username 字段映射为 phonepassword:data.password}returnpost('/users/login',params)}2.2 注册功能实现
注册页面包含用户名、手机号、密码、年级、专业等字段。关键处理:
- 年级映射:前端中文选项映射为数字(大一→1,大二→2,…)
- 实时验证:输入框失焦时触发验证,显示红色错误提示
- 注册成功后跳转:1.5 秒后自动返回登录页
2.3 记住密码功能
使用本地存储实现记住密码,密码采用 Base64 + 字符位移的轻量级加密:
functionencryptPassword(password){// 字符位移 + Base64 编码constshifted=password.split('').map(c=>String.fromCharCode(c.charCodeAt(0)+3)).join('')returnbtoa(shifted)}functiondecryptPassword(encrypted){constshifted=atob(encrypted)returnshifted.split('').map(c=>String.fromCharCode(c.charCodeAt(0)-3)).join('')}三、智能问答模块联调
3.1 文本流式问答
对接后端POST /api/ai/chat接口,使用 SSE 接收流式响应。在 Pinia store 中实现:
// store/chat.jsasyncsendChatMessage(message,subject=''){if(!this.conversationId){this.conversationId='sess_'+Date.now()+'_'+Math.random().toString(36).substr(2,9)}constrequestData={prompt:message,sessionId:this.conversationId,subject:subject,useRag:true}const{abort}=apiSendChat(requestData,{onMessage:(content)=>{this.updateStreamingContent(content)// 逐步更新流式内容},onComplete:()=>{if(this.streamingContent){this.addAssistantMessage(this.streamingContent)}this.stopStreaming()},onError:(error)=>{this.addAssistantMessage('抱歉,我暂时无法回答这个问题')this.stopStreaming()}})this.currentAbort=abort}交互变化:
- AI 生成期间输入框禁用,显示"停止生成"按钮
- 点击停止按钮调用
abort()中断 SSE 请求
3.2 多模态问答(看图答题)
对接后端POST /api/ai/chat/multimodal接口,支持图片上传:
asyncsendMultimodalMessage(imagePath,prompt=''){constresponse=awaitapiChatMultimodal(imagePath,prompt)constdata=response.data// 展示识别结果this.addUserMessage(prompt||'图片内容:'+data.recognizedText,data.imageUrl)// 展示 AI 回答this.addAssistantMessage(data.answer)// 置信度低时提示if(data.confidence<0.7){this.addAssistantMessage('⚠️ 图片识别置信度较低...')}}3.3 对话历史加载
对接后端GET /ai/history/{type}/{chatId}接口,将后端消息格式转换为前端格式:
asyncloadChatHistory(conversationId=''){constres=awaitapiGetHistory(targetId,'chat')if(res.ok===1&&res.data&&Array.isArray(res.data)){this.messages=res.data.map(msg=>({id:msg.id||Date.now().toString(),role:msg.role||(msg.userId?'user':'assistant'),content:msg.content||msg.message||'',time:msg.timestamp?this.formatTimestamp(msg.timestamp):this.getCurrentTime(),image:msg.imageUrl||null}))}}四、三阶段接口接入计划
4.1 第一阶段:高优先级接口(5 个)
| 接口 | 说明 | 关键处理 |
|---|---|---|
GET /users/profile | 获取个人信息 | 字段映射:userId→id,username→name |
GET /users/learning-profile | 获取学情画像 | 传入userId参数 |
POST /api/mistakes | 添加错题 | 请求体:{ qId, userAnswer } |
POST /study-plans | 创建学习计划 | AI 自动生成,创建后自动获取任务列表 |
GET /api/onboarding/questions | 获取引导问题 | 支持按步骤动态加载,失败时使用模拟数据降级 |
4.2 第二阶段:中优先级接口(6 个)
| 接口 | 说明 | 关键处理 |
|---|---|---|
POST /users/avatar | 上传头像 | multipart/form-data,字段名file |
POST /users/profile | 创建用户画像 | 提交问卷时调用,存储到本地 |
PUT /users/profile/{userId} | 更新用户画像 | 路径参数 + 请求体 |
POST /api/exercises/next | 获取下一批练习 | 难度参数为字符串medium |
GET /api/exercises/targeted | 获取专项练习 | GET 方式,参数:kpId,difficulty,count |
GET /ai/history/{type} | 获取会话列表 | 返回会话 ID 数组 |
4.3 第三阶段:低优先级接口(4 个)
| 接口 | 说明 | 关键处理 |
|---|---|---|
GET /users/profile/{userId} | 获取用户画像 | 路径参数userId |
POST /api/user/materials | 上传学习资料 | multipart/form-data,额外参数userId,materialType |
POST /fileUpload | 文件上传 | 字段名imgFile(非默认file) |
GET /fileDownload/{filename} | 文件下载 | 返回完整 URL,直接用于下载 |
五、边界问题排查与修复
在接口联调过程中,发现了 7 个边界问题,这些问题在代码审查阶段被逐一修复。
5.1 难度参数类型错误(3 处)
问题描述:前端将难度字符串easy/medium/hard映射为整数1/2/3,但后端文档明确要求difficulty字段为字符串类型。
影响接口:
POST /api/exercises/nextPOST /api/exercises/targetedGET /api/exercises/targeted
后端文档示例:
{"userId":1,"knowledgePoint":"二叉树","difficulty":"medium",// 字符串类型!"count":5}修复方案:移除整数映射,直接使用字符串。
// 修复前difficulty:data.difficulty?{easy:1,medium:2,hard:3}[data.difficulty]:undefined// 修复后difficulty:data.difficulty||'medium'5.2 响应格式检查错误(3 处)
问题描述:前端使用res.code === 200检查响应,但request.js响应拦截器已将后端{ ok: 1, data }映射为{ code: 200, data },在 store 中应直接使用res.data访问业务数据。
影响文件:
store/chat.js中的loadChatHistory、loadChatSessions、sendMultimodalMessage
修复方案:改为res.ok === 1检查(或直接依赖拦截器的错误处理)。
// 修复前if(res.code===200&&res.data&&Array.isArray(res.data)){// 修复后if(res.ok===1&&res.data&&Array.isArray(res.data)){5.3 数据访问层级错误(1 处)
问题描述:submitSingleAnswerWithFeedback方法直接使用result.correct,但request.js返回的是{ code: 200, data: { correct, ... } },应该访问res.data.correct。
修复方案:添加const result = res.data提取实际业务数据。
// 修复前constresult=awaitsubmitSingleAnswer(exerciseId,answer,userId)this.exerciseResults[this.currentIndex]={correct:result.correct===true||result.correct===1,// ...}// 修复后constres=awaitsubmitSingleAnswer(exerciseId,answer,userId)constresult=res.data// 提取业务数据this.exerciseResults[this.currentIndex]={correct:result.correct===true||result.correct===1,// ...}六、前端待补充接口需求文档
在联调过程中,整理了28 个前端需要但后端尚未实现的接口,生成《前端待补充接口需求文档》提供给后端团队。
6.1 高优先级接口(12 个)
| 模块 | 接口 | 说明 |
|---|---|---|
| 任务 | PUT /tasks/{taskId}/status | 更新任务状态(支持 0/1/2) |
| 练习 | GET /api/exercises/subjects | 获取科目列表 |
| 练习 | GET /api/exercises/subjects/{subjectId}/topics | 获取知识点列表 |
| 练习 | POST /api/exercises/{sessionId}/submit | 批量提交答案(考试模式) |
| 引导 | GET /api/courses | 获取课程列表 |
| 引导 | POST /api/profile/generate | AI 生成学情画像 |
| 错题 | PUT /api/mistakes/{mistakeId}/status | 标记错题状态 |
| 报告 | GET /api/report | 获取学情报告 |
| 用户 | GET /api/user/info | 获取用户信息 |
| 用户 | PUT /api/user/info | 更新用户信息 |
| 通知 | GET /api/notifications | 获取通知列表 |
| 通知 | GET /api/notifications/unread-count | 获取未读数量 |
6.2 中优先级接口(10 个)
包括获取推荐问题、清空对话历史、获取练习记录、上传错题(拍照识别)、获取错题统计、打卡签到、获取学习统计、获取能力成长趋势、通用文件上传、全部标记已读。
6.3 低优先级接口(6 个)
包括取消 AI 生成、通知设置(获取/保存)、获取成就列表、获取等级信息、导入用户画像。
七、技术总结
7.1 接口对接最佳实践
- 严格对照后端文档:每个接口的路径、参数类型、响应格式都要与文档一致
- 统一响应拦截器:在
request.js中统一处理响应格式映射,store 层只需关注业务逻辑 - 降级方案:关键功能在接口失败时提供本地模拟数据,保证用户体验
- 错误处理:所有接口调用都添加 try-catch,错误信息通过
uni.showToast提示用户
7.2 状态管理设计
使用 Pinia 进行状态管理,按模块拆分 store:
| Store | 职责 |
|---|---|
user.js | 用户信息、登录状态、头像上传 |
chat.js | AI 对话消息、流式响应、会话历史 |
plan.js | 学习计划、任务列表、统计计算 |
exercise.js | 练习会话、答题记录、计时器 |
notification.js | 通知列表、未读数量、设置 |
7.3 经验教训
- 不要假设参数类型:后端文档说
difficulty是字符串,就不要自作聪明映射为整数 - 注意响应层级:
request.js返回的是{ code: 200, data },store 中访问业务数据需要res.data - 统一响应检查:使用
res.ok === 1而非res.code === 200,保持与后端Result格式一致 - 文件上传字段名:不同接口可能使用不同的字段名(
file、imgFile、image),需严格对照文档
八、项目技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| uni-app | 最新 | 跨端开发框架 |
| Vue 3 | 3.x | 前端框架 |
| Pinia | 2.x | 状态管理 |
| Spring Boot | 2.x | 后端框架 |
| SSE | - | 流式响应 |
| Session | - | 用户认证 |
九、后续计划
- 等待后端补充高优先级接口,完成剩余功能联调
- 优化 SSE 在 App 端的兼容性测试
- 完善错题拍照上传功能
- 实现学情报告页面的数据可视化
- 生产环境 BASE_URL 配置和跨域处理