当前位置: 首页 > 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.jsqmd.com/news/885444/

相关文章:

  • ImageForge - 用对话的方式,打造专业图像处理工具
  • Icarus Verilog:为什么这个开源仿真器成为数字电路验证的首选?
  • 从零开始的web前端开发11
  • 10款主流网盘深度对比:不限速之外,哪款更适合长期当“主力盘”?
  • 20252821 2025-2026-2 《网络攻防实践》第8周作业
  • 华为光猫配置解密工具终极指南:5分钟快速掌握配置解密全流程
  • 天文时序数据分析:机器学习评估、半监督学习与无监督方法实战
  • DeepSeek协议识别技术白皮书(含17个真实GitHub仓库扫描对比数据,仅限本周开放下载)
  • 别再只看准确率!DeepSeek代码质量评估必须关注的3个反直觉指标(附可运行的自动化评估脚本)
  • 电子签如何打通企业数字化“最后一公里”?
  • 2026年黄金回收暗语揭秘,在淮安认准这5家机构不会错 - 生活测评君
  • 《自在独行》
  • 空间扭曲、线条跑偏?聊聊 Seedance 2.0 在建筑漫游与科幻场景中的调教
  • 集成Taotoken为OpenClaw工作流提供持久化模型支持
  • vLLM--如何创建物理块
  • 如何让AI推荐你的网站?独立站 SEO + GEO 全攻略
  • 掌握AI教材写作:低查重AI工具,让教材编写不再难!
  • 从零开始的web前端开发10
  • 2026 镇江・杭州(全区域服务)本地人必选彩钢瓦金属屋面防水防腐公司避坑指南 TOP5 推荐 - 本地便民网
  • 2026年5月巨量本地推代理推荐:TOP5排名专业评测本地获客性价比高价格
  • QMCDecode:突破QQ音乐加密限制,轻松解锁音乐自由的终极方案
  • OpenAPI驱动的AI测试用例生成器:可嵌入CI的结构化接口测试工具
  • Unity资源逆向实战:AssetStudio底层原理与五大卡点排障
  • 【优】B+树,Mysql优化 慢查询 执行计划 优化表结构 避免死锁 大量插入数据大数据后果
  • 通用物联网开发板设计:基于ESP8266的硬件集成与开发实践
  • 美国海派专线的运输时效受哪些因素影响? - 恒盛通物流
  • AI掘金头条新闻系统 (Toutiao News)-用户注册-生成Token
  • 中小企业本地化RAG一体机实测:从“文档杂乱”到“5秒溯源”,一个开箱即用的工程方案
  • Google 官方回应:GEO 不会取代 SEO,AI 搜索时代真正重要的是“内容理解力”
  • AI教材生成大揭秘:低查重工具实测,快速完成教材编写任务!