技能|单文件应用并发与安全收尾SOP
> 来源:本次「<我的知识库>工作台」V2.1 安全与健壮性收尾(2026-09-28),五项实改全部取过证。
> 治的是同一类病:看着能用,实则撒谎、抢跑、卡死没反馈、数据被悄悄藏起来。
适用场景
单文件巨石 Web 应用(FastAPI + 内联前端)在功能齐备后做最后一轮收尾:
免登录接口的信息面、错误形状、并发占位、超时降级、被过滤文件的可见性。
不适用:已有成熟网关/鉴权体系的多服务项目。
触发词
/api/me 免登录泄露、默认密码状态外泄、错误形状不统一、detail Not Found、并发抢跑、同一个脚本跑两遍、任务占全局锁、30 分钟排队、超时 900 秒、前端一直转圈、AbortController、resToast 降级、20 篇生成文件看不见、搜不到生成文件、Minimal 信息面、任务占位原子化
操作步骤
-
免登录接口的最小信息面:
-public白名单里的接口(如/api/me)login必须按 Cookie 真实校验:request.cookies.get("wb_session","")==a.get("token","")。
- 未登录只返回登录页所需的最小字段({"login":false,"user":"<作者>"}),不给default_pw/has_recovery/last_login。
- 写死login: True等于告诉任何本机进程"你已登录",还把是否仍在用默认密码一起泄露。 -
错误形状归一(全站只有一种):
- 注册@app.exception_handler(StarletteHTTPException)(需from starlette.exceptions import HTTPException)。
- 映射401/403/404/422 → 中文,统一返回{"ok":false,"error":"中文","auth":code in (401,403)}。
- 把自家缺error的返回补上文案(如{"ok":false,"hits":[]})。 -
并发占位用 set 做 CAS(不要"先查 busy 再加 busy"):
python def _claim(task): busy=_state["busy"]; before=len(busy); busy.add(task) return len(busy) > before
- 三处入口共用(run_task/ 自动任务 / 蒸馏任务);命中重复返回「该任务正在执行中,请稍候」。
- 重活不能包在全局锁内(subprocess.run(timeout=1800)若占锁,第二个任务排队干等且前端无反馈);锁只包占位,随之失效的全局锁变量要删干净(删前删后各 grep 一次)。
- 验证:6 并发打同一任务,应只见 1 个ok:true,其余 0.02s 返回。 -
超时收敛 + 前端降级:
-_llm类默认超时 900s → ≤180s(批处理 300s)。
- 前端api(u,opt,ms)用AbortController做MODEL_MS=20000降级,不取消服务端执行(结果照样落库),返回{ok:false,slow:true,error:"本地模型仍在处理,完成后刷新可看结果"}。
- 抽resToast(r,okMsg,ms)统一处理 slow/成功/失败三态,替掉散落的三元判断。
- 删除类等瞬时操作不套超时——找准句式:只改"上一行含特征串 + 向下 3 行窗口"命中的调用点(全库 6 处里通常只有 3 处该改)。 -
被跳过的文件别真丢(既不污染统计,又可按需可见):
- 过滤生成/规则类文件是对的,但要在同一遍os.walk里顺手登记路径(只getmtime+getsize,不读全文,零 I/O 感知)。
- 接口多返回一个generated数组,页面给一个默认折叠的开关(.genbar+toggleGen)。
- 搜索兜底:正文 0 命中时才按需读生成文件,命中带gen:true、响应带gen_only(实测正文 16–30ms,兜底 42–70ms)。
注意边界
- 显示与否都不改变任何统计——开关只影响可见性,不影响计数口径。
- 删除任何"看起来还有用"的锁/变量前必须 grep 全量引用,确认零残留。
- 前端降级不能取消服务端正在跑的任务,否则用户以为失败了其实没跑。
- 查询参数一律给默认值(
date: str = ""),避免 FastAPI 缺参 422 给 SPA 吐非 JSON。
反例避雷
| 反例 | 为什么错 | 正确做法 |
|---|---|---|
/api/me 白名单里写死 login: True |
任何人都能被告知"已登录",还泄露默认密码状态 | 按 Cookie 真实校验,未登录只给最小字段 |
| 「先查 busy 再加 busy」 | API/监听/调度三方同时通过检查,同一脚本跑两遍互相覆盖 | set.add 的原子性做 CAS(_claim) |
把 subprocess.run(timeout=1800) 包在全局锁里 |
单任务占锁 30 分钟,第二个请求干等 | 锁只包占位,重活挪出锁 |
| 全局 replace 给所有调用加 20s 超时 | 误伤删除类等瞬时操作 | 「上一行特征 + 向下窗口」定位,只改该改的 |
_llm 超时 900s 不做降级 |
前端一直转圈无任何反馈 | 超时压到 ≤180s + 前端 20s 慢速提示 |
| 生成文件只过滤不登记 | 最近产出在页面"消失",其实还在磁盘上 | 同一遍 walk 里登记路径 + 折叠开关 + 搜索兜底 |
PAIRS 断言解包写成 for o, n, _ in PAIRS |
n 实为 new 串,计数永远不等,全部误报 FAIL |
解包 (old, new, cnt) |
必填查询参数(date: str) |
前端漏传 → 422 HTML,SPA 拿到非 JSON | 一律给默认值 |
关联
- 知识库条目:
05-技能库/技能|单文件应用并发与安全收尾SOP.md - 同源:
~/<运行时目录>/skills/web-app-aesthetics-hardening(步骤 M) - 实证:
<项目根目录>\改进落地报告V2.1.md、<项目根目录>\<应用>\app.py.bak_v21a - 相关笔记:
[[技能|全要素缺陷探针法]]、[[技能|Web应用美学加固流程]]、[[技能|应用启动多线程预热与缓存法]]
版本记录
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0.0 | 2026-09-29 | 首版实战固化(五项收尾 + 129 行的有证改动) |
关联笔记
- [[技能|Web应用卡顿定位与提速法]]
- [[技能|应用启动多线程预热与缓存法]]
- [[技能|Web应用美学加固流程]]