6.6 KiB
待修复问题清单
全面代码审查后整理,按严重程度排列。 修复后在对应条目前打
[x]。
Critical — 必须修复
-
#1 非成员可操作任意房间
- 文件:
api/room/[id]/swipe/route.ts、undo/route.ts、reset/route.ts - swipe / undo 没有校验 userId 是否在
data.users中,非成员的 like 会被计入匹配 - reset 无需任何 userId,任何人知道 4 位房间号即可清空所有投票
- 修复:在
atomicUpdateRoom回调开头加成员校验;reset 要求 userId 并验证 creatorId
- 文件:
-
#2 盲盒抽取竞态条件
- 文件:
api/blindbox/draw/route.ts findMany({ status: "in_pool" })和update不在事务中,两个并发请求可抽到同一个想法- 修复:用
prisma.$transaction+updateMany({ where: { id, status: "in_pool" } })保证原子性,count=0 时返回 409
- 文件:
-
#3 SwipeDeck 不响应他人重置
- 文件:
src/components/SwipeDeck.tsx - 其他用户重置房间后,当前用户的
currentIndex/localMatchId不清零,卡片位置错乱 - 修复:加 useEffect 检测 server 端 swipeCount 归零时调用
clearLocalState()
- 文件:
-
#4 API 错误响应被当数组用导致崩溃
- 文件:
src/app/panic/page.tsx(suggestions)、src/app/profile/page.tsx(history/favorites) - fetch 后没检查
res.ok,非 200 返回的{ error: "..." }被当作数组,.map()崩溃 - 修复:所有 fetch 后先检查
res.ok,失败时设为空数组
- 文件:
-
#5 theme.ts SSR 崩溃
- 文件:
src/lib/theme.ts setStoredTheme/applyTheme直接访问localStorage/document,无typeof window守卫- 修复:函数开头加
if (typeof window === "undefined") return;
- 文件:
High — 应该修复
-
#6 用户名唯一性 TOCTOU 竞态 ✅
- register 去掉 findUnique 直接 create + catch P2002;user PUT 同理
- apiHandler 全局兜底 P2002 → 409
-
#7 SSE events 接口无认证 ✅
- 校验 userId query param + 房间成员身份;start() 加 try/catch
-
#8 GET /api/user 暴露任意用户邮箱 ⚠️
- 需要真实服务端 auth 才能彻底修复,暂保留;已加 JSON.parse 安全防护
-
#9 收藏去重用 JSON contains 匹配 ✅
- Favorite 模型新增
restaurantId字段 +@@unique([userId, restaurantId])
- Favorite 模型新增
-
#10 缺少数据库索引 ✅
- 补齐所有缺失索引并 db push
-
#11 无 onDelete 级联 ✅
- Decision/Favorite/BlindBoxMember 加
onDelete: Cascade,BlindBoxIdea.drawnBy 加onDelete: SetNull
- Decision/Favorite/BlindBoxMember 加
-
#12 密码无最大长度限制 ✅
validatePassword加 128 字符上限
-
#13 AuthModal 关闭后重开不重置表单 ✅
- useEffect 监听 open 变 true 时重置全部表单状态
-
#14 邀请页加入失败无错误提示 ✅
- 新增 joinError state,catch 中捕获并渲染错误消息
Medium — 建议修复
-
#15 房间 ID 空间仅 9000 个
- 文件:
src/lib/store.ts - 4 位数字 (1000-9999) 碰撞概率高,fallback 也没兜住 P2002
- 修复:扩展到 6 位字母数字,或 catch P2002 后重试
- 文件:
-
#16 盲盒想法编辑/删除有 TOCTOU
- 文件:
api/blindbox/route.tsPUT / DELETE - 状态检查和修改之间可被并发抽取,drawn 的想法可能被编辑/删除
- 修复:用
updateMany({ where: { id, status: "in_pool" } })替代先查后改
- 文件:
-
#17 lat/lng 未校验为合法坐标
- 文件:
api/room/create/route.ts lat=0(赤道有效坐标)被当 falsy 拒绝;非数值字符串也能通过- 修复:
Number.isFinite(lat)+ 范围校验 (-9090, -180180)
- 文件:
-
#18 swipe action 未校验
- 文件:
api/room/[id]/swipe/route.ts - 非
like/pass值静默通过,只增 count 不记偏好 - 修复:校验
action是否为允许的值
- 文件:
-
#19 JSON.parse(preferences) 可能崩溃
- 文件:
api/user/route.tsGET - 残损 JSON 导致 500,用户无法加载 profile
- 修复:使用 safe parse helper,fallback 为
{}
- 文件:
-
#20 ShareCardModal data 每次新引用触发 useEffect
- 文件:
src/components/ShareCardModal.tsx - 内联对象导致 data 引用每次变化,图片重复加载
- 修复:改用
imageSrc字符串作为 useEffect 依赖,或在调用侧 useMemo
- 文件:
-
#21 BlindboxRoomPage 多处 setTimeout 未清理
- 文件:
src/app/blindbox/[code]/page.tsx - confetti rAF 循环、确认退出 3s 定时器、focus 定时器均未在 unmount 时清理
- 修复:用 ref 存储 timer ID,useEffect return 中 clearTimeout / cancelAnimationFrame
- 文件:
-
#22 外部 API (高德) 失败返回泛化 500
- 文件:
api/room/create、api/location/suggest、api/location/regeo - 用户不知道是定位服务不可用
- 修复:wrap fetch 加 try/catch,throw
ApiError("位置服务暂时不可用", 503)
- 文件:
-
#23 navigation.ts location.split(",") 不校验格式
- 文件:
src/lib/navigation.ts - 无逗号的字符串产生
undefined坐标 - 修复:校验 split 结果长度为 2 且两部分非空
- 文件:
-
#24 handleReset / handleNarrow 吞掉 fetch 错误
- 文件:
src/app/room/[id]/page.tsx - 无 res.ok 检查,失败时用户无反馈
- 修复:检查 res.ok,失败时 throw
- 文件:
-
#25 confettiCanvasRef 未使用(死代码)
- 文件:
src/app/blindbox/[code]/page.tsx - 渲染了 canvas 但 confetti() 使用全局实例
- 修复:删除 ref 和
<canvas>元素
- 文件:
-
#26 requireString 接受纯空格字符串
- 文件:
src/lib/validation.ts " "是 truthy 但没有实质内容- 修复:加
.trim()校验
- 文件:
Low — 可以改进
-
#27 icon-only 按钮缺少 aria-label
- 文件:
panic/page.tsx、blindbox/[code]/page.tsx、ShareCardModal.tsx等多处 - 屏幕阅读器只报 "按钮" 无描述
- 文件:
-
#28 AudioContext 每次 playChime() 新建
- 文件:
src/lib/celebrate.ts - 浏览器限制并发 AudioContext 数量 (~6),快速重复触发可能静默失败
- 修复:缓存复用单个 AudioContext
- 文件:
-
#29 ApiError.name 是 "Error" 而非 "ApiError"
- 文件:
src/lib/api.ts - 堆栈和日志中显示
Error:而非ApiError:,调试不便 - 修复:构造函数加
this.name = "ApiError"
- 文件:
-
#30 blindbox lobby 加载房间失败静默无提示
- 文件:
src/app/blindbox/page.tsx - catch 空处理,用户看到空列表以为没房间
- 文件:
-
#31 theme localStorage 读取不校验合法值
- 文件:
src/lib/theme.ts - 任意字符串被当作 Theme 使用
- 修复:对比
["light", "dark", "system"]白名单
- 文件: