12 KiB
PRD — App Factory / Config Unification Batch 2 Follow-up
背景
基于 origin/main 最新代码的完整复审,Batch 2 已经完成了两项重要统一:
- 五个服务入口
main.py已统一切到shared.app_factory.create_service_app(...) - 五个服务配置
config.py已基本统一切到shared.config_base
对应远端提交主线为:
14484caAdd shared modules: app factory, config base, error handlers with tests6d94b08Refactor all apps to use shared config and app factory378a4e5Update home pages route and add test templates
这说明 Batch 2 的方向是正确的,且公共启动逻辑已经完成第一轮收敛。
但完整复审后,当前状态还不能定义为“重构完成”。现有实现仍存在三类结构性残留:
- 入口层和业务层仍大量依赖
sys.path.insert(...)做导入兜底 shared.app_factory已把slowapi变成统一启动依赖,但测试/安装契约没有同步补齐- home/admin 的认证守卫仍是服务内私有实现,没有进一步抽象为 shared guard helper,后续容易再次漂移
因此需要补一轮 follow-up 修正,目标不是继续大规模抽象,而是把 Batch 2 从“表面统一”推进到“结构上站稳”。
本轮目标
- 消除本轮重构后最显著的导入技术债,减少
sys.path.insert(...)残留 - 补齐
slowapi的依赖契约与测试契约,确保 shared factory 在开发、测试、部署三条链路上一致 - 为 home/admin 当前的路由守卫模式定义统一抽象边界,避免未来各服务重复复制
_require_auth(...) - 补齐回归验证标准,让“通过”不再只表示 shared 单测通过,而是关键服务入口也能被实际导入/启动
非目标
本轮不做以下内容:
- 不重写整站认证体系
- 不把所有服务的 auth/token 校验一次性全量抽到 shared
- 不强行改成 Python package 完整安装式布局(如一次性引入 pyproject + editable install 作为唯一导入方案)
- 不改动业务权限模型本身
- 不为所有历史测试做全面 fixture 重构
本轮只处理 Batch 2 复审中已经暴露、且会持续影响后续维护的最小必要问题。
发现的问题
P0. shared factory 统一后,slowapi 变成强依赖,但测试/环境契约未同步
现象
复审中执行:
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
结果通过:
13 passed
但执行 auth 侧 smoke tests:
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
结果在 collection 阶段失败:
ModuleNotFoundError: No module named 'slowapi'
根因
shared.app_factory.py 直接导入:
from slowapi import _rate_limit_exceeded_handlerfrom slowapi.errors import RateLimitExceeded
这意味着一旦服务入口统一切到 shared factory,slowapi 就不再是某些服务“可选依赖”,而是所有服务公共启动路径的硬依赖。
但是当前测试环境或依赖清单没有保证这一点,导致:
- shared 层合同测试可以过
- 真实服务入口测试却无法 import app
这会制造“重构似乎完成,但服务级 smoke 根本没跑”的假阳性。
需要修改
- 检查并统一项目依赖声明,确保
slowapi出现在实际安装入口中- 根
requirements*.txt/ 服务依赖清单 / 部署安装脚本中必须一致
- 根
- 如果项目采用多入口安装方式,至少保证:
- 本地开发安装能拿到
slowapi - CI / 测试安装能拿到
slowapi - 生产部署安装能拿到
slowapi
- 本地开发安装能拿到
- 补一条最小 smoke 验证:
- 能成功
import auth/src/main.py对应 app - 能成功
import home/src/main.py对应 app
- 能成功
验收标准
以下命令在标准开发环境中必须通过:
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
不允许再出现 ModuleNotFoundError: slowapi。
P1. sys.path.insert(...) 残留过多,说明 shared 化尚未形成稳定导入边界
现象
本轮复审中,目标文件本身仍保留路径注入:
auth/src/config.pyblog/src/config.pycanvas/src/config.pyprompt/src/config.pyhome/src/config.pyauth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.py
此外,搜索结果表明更多 route / service / test 文件中也仍在重复同一模式。
问题
这说明当前 shared 重构虽然抽走了公共逻辑,但没有真正建立清晰的模块导入边界。直接后果有三个:
- 各服务仍默认依赖“脚本式运行 + 手动改 sys.path”
- 测试文件需要反复插 path 才能 import
src.* - 后续任何目录结构变化,都会在多个服务同时引发脆弱导入问题
本轮建议策略
不要一口气清理全仓所有 sys.path.insert(...),而是先做“核心路径收缩”:
第一阶段:只处理 Batch 2 涉及的入口与配置
目标:
- 让五个
main.py - 五个
config.py
尽量不再各自复制路径注入模板。
推荐方案二选一:
方案 A:抽一个 shared import bootstrap helper
- 新建如
shared/imports.py或等价模块 - 提供统一函数,例如:
ensure_project_root(__file__, levels=3)
- 所有入口/配置文件统一调用这一 helper,而不是手写
Path(...); if str(...) not in sys.path: sys.path.insert(...)
方案 B:明确项目运行约定,减少入口内 path hack
- 如果现有运行方式允许,以仓库根目录为 working directory 运行 uvicorn / pytest
- 配合测试配置修正 import path
- 让入口文件不再自己改
sys.path
本轮更推荐 方案 A,原因:
- 改动小
- 风险低
- 不要求一次性重建整个包结构
- 能先消掉最显眼的重复模板
第二阶段:只在 follow-up 里列出,不要求本轮全做
- route 层残留
sys.path.insert(...) - service 层残留
sys.path.insert(...) - test 层残留
sys.path.insert(...)
这部分可以作为下一轮技术债治理,不在本轮强制清仓。
验收标准
至少以下 10 个文件不再各自手写重复的 sys.path.insert(...) 模板,或改为统一 helper:
auth/src/config.pyblog/src/config.pycanvas/src/config.pyprompt/src/config.pyhome/src/config.pyauth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.py
并补一条测试/检查,验证这些入口仍可被正常导入。
P1. home/admin 路由守卫真实存在,但仍是私有实现,后续易漂移
现象
home/src/routes/admin.py 目前已经具备明确的服务端守卫逻辑:
_require_auth(...)负责 cookie 用户校验- 未登录跳转到 auth 登录页,并携带 redirect
- 已登录但无权限时拒绝或回首页
- service token 明确禁止访问 home admin
这说明 ephron.ren 现状是:
- 前台公开页无全局 guard
- 管理后台路由有服务端 guard
这一方向本身是合理的。
问题
现在的 guard 逻辑仍被实现为 home 服务内部私有函数,风险在于:
- 如果 blog/canvas/prompt 后续继续补 admin 规则,很容易各自再复制一份
_require_auth(...) - 登录跳转、redirect 处理、service token 禁入策略可能在不同服务间漂移
- 审计时很难快速确认“所有 admin 路由是否遵循同一守卫契约”
本轮要求
本轮不要求把所有服务的鉴权都重构完,但至少要把“守卫抽象边界”定下来。
推荐最小改法:
- 在
shared/新增 admin guard helper(命名可调整)- 负责未登录重定向的通用拼装
- 负责 service token 是否允许访问 admin 的统一策略
- 负责权限失败的统一响应约定
home/src/routes/admin.py改为消费 shared helper,而不是继续把完整策略内嵌在文件内部- 先以 home 为落点,其他服务后续增量迁移
范围边界
本轮 shared guard helper 不要求覆盖:
- 所有业务权限判断细节
- 各服务特有的资源级校验
- 所有服务 token 行为差异
只需要先统一“admin 入口守卫”的公共骨架。
验收标准
home/src/routes/admin.py中不再保留大段内联守卫模板代码- 登录跳转、redirect、service token 禁止访问 admin 的公共逻辑进入 shared helper
- 现有 home admin 行为保持不变
P2. 当前回归验证偏 shared 层,缺少“服务入口可导入”级别的合同测试
现象
当前已经有的 shared/contract 测试能证明:
- 安全头策略存在
- reuse contract 存在
但它们不能证明:
- 五个服务入口都还能成功 import
- shared app factory 的依赖在真实入口层可用
- 各服务在统一 factory 下仍保持自己的 startup 责任
需要补齐
新增一组轻量合同测试,目标不是测业务,而是测“入口仍然站得住”:
建议至少覆盖:
- 五个服务入口可导入
auth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.py
- 导入后存在
app app.routes中至少包含/health- docs 开关、static mount、router 注入保留各自行为(可做轻量断言)
验收标准
新增测试在标准环境下通过,并能在 shared factory 依赖缺失、入口装配损坏、health route 丢失时给出明确失败信号。
建议实施顺序
Task 1 — 补齐 slowapi 依赖契约
- 检查根依赖声明
- 检查测试环境安装入口
- 补齐缺失项
- 跑 auth smoke tests 验证
Task 2 — 收缩 Batch 2 目标文件中的 path hack
- 先只处理五个
main.py - 再处理五个
config.py - 如果采用 shared helper,保证 helper 本身足够轻量,不引入新的循环依赖
Task 3 — 提取 home admin guard 公共骨架
- 保留当前行为不变
- 只抽公共流程,不重写权限模型
- 先让
home成为第一个 shared guard 使用者
Task 4 — 补轻量入口合同测试
- 测入口 import
- 测
/health - 测 shared factory 关键装配结果
建议涉及文件
必改
shared/app_factory.pyshared/config_base.py(如需辅助 import/helper,可一并评估)auth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.pyauth/src/config.pyblog/src/config.pycanvas/src/config.pyprompt/src/config.pyhome/src/config.py- 依赖声明文件(按实际项目结构)
可能新增
shared/imports.py或等价 helpershared/admin_guard.py或等价 helper- 新的入口合同测试文件
本轮只观察,不强制修改
home/src/services/auth.py- 其他 route/service/test 中的历史
sys.path.insert(...)
验证命令
至少执行并记录以下命令:
cd /home/ubuntu/ephron.ren
git fetch origin
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
如果新增了入口合同测试,再补:
python -m pytest tests/test_service_entrypoints.py -q
如有 home/admin 相关抽象变动,补对应最小验证:
python -m pytest home/tests -q
完成定义
满足以下条件才算本轮完成:
slowapi不再导致服务入口测试 import 失败- Batch 2 涉及的 10 个入口/配置文件不再各自复制同一段 path 注入模板,或至少统一为同一个 helper
- home/admin 的公共守卫骨架进入 shared 层,当前行为无回归
- 新增轻量入口合同测试,能证明五个服务入口仍可正常装配
- 所有新增/修改测试通过
备注
本 PRD 的定位不是重新定义 Batch 2,而是对 Batch 2 做“完整复审后的结构补强”。
换句话说,前一轮已经解决“多个服务启动方式不统一”的问题;这一轮要解决的是:
- 统一之后是否真的可维护
- 统一之后测试是否真能覆盖到入口层
- 统一之后 guard/导入边界是否足够稳定
这轮做完,才能把 Batch 2 从“完成了一次重构”提升为“建立了可持续的基础设施约束”。