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

研途灵伴——联调我修了七个 Bug

写在前面

这周我们组的研途灵伴项目进入联调阶段。功能基本都搭完了,但拼到一起之后问题一个接一个地冒:接口 500、页面白屏、按钮看不清字、骨架屏永远不消失。

一共修了七个 Bug,另外有两个问题排查后发现涉及架构层面的决策,暂时没修,先记下来等团队讨论。这篇文章把每个问题的排查过程和修法都记一遍,算是给自己留个底。


一、两个 500——重构之后调用方忘改了

最先冒出来的是两个 500,都在学习会话模块。

POST /api/v1/study-session/start的时候后端直接报 500,错误信息是CareService.on_study_start() got an unexpected keyword argument 'now'。结束会话那个接口也一样,on_mood_recorded()报了同样的错。

我去看了一下调用方的代码,study_session.py里写了self.care_service.on_study_start(user_id, now=start_time, auto_commit=False)。再去看care.pyon_study_start的签名——只有一个user_id参数,nowauto_commit根本不接受。

应该是之前某次重构改了CareService的方法签名,但调用方没跟着改。两个地方,各删一行多余的参数,问题就解决了。

这种 bug 不难查,但很典型:重构改了接口,调用方漏改了。Python 不像 TypeScript 有编译期类型检查,少一个参数运行时才报错,联调的时候才发现。


二、饮食页面白屏——数组和对象没对齐

点击"饮食记录"按钮,页面直接白屏。打开控制台一看:TypeError: Cannot read properties of undefined (reading 'find')

顺藤摸瓜查下去,后端/api/v1/meal/menu返回的data字段直接是一个数组。但前端getMealMenus的返回类型写的是{items: MealMenu[]},父组件拿到返回值后执行menus.items,得到的是undefined,传给子组件后.find()就崩了。

改法很简单:在getMealMenus里把裸数组包一层,return { items: unwrap(response.data) },让实际返回值和类型声明对上。

这个 bug 暴露了一个联调中很常见的问题:后端觉得返回数组没问题,前端觉得返回对象更合理,两边各改各的,类型系统又拦不住运行时的结构不匹配。如果后端的接口文档或者类型定义足够严格,这种问题在开发阶段就能发现。


三、错题本标签重复——同一份数据存了两份

错题详情页里,"这道题目如何解决"区域的知识点标签出现了重复。比如"自然语言处理"出现了两次。

查了一下后端的数据流:创建错题时,knowledge_points存进了WrongQuestion模型的 JSON 字段,同时相同的值又作为WrongQuestionTagtag_type="knowledge")存进了 tag 表。详情 API 返回时两个字段都带着这些值,前端两组都渲染,自然就重复了。

修法在前端:渲染tags的时候加一行过滤,跳过tag_value已经存在于knowledge_points中的条目。

这个不算严格的 bug,更像是数据冗余导致的展示问题。后端存了两份一样的数据,前端得自己判断该信哪一份。


四、小测再练没有图片——数据在模块间传递时丢了

从错题本点"小测再练"进入答题页,题目只有文字,没有图片。但原始的错题记录里是有图的。

问题出在数据传递链路上:错题来源的WrongQuestionimages字段,但小测走的是QuestionItem模型,这个模型没有images。后端构建小测题目 payload 的时候,只取了QuestionItem的字段,图片就这样丢了。

改法涉及后端三个地方:

  • _get_questions_from_wrong_review改为返回三元组(QuestionItem, origin, images),把图片一起带出来
  • _build_start_question_payload新增images参数,写入响应
  • schemaQuizStartQuestionResponse新增images字段

前端也跟着改了:QuizStartQuestion类型加上images,Quiz 页面渲染题目时用<Image>组件展示。

这个问题属于典型的"数据在模块间传递时丢失"。每个模块只关心自己的模型定义,没人负责把图片从错题一路带到小测。这种问题在单独开发各自模块时不会发现,联调时才暴露。


五、聊天按钮看不清字——旧 API 在新版本上的坑

Tutor 回复消息底部有一排动作按钮:“加入错题本”"小测再练"之类的。绿色文字配深色气泡背景,肉眼几乎看不清。

查了一下ActionButtons.tsx,用的是 Ant Design 的type="primary"+ghost={true}。ghost 模式下按钮是透明背景,文字颜色继承 primary 色(teal),在深色背景上对比度不够。

一开始想走 CSS 覆盖的路子,加了.ant-btn-primary.ant-btn-ghost的样式规则。结果没用——Ant Design 5 用的是 CSS-in-Js,优先级比外部 CSS 高,样式根本覆盖不上去。

最后换成了 Ant Design 5 的新 props:color="primary"+variant="solid"。文字变白色,背景变成 teal 实心,对比度一下就够了。已完成状态的按钮用variant="outlined"保持灰色风格。

这件事让我对 Ant Design 5 的 API 体系有了更清楚的认识。type/ghost是旧写法,color/variant是新写法,两者不能混用。如果项目一开始就统一用新 API,这类问题根本不会出现。


六、情绪页面骨架屏永远不消失——这个最折腾

这个问题排查时间最长,也是我觉得最有意思的一个。

打开情绪记录页面,左侧的"今日心情打卡"表单正常显示,但右侧"最近 7 天趋势"卡片和下方"历史记录"卡片始终是骨架屏——灰色条状占位符,内容永远加载不出来。

我一开始以为是某个 API 接口挂了,但单独调三个接口都没问题。后来发现是三层问题叠在一起才产生的:

第一层:Promise.all的失败传播

MoodPagePromise.all并行调了三个 API。Promise.all的语义是"全部成功"——只要有一个 reject,整个 Promise 就 reject。虽然外层有 try-catch-finally,finally里写了setLoading(false),但在快速重渲染的场景下存在竞态条件。

第二层:全局 store 触发的竞态

情绪页面监听了全局 store 里的moodRefreshSequence。当其他模块(比如聊天、学习会话)调用emitRefreshTargets(['mood', ...])时,这个 sequence 会递增,触发情绪页面重新加载数据。每次重新加载开头就setLoading(true),如果上一次还没加载完,新的setLoading(true)会覆盖掉finally里的setLoading(false),loading 就永远卡在 true。

第三层:组件间共享 loading 状态

MoodTrendMoodHistory都通过loading={loading}接收同一个状态。一旦 loading 卡住,所有 Card 同时卡在骨架屏。

修法:

  1. Promise.all改成Promise.allSettled,每个 API 独立处理成功和失败,一个挂了不影响其他
  2. 移除MoodTrendMoodHistoryloading属性,组件内部自己处理空状态(显示"还没有情绪记录"之类的提示)
  3. 清掉了不再使用的trendLoading状态变量

改完之后,即使某个 API 超时或者报错,其他数据照常展示,骨架屏不会再卡死。


七、另外两件事

除了上面七个 Bug,这轮还做了两个小改动:

Vite 预加载:给vite.config.ts加了build.warmup.clientFiles,把主要页面组件加进预加载列表。改动不大,但能减少首次打开页面时的白屏时间。

未修复 Bug 沉淀:有两个问题排查后发现涉及架构层面的决策,暂时没修,记录到了未修复的bug/目录下:

  • “错题本 correct_answer 在聊天与答疑链路中始终为空”——聊天和截图答疑来源的错题没有标准答案,需要确认"标准答案"的业务定义
  • “情绪打卡提交因 CareService 调用 LLM 超时而卡死”——on_mood_recorded触发的 care 服务会调用 LLM API,没有超时设置,导致整个请求挂起

这两个问题不是修不了,是修之前需要团队先统一口径。


八、几点感受

联调不比开发轻松。每个模块单独看都没问题,拼到一起之后各种边界问题就冒出来了。500、白屏、骨架屏卡死,这些都不是"代码写错了",而是"拼起来之后才有的病"。

竞态条件是最难查的 Bug。情绪页面那个骨架屏问题,不是逻辑错了,而是多个异步操作在特定时序下产生了不可预期的行为。时序相关的 bug 很难用单元测试覆盖,因为执行顺序是不确定的。最后是靠理清楚数据流和状态更新的时序才定位到的。

Ant Design 升级要注意 API 迁移。ghost 按钮的问题,本质是旧 API 在新版本上表现不如预期。如果项目一开始就用color/variant写法,这类问题根本不会出现。以后用新框架的时候,得先看看有没有 API 迁移指南。

数据在模块间传递时容易丢东西。小测图片缺失的问题,每个模块只关心自己的模型,没人负责把数据一路带下去。这类问题在项目初期不容易发现,联调时才暴露。如果能在模型设计阶段就考虑好跨模块的数据流,后面会省很多事。


九、还差什么

  • 情绪打卡提交卡死的问题需要团队讨论后决定修法,核心是 care 服务的 LLM 调用需要加超时
  • 错题本 correct_answer 为空的问题需要确认"标准答案"到底由谁提供
  • 这轮主要是修 Bug,没有新增功能模块

最后

这轮联调修下来,最大的收获不是修掉了几个具体 bug,而是对"系统拼装"这件事有了更具体的体感。

单个模块开发的时候,边界是清晰的,输入输出是可控的。但多个模块拼到一起之后,时序、数据结构、状态管理之间的配合就变得复杂了。情绪页面的骨架屏问题尤其典型——Promise.all的失败传播、全局 store 触发的重渲染、组件间共享 loading 状态,三层问题叠在一起,单独看每一层都不算 bug,合在一起就是用户体验灾难。

修这种问题没有捷径,只能一层一层拆开看,找到真正的根因。打补丁只会让下一次排查更难。

http://www.zskr.cn/news/1382899.html

相关文章:

  • DeepSeek性能基线测试不达标?2024最新《LLM服务端压测白皮书》仅开放72小时下载(含CUDA 12.4适配校验表)
  • GitMemo 安卓版发布了:现在可以随时随地查看和记录自己的笔记
  • 好用还专业!2026年最流行AI论文软件榜单,高质初稿轻松写
  • 别让细节毁了你的论文:从TII投稿要求看IEEE期刊对学术写作的“强迫症”式规范
  • 2026年黄金回收暗语揭秘,在淮安认准这5家机构不会错 - 生活测评君
  • 2026.05.24cpp学习内容
  • Video2X终极指南:如何用AI实现专业级视频超分辨率与无损放大
  • 专业的工业洗衣机哪个品牌好
  • 如何让AI推荐你的网站?独立站 SEO + GEO 全攻略
  • TII投稿避坑实录:从LaTeX编译报错到作者照片命名,我踩过的那些雷
  • 2026 镇江・杭州(全区域服务)本地人必选彩钢瓦金属屋面防水防腐公司避坑指南 TOP5 推荐 - 本地便民网
  • ArcGIS网络分析实战:用OD成本矩阵搞定湖北省内城市间真实路网距离(附完整数据)
  • OpenAPI驱动的AI测试用例生成器:可嵌入CI的结构化接口测试工具
  • AI教材生成大揭秘:低查重工具实测,快速完成教材编写任务!
  • 中小企业本地化RAG一体机实测:从“文档杂乱”到“5秒溯源”,一个开箱即用的工程方案
  • Google 官方回应:GEO 不会取代 SEO,AI 搜索时代真正重要的是“内容理解力”
  • 【限时开源】Midjourney辉光效果参数矩阵表(含137组实测RGB辉光偏移值+环境光衰减系数),仅剩87份完整版
  • 五管OTA设计翻车实录:用Cadence仿真揭示工艺参数法的三大坑(及如何用gm/Id法拯救)
  • ctf show web 入门171
  • 陕西西安月嫂怎么选?五大机构深度测评,孕产家庭省心避坑指南 - 深度智识库
  • 企业网盘怎么选?2026 年 10 款团队协作工具对比
  • 零阶优化:超越梯度下降的神经网络训练新范式
  • 个人企业通用活动报名小程序管理系统
  • 成都学车靠谱判定指南:西华驾校核心维度解析 - 奔跑123
  • 【仅限首批200位架构师获取】DeepSeek v3.2设计模式补丁包:含4个已验证的Pattern-Override补丁
  • 合肥工业大学高等数学A(下)期末试卷及答案2016-2025年
  • Claude辅助数据库设计的7大黄金法则:从ER图生成到SQL优化,一线团队已验证有效
  • DeepSeek推理速度提升300%?揭秘LLM量化压缩与KV缓存优化实战路径
  • 微服务寻址的“智慧大脑”:一篇文章彻底搞懂 Nacos 注册中心与实战
  • PDF4QT:如何用开源工具彻底解决你的PDF文档处理难题?