Files
no-whatever/PROJECT_AUDIT_2026-03-03.md
T

216 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 项目全面审查报告(2026-03-03
## 审查范围与方法
- 范围:`src/``prisma/``Dockerfile``Jenkinsfile``README.md`、测试与构建脚本。
- 方式:静态审查 + 命令基线 + 关键链路手工走读(Panic 模式、房间投票、盲盒计划)。
- 执行过的基线命令:
- `npm run lint`
- `npm test`
- `npx tsc --noEmit`
- `npm run test:coverage`
- `npm run build`(当前环境因无法访问 Google Fonts 未完全通过)
## 结论概览
- 发现高优问题(P0/P1):5 个
- 发现中优问题(P2):6 个
- 主要风险类型:业务功能中断、状态数据污染、鉴权边界不完整、敏感信息暴露、工程质量门禁缺失。
---
## 关键问题(按优先级)
### P0-1 Panic 模式“手动加入房间”输入规则与后端房间 ID 规则不一致(功能性缺陷)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- 前端改为 6 位字母数字房间号输入,统一转大写并过滤非法字符;
- 后端 `POST /api/room/[id]/join` 增加房间号格式校验与标准化;
- 补充校验与页面行为测试用例。
- 证据:
- `src/app/panic/page.tsx:173`(要求长度必须为 4
- `src/app/panic/page.tsx:567``maxLength={4}`
- `src/app/panic/page.tsx:566`(仅允许数字)
- `src/lib/roomRepository.ts:21`(实际房间 ID 生成规则为 6 位,且含字母)
- 影响:
- 用户无法通过手输房间号加入大多数房间(尤其是含字母的房间号)。
- 直接影响核心转化路径(邀请后加入房间)。
- 建议:
- 前端加入输入改为 6 位字母数字(与 `ROOM_ID_CHARS` 一致);
- 后端补充房间号格式校验与明确错误提示;
- 增加 E2E 用例覆盖“手输邀请码加入”。
### P0-2 投票接口未校验 `restaurantId` 是否属于房间候选列表(可污染房间状态)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- `POST /api/room/[id]/swipe` 增加 `restaurantId` 必须属于当前房间候选列表的强校验;
- 对非法 `restaurantId` 返回 400
- 增加对应 API 测试用例,防止状态污染回归。
- 证据:
- `src/app/api/room/[id]/swipe/route.ts:25`(仅查 index
- `src/app/api/room/[id]/swipe/route.ts:32-42`(即使 `restaurantIndex === -1` 仍可写入 `likes` 并可能设置 `match`
- 影响:
- 客户端可提交伪造 `restaurantId`,导致房间匹配结果异常或不可展示;
- 状态污染会影响所有成员的结果一致性。
- 建议:
- 在进入投票逻辑前强制校验 `restaurantIndex >= 0`,否则返回 400
-`undo/reset` 等相关接口同步增加 ID 合法性校验;
- 增加“非法 restaurantId”单测。
### P1-1 `suggest-item` 接口缺失鉴权(可被匿名滥用,产生外部 API 成本)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- `POST /api/blindbox/plan/suggest-item` 增加登录鉴权(无登录态返回 401);
- 新增接口测试,覆盖“未登录拒绝访问”与“登录成功返回推荐”。
- 证据:
- `src/app/api/blindbox/plan/suggest-item/route.ts:6-11`(无 `getAuthUserId` / 无 membership 校验)
- 影响:
- 任意匿名请求可触发 AI + 地图查询,存在成本与滥用风险。
- 建议:
- 至少要求登录态;更稳妥是同时校验房间成员身份(若与房间上下文绑定);
- 配合限流(IP + 用户维度)。
### P1-2 API 错误响应直接回传内部异常细节(信息泄露)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- `apiHandler` 对未知异常返回统一文案 `操作失败,请稍后重试`
- 内部异常细节仅保留在服务端日志;
- 更新对应单元测试断言。
- 证据:
- `src/lib/api.ts:63-65`500 响应包含 `ErrorName: message`
- 影响:
- 暴露内部实现信息、库错误细节、潜在环境配置线索;
- 提升攻击者探测效率。
- 建议:
- 客户端统一返回泛化错误文案;
- 详细错误仅记录在服务端日志(可加 requestId 关联)。
### P1-3 CI 配置中存在敏感信息硬编码
- 证据:
- `Jenkinsfile:6`(地图 key 常量)
- `Jenkinsfile:11`(固定触发 token
- 影响:
- 密钥泄露与流水线被外部触发的风险增加;
- 密钥轮转和环境隔离困难。
- 建议:
- 全部迁移到凭据系统(Jenkins Credentials / Secret Manager);
- 触发 token 改为密文凭据并定期轮换。
---
## 中优问题(P2
### P2-1 SSE 成员校验逻辑未真正启用(前端未传 `userId`)
- 证据:
- `src/app/api/room/[id]/events/route.ts:14-18`(仅在 query 存在 `userId` 时校验)
- `src/hooks/useRoomPolling.ts:32`(实际建立 SSE 连接时未携带 `userId`
- 影响:
- 只要知道房间 ID 即可订阅房间流事件(当前实现下)。
- 建议:
- 改为服务端基于认证态校验,或强制 query/body 校验成员身份;
- 前端补齐身份参数并确保不可伪造(推荐 token/cookie 方案)。
### P2-2 “接受计划”前端逻辑乐观更新过早,且未检查 `res.ok`
- 证据:
- `src/hooks/useBlindboxPlan.ts:227-240`(先 `setPlanAccepted(true)`,请求后不判断 `res.ok`
- 影响:
- 后端失败时前端仍显示“已接受”,形成状态不一致。
- 建议:
- 先请求成功再更新 `planAccepted/activeContract`
- 失败回滚并展示错误信息。
### P2-3 TypeScript 基线不通过(测试代码类型漂移)
- 证据:
- `npx tsc --noEmit` 当前报多个错误(测试辅助类型、mock 签名、类型定义漂移等)。
- 影响:
- 类型系统对回归的兜底失效;重构时风险上升。
- 建议:
-`tsc --noEmit` 纳入 CI 必跑;
- 优先修复测试目录类型错误,确保类型门禁恢复。
### P2-4 Lint 存在阻塞错误(React hooks 新规则触发)
- 证据:
- `npm run lint`10 error / 32 warning。
- 代表性问题:
- `src/components/SwipeableCard.tsx:81`render 阶段注册副作用)
- `src/components/GlobalUserBadge.tsx:23`effect 内同步 setState
- `src/components/PageTransition.tsx:14`render 期间 ref 访问)
- `src/hooks/useGeolocation.ts:67`(effect 内直接触发状态写入路径)
- 影响:
- 渲染稳定性与性能风险;后续升级 React/Next 成本上升。
- 建议:
- 先清理 error 级规则,再统一处理 warning;
- 针对 hooks 规则建立最小回归测试。
### P2-5 定时器清理不完整(潜在内存泄漏/卸载后状态写入)
- 证据:
- `src/hooks/useBlindboxIdeas.ts:52` + `:129`(保存定时器,但无统一 cleanup)
- `src/hooks/useBlindboxDraw.ts:25` + `:38`(定时器与动画回调未见 unmount 清理)
- 影响:
- 页面切换或快速操作下可能出现卸载后 setState、额外渲染噪音。
- 建议:
- 为上述 hook 增加 `useEffect` cleanup 清理 `timersRef`
- confetti 动画增加销毁标志与取消逻辑。
### P2-6 构建/测试门禁链路不完整
- 证据:
- `Jenkinsfile` 当前仅构建和部署,无 `lint/test/tsc` 阶段。
- `npm run test:coverage` 缺少 `@vitest/coverage-v8`
- `npm test` 虽通过,但出现多处 `act(...)` 警告。
- 影响:
- 回归问题可能绕过 CI 直接进入部署。
- 建议:
- CI 最少增加:`npm run lint``npx tsc --noEmit``npm test`
- 补齐 coverage 依赖并设定最低阈值;
- 修复 `act(...)` 警告提升测试可信度。
---
## 可重构/可优化项(非阻塞,但收益高)
### R1 计划查询存在 N+1 查询模式
- 证据:
- `src/lib/planQueries.ts:30-36``63-68`(每条计划单独查房间)
- 建议:
- 使用 `include` / 批量映射一次性拉取房间信息,减少 DB 往返。
### R2 `atomicUpdateRoom` 对 likes 的“全删全建”策略成本较高
- 证据:
- `src/lib/roomRepository.ts:185-195`
- 建议:
- 按增量 diff 更新(新增/删除差集)替代全量重建;
- 对高频路径(swipe/undo)优先优化。
### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`)
- 证据:
- 例如 `src/hooks/useBlindboxIdeas.ts:57/71/104/...``src/hooks/useBlindboxPlan.ts:50/68/...`
- 建议:
- 明确“鉴权由 cookie/token 提供”,清理冗余 `userId` 参数;
- 用 Zod schema 统一约束(`src/lib/schemas/requests.ts` 目前未形成全链路使用)。
### R4 统一 API 调用层(减少重复 fetch + 错误处理分散)
- 现状:
- 客户端很多模块各自拼接 URL、手写错误分支。
- 建议:
- 为业务 API 建立 typed client(含统一重试、错误映射、鉴权处理);
- 与 SWR key 规范化一起推进。
---
## 基线执行结果(本次审查)
- `npm run lint`:失败,`10 errors / 32 warnings`
- `npm test`:通过,`53 files / 329 tests`,但有 `act(...)` 警告。
- `npx tsc --noEmit`:失败(测试相关类型错误若干)。
- `npm run test:coverage`:失败(缺少 `@vitest/coverage-v8`)。
- `npm run build`:本地环境因无法访问 Google Fonts 失败(与当前受限网络环境相关)。
---
## 建议修复顺序(可执行)
1. 先修 P0(房间号规则不一致、swipe 非法 ID 校验)。
2. 再修 P1`suggest-item` 鉴权、API 错误脱敏、CI 密钥治理)。
3. 处理 P2 工程门禁(lint/tsc/CI),恢复“提交即验证”。
4. 最后推进 R 类重构(N+1、增量更新、API client 统一)。
## 交付说明
- 本文档为静态审查结论,未改动业务代码。
- 建议下一步按“P0/P1 修复 PR + 回归测试补齐”方式分批落地。