N.05 · 开发 · 2026.05.25 · 约 2 分钟

给 PicSpeak 做第二轮审查:一个竞态、一个关不干净的线程和一份 20MB 的审计日志

距离上一轮审查一个月,上一轮的问题都已闭环。这一轮按后端、前端、基础设施和测试四个方向重新看了一遍,又找出了几个藏在正常运行背后的问题。

5 月 23 日,我给 PicSpeak 做了第二轮深度代码审查。上一轮是 4 月 26 日,当时列出来的高、中优先级问题已经全部修完:审计日志改成异步落库,首页改成服务端组件,前端补上了全局错误边界,N+1 查询换成批量预取。按理说可以松一口气,但我想知道,修完的这一个月里又长出了什么。

这一轮按四个方向并行看:后端、前端、基础设施与部署、测试覆盖。最值得记下来的,是三个平时完全看不出来的问题。

限流器里的读改写

限流的代码先读出当前计数,判断有没有超过上限,再把计数加一写回去。两个请求同时进来,可能都读到「还没到上限」,然后都放行。这是典型的检查与使用之间的竞态,平时流量小,永远不会出事,一旦被人集中请求,限流就形同虚设。

修复是把判断和递增合并成一条数据库语句:插入时如果冲突就加一,而且只在计数还没到上限时才加,再把加完的值返回出来。判断和写入在同一个原子操作里完成,就不存在中间的窗口了。

关闭时被丢掉的任务

后台 worker 关闭时,生成图片的线程池是直接关掉的,不等运行中的任务完成,而一次生成最长可能要三分钟。部署重启的那一刻,正在生成的图片就这么没了。

现在关闭顺序改成:先等待运行中的任务完成,等待时间跟图片生成的超时时间对齐,超时之后才放弃。重启变慢了一点,但用户付了额度的生成不会被悄悄吞掉。

什么都记的审计日志

审计中间件会无条件读取每个请求的完整请求体,包括最大 20MB 的图片上传。日志是安全的底线,可如果它把每一张照片都缓冲一遍,内存就会被拖垮。

修复加了三层防护:图片代理路径整体跳过;二进制和表单上传的请求体不捕获;其余请求的请求体最多只记 4KB。健康检查和文档端点本来就跳过,这次一并保留。

没修的,也写下来

还有两个关键问题这一轮没有动:Clerk 的 JWT 用的是自己实现的 RSA 签名验证,以及两套令牌体系之间的安全不对称。它们都牵涉架构,我标成了需要单独处理的事项,而不是在这轮审查里顺手改掉。

审查表里分数最低的两项是可访问性和测试覆盖:键盘操作、焦点管理几乎空白,核心的评图流程、上传链路和前端界面都没有测试。这两项不会让产品今天出错,但决定了以后改代码敢不敢放手。下一轮,我想先从这里补起。