# 项目全面审查报告（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 配置中存在敏感信息硬编码【已完成】
- 修复状态：✅ 已完成（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` 改为显式接收 `userId`，SSE 连接带上 `?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）
- 修复内容：
  - 修复 `createRequest` 与 `NextRequest` 构造参数类型不兼容问题；
  - 修复盲盒测试中的 Prisma 事务 mock 签名、SSE 计划 mock 返回结构；
  - 补齐路由测试上下文参数与测试数据类型字段（`reason`、`creatorId`、`isLoading/error`、`matchType`）；
  - `npx tsc --noEmit` 已恢复通过。
- 证据：
  - 修复后执行 `npx tsc --noEmit` 已无报错。
- 影响：
  - 类型系统对回归的兜底失效；重构时风险上升。
- 建议：
  - 将 `tsc --noEmit` 纳入 CI 必跑；
  - 优先修复测试目录类型错误，确保类型门禁恢复。

### P2-4 Lint 存在阻塞错误（React hooks 新规则触发）【已完成】
- 修复状态：✅ 已完成（2026-03-03）
- 修复内容：
  - 修复 `GlobalUserBadge`、`RestaurantCard`、`SwipeableCard`、`PageTransition`、`useGeolocation` 的 hooks 规则 error；
  - 修复页面文案中的未转义引号（`react/no-unescaped-entities`）；
  - 第二轮清理补齐所有剩余 warning（未使用变量、无效 eslint-disable、hooks 依赖、`no-img-element` 策略化处理）；
  - `npm run lint` 已恢复为 0 error / 0 warning。
- 证据：
  - 修复后执行 `npm run lint`：`0 errors / 0 warnings`。
  - 代表性问题：
    - `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 定时器清理不完整（潜在内存泄漏/卸载后状态写入）【已完成】
- 修复状态：✅ 已完成（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 Dependencies` 与 `Quality Gate` 阶段，执行 `npm ci`、`npm run lint`、`npx tsc --noEmit`、`npm run test:coverage`；
  - 补齐 `@vitest/coverage-v8` 依赖，恢复 `npm run test:coverage` 可执行；
  - 在 `vitest.config.ts` 增加覆盖率阈值（`statements:57 / branches:50 / functions:47 / lines:60`）；
  - 调整 `blindbox/page` 与 `profile/page` 测试异步断言，减少 `act(...)` 噪声。
- 证据：
  - `npm run test:coverage` 通过（`54 files / 337 tests`，覆盖率总览满足阈值）；
  - `npm run lint` 当前 `0 errors`，`npx tsc --noEmit` 通过。
- 影响：
  - 回归问题可能绕过 CI 直接进入部署。
- 建议：
  - CI 最少增加：`npm run lint`、`npx tsc --noEmit`、`npm test`；
  - 补齐 coverage 依赖并设定最低阈值；
  - 修复 `act(...)` 警告提升测试可信度。

---

## 可重构/可优化项（非阻塞，但收益高）

### R1 计划查询存在 N+1 查询模式【已完成】
- 修复状态：✅ 已完成（2026-03-03）
- 修复内容：
  - `getPendingPlans`、`getHistoryPlans` 改为在 `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`）【已完成】
- 修复状态：✅ 已完成（2026-03-03）
- 修复内容：
  - 盲盒主链路前端请求已去除冗余 `userId`（`useBlindboxIdeas`、`useBlindboxDraw`、`useBlindboxPlan`、`useBlindboxRoom`、`blindbox/page`）；
  - `useBlindboxRooms` 改为基于登录态启用请求，接口统一使用鉴权 cookie，不再拼接 `userId` query；
  - 保留房间实时 SSE 的 `userId` 参数（用于已修复的成员校验链路），其余盲盒链路契约已统一。
- 证据：
  - `rg` 检索显示盲盒前端链路已无 `?userId=` 或 `userId: profile.id` 传参。

### R4 统一 API 调用层（减少重复 fetch + 错误处理分散）【已完成】
- 修复状态：✅ 已完成（2026-03-03）
- 修复内容：
  - 在 `src/lib/fetcher.ts` 增加统一请求入口 `requestJson` 与 `ApiRequestError`，统一 JSON 序列化、响应解析与错误映射；
  - 盲盒核心前端链路已迁移到统一调用层（`blindbox/page`、`useBlindboxIdeas`、`useBlindboxRoom`、`useBlindboxDraw`、`useBlindboxPlan`）；
  - 降低重复 `fetch + res.ok + res.json` 模板代码，错误处理集中化。
- 证据：
  - 以上模块中的请求分支已改为 `requestJson(...)` 调用；
  - 相关盲盒 API/UI 回归测试通过，`npx tsc --noEmit` 通过。

---

## 第二轮执行进展（2026-03-03）
- 任务 2（清理 lint warning）：✅ 已完成
  - 结果：`npm run lint` => `0 errors / 0 warnings`。
- 任务 3（补 E2E 用例）：✅ 已完成（代码已补齐）
  - 新增：`playwright.config.ts`、`e2e/home-navigation.spec.ts`、`e2e/panic-join.spec.ts`、`e2e/invite-join.spec.ts`。
  - CI 门禁：`Jenkinsfile` 已新增 `E2E Gate` 阶段，执行 `test:e2e:install` + `test:e2e`。
  - 执行结果：已在可联网主机环境完成 Playwright 安装并跑通 `npm run test:e2e`（3/3 通过）。

---

## 基线执行结果（本次审查）
- `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 统一）。

## 交付说明
- 本文档已从“静态审查结论”演进为“修复跟踪文档”，会随每轮修复持续更新状态。
- 现阶段建议继续按“问题分组 + 回归测试同步补齐”的节奏推进。
