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

13 KiB
Raw Blame History

项目全面审查报告(2026-03-03)

审查范围与方法

  • 范围:src/prisma/DockerfileJenkinsfileREADME.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:567maxLength={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-65500 响应包含 ErrorName: message
  • 影响:
    • 暴露内部实现信息、库错误细节、潜在环境配置线索;
    • 提升攻击者探测效率。
  • 建议:
    • 客户端统一返回泛化错误文案;
    • 详细错误仅记录在服务端日志(可加 requestId 关联)。

P1-3 CI 配置中存在敏感信息硬编码【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • Jenkinsfile 中地图 key 改为 credentials('amap-api-key')
    • Webhook 触发 token 改为 tokenCredentialId 方式读取凭据;
    • 消除源码内硬编码敏感值。
  • 证据:
    • Jenkinsfile:6(地图 key 常量)
    • Jenkinsfile:11(固定触发 token
  • 影响:
    • 密钥泄露与流水线被外部触发的风险增加;
    • 密钥轮转和环境隔离困难。
  • 建议:
    • 全部迁移到凭据系统(Jenkins Credentials / Secret Manager);
    • 触发 token 改为密文凭据并定期轮换。

中优问题(P2

P2-1 SSE 成员校验逻辑未真正启用(前端未传 userId)【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • useRoomPolling 改为显式接收 userIdSSE 连接带上 ?userId=...
    • room/[id]/page 传入当前用户 ID
    • GET /api/room/[id]/events 强制 userId 必填并做成员校验(无 userId 返回 401,非成员返回 403);
    • 更新 hook 测试覆盖 SSE URL 与缺失 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【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • handleAcceptPlan 改为“后端成功后再更新 planAccepted/activeContract”;
    • 增加 res.ok 检查与失败错误提示;
    • 防止后端失败时前端误显示“已接受”。
  • 证据:
    • src/hooks/useBlindboxPlan.ts:227-240(先 setPlanAccepted(true),请求后不判断 res.ok
  • 影响:
    • 后端失败时前端仍显示“已接受”,形成状态不一致。
  • 建议:
    • 先请求成功再更新 planAccepted/activeContract
    • 失败回滚并展示错误信息。

P2-3 TypeScript 基线不通过(测试代码类型漂移)【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • 修复 createRequestNextRequest 构造参数类型不兼容问题;
    • 修复盲盒测试中的 Prisma 事务 mock 签名、SSE 计划 mock 返回结构;
    • 补齐路由测试上下文参数与测试数据类型字段(reasoncreatorIdisLoading/errormatchType);
    • npx tsc --noEmit 已恢复通过。
  • 证据:
    • 修复后执行 npx tsc --noEmit 已无报错。
  • 影响:
    • 类型系统对回归的兜底失效;重构时风险上升。
  • 建议:
    • tsc --noEmit 纳入 CI 必跑;
    • 优先修复测试目录类型错误,确保类型门禁恢复。

P2-4 Lint 存在阻塞错误(React hooks 新规则触发)【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • 修复 GlobalUserBadgeRestaurantCardSwipeableCardPageTransitionuseGeolocation 的 hooks 规则 error
    • 修复页面文案中的未转义引号(react/no-unescaped-entities);
    • npm run lint 已恢复为 0 error(仍有 warning,后续可持续清理)。
  • 证据:
    • 修复后执行 npm run lint0 errors / 29 warnings
    • 代表性问题:
      • src/components/SwipeableCard.tsx:81render 阶段注册副作用)
      • src/components/GlobalUserBadge.tsx:23effect 内同步 setState
      • src/components/PageTransition.tsx:14render 期间 ref 访问)
      • src/hooks/useGeolocation.ts:67(effect 内直接触发状态写入路径)
  • 影响:
    • 渲染稳定性与性能风险;后续升级 React/Next 成本上升。
  • 建议:
    • 先清理 error 级规则,再统一处理 warning;
    • 针对 hooks 规则建立最小回归测试。

P2-5 定时器清理不完整(潜在内存泄漏/卸载后状态写入)【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • useBlindboxIdeas 增加统一 useEffect cleanup,卸载时清理所有 timersRef
    • useBlindboxDraw 增加 confetti 停止逻辑(timeout + requestAnimationFrame + confetti.reset),并在 handleContinue 与 unmount 时清理;
    • 同步修正盲盒房间页与 useBlindboxRoom 的定时器 cleanup 方式,避免 ref 清理时机不稳定。
  • 证据:
    • src/hooks/useBlindboxIdeas.ts 已新增 timersRef 统一清理;
    • src/hooks/useBlindboxDraw.ts 已新增 confetti 与动画回调销毁逻辑。
  • 影响:
    • 页面切换或快速操作下可能出现卸载后 setState、额外渲染噪音。
  • 建议:
    • 为上述 hook 增加 useEffect cleanup 清理 timersRef
    • confetti 动画增加销毁标志与取消逻辑。

P2-6 构建/测试门禁链路不完整【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • Jenkinsfile 新增 Install DependenciesQuality Gate 阶段,执行 npm cinpm run lintnpx tsc --noEmitnpm run test:coverage
    • 补齐 @vitest/coverage-v8 依赖,恢复 npm run test:coverage 可执行;
    • vitest.config.ts 增加覆盖率阈值(statements:57 / branches:50 / functions:47 / lines:60);
    • 调整 blindbox/pageprofile/page 测试异步断言,减少 act(...) 噪声。
  • 证据:
    • npm run test:coverage 通过(54 files / 337 tests,覆盖率总览满足阈值);
    • npm run lint 当前 0 errorsnpx tsc --noEmit 通过。
  • 影响:
    • 回归问题可能绕过 CI 直接进入部署。
  • 建议:
    • CI 最少增加:npm run lintnpx tsc --noEmitnpm test
    • 补齐 coverage 依赖并设定最低阈值;
    • 修复 act(...) 警告提升测试可信度。

可重构/可优化项(非阻塞,但收益高)

R1 计划查询存在 N+1 查询模式【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • getPendingPlansgetHistoryPlans 改为在 weekendPlan.findMany 中直接 select room { name, code }
    • 移除逐条 findUnique 查房间的 N+1 查询路径。
  • 证据:
    • src/lib/planQueries.ts 已由“逐条房间查询”改为“单次查询带房间关系”。

R2 atomicUpdateRoom 对 likes 的“全删全建”策略成本较高【已完成】

  • 修复状态: 已完成(2026-03-03
  • 修复内容:
    • roomRepository 中新增 diffRoomLikes,按差集计算 toCreate/toDelete
    • atomicUpdateRoom 改为 likes 增量更新(deleteMany + createMany 仅处理变更项),替代全量重建;
    • 补充 src/lib/roomRepository.test.ts 验证增量 diff 行为与去重逻辑。
  • 证据:
    • src/lib/roomRepository.ts 已移除 likes 全删全建逻辑,改为差量同步。

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. 再修 P1suggest-item 鉴权、API 错误脱敏、CI 密钥治理)。
  3. 处理 P2 工程门禁(lint/tsc/CI),恢复“提交即验证”。
  4. 最后推进 R 类重构(N+1、增量更新、API client 统一)。

交付说明

  • 本文档为静态审查结论,未改动业务代码。
  • 建议下一步按“P0/P1 修复 PR + 回归测试补齐”方式分批落地。