M4 驱动层 · L09

4062 行单文件后端怎么读:先画端点地图

45 min · 精读 web/app.py 的骨架(用 grep 速览,不通读)

🎒 预备知识:完成上一课「L08」(零基础入口:第 0 课)

本课唯一结论:读巨型单文件的正确姿势是先 grep 路由、再按分组下钻—— 约 60 个端点按职责分成 9 组,每组内聚。别从第 1 行读到第 4062 行。

一、基础:FastAPI 和这个文件的角色

FastAPI 是 Python 的 Web 框架:你用装饰器声明"哪个 URL 路径 + 哪个 HTTP 方法" 交给哪个函数处理,框架负责解析请求、调用函数、返回响应。web/app.py 就是 Easel Web 工作台的全部后端:前端(React)发的每个请求都在这里被处理。

先回答一个该问的问题:为什么 4000 行塞一个文件?这是取舍不是事故—— 单文件让"全局状态、中间件、端点"的可见性最高(都在一起),对单人/小团队快速迭代友好; 代价是必须靠纪律分区(文件内注释分区清晰)+ 外部校验(tests/ 里的 test_web_security.py)。 单文件不是原罪,无结构的单文件才是——这门课教的就是怎么在有结构的单文件里导航。

二、动手:30 秒生成端点清单

cd ~/Code/Projects/easel-repo
grep -n '@app\.\(get\|post\|put\|delete\)' web/app.py | head -60

这一条命令就是你的地图生成器:每个 @app.get(...) 装饰器是一个路由声明。 顺着行号扫一遍,你会发现端点天然聚簇——聚簇就是分区。

三、九组端点地图(每组记一个问题)

分组代表端点读时问自己
画像GET /api/personas六维文件怎么在线编辑?
技能POST /api/skill怎么同步执行一个技能?
配置POST /api/env(白名单)为什么有 _ENV_ALLOWLIST?
对话POST /api/chat/stream下一课主角
素材/产物GET /api/outputs为什么忽略 _ 前缀目录?
账号登录POST /api/login/{platform}LOGIN_RUNNERS 注册表?(L12)
归因GET /api/analytics/{p}headless 浏览器取数?
发布POST /api/publish/{p}分发到各平台脚本?(L07 的双调用方)
运营/api/trends /api/schedule状态存哪?

这张地图同时回答了 L07 的悬案:POST /api/publish/{platform} 直接 Popen skills/shared/scripts/ 里的 xhs_publish.py 等——Web 后端和 agent 是同一份发布脚本的 两个调用方,这就是脚本住 shared 的原因。

四、三个结构性防御(精读点)

① 双层锁:每会话 asyncio.Lock(进程内)+ fcntl.flock 跨进程锁。 为什么需要两把?asyncio.Lock 只管本进程内的并发(同一事件循环里的协程); 但 Easel 的会话可能被两个进程同时推进——Web 后端推进中,用户又开了 CLI chat。 flock 是操作系统级的文件锁,跨进程生效。锁的粒度 = 会话:不同会话互不阻塞, 同一会话严格串行(A9 的 lane 思想在 Web 侧的镜像)。

② local_write_guard + TrustedHost 中间件:防浏览器跨站写本地文件。 Web UI 在浏览器里跑,恶意网页可以让你的浏览器向 localhost:7860 发写请求(CSRF 思路)—— 守卫确保写操作只接受合法来源。有专门测试(test_web_security.py)护着。

③ 监听 0.0.0.0 但有注释辩护(:4060):"监听 0.0.0.0(非仅回环)是为了兼容 Docker/远程容器端口转发访问 Easel 的场景"。写清"为什么"的注释本身就是架构文档—— 没有这行注释,下个人看到 0.0.0.0 会当成安全隐患"修掉",Docker 用户全线失联。 对比 gateway 的 --bind loopback(L01):一个对外一个对内,各自有注释说理由。 安全相关的绑定选择必须留注释,这是给未来审计者的交代。

五、常见踩坑

坑 1:通读 4000 行。第 3 天你还在第 800 行;grep 路由 30 秒有全图。
坑 2:把端点当孤立函数读。端点之间共享大量辅助函数(拼消息/取画像/锁会话), 按组读才能看到共享面。坑 3:忽视轮询类端点。GET /api/login/{p}/status 这类 轮询端点是"文件即协议"(L12)的 Web 消费侧——单独看它没意义,连着状态文件看才懂。

六、检索练习

七、出口检验

不看资料,画出 9 组端点地图 + 每组一个代表端点。追问准备:为什么对话端点要单独一整层流式机制?(下一课答案)

一手源:web/app.py 的分区注释 + grep -n '^# ---\|^# ===' 看作者的分区方式。