docs: Phase 4 计划评审修复(health分级/VITE_BASE_URL/healthcheck无curl/dockerignore通配/resolver快照方向/老库拷贝纪律等)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-14 08:01:05 +08:00
parent e0ad95915f
commit 4829baf9c1
@@ -21,14 +21,15 @@
- Test: `tests/test_admin_serving.py` - Test: `tests/test_admin_serving.py`
- [ ] 失败测试: - [ ] 失败测试:
- `GET /admin/` 返回 index.html(dist 存在时);`GET /admin/assets/xxx` 静态可达;未知路径 `/admin/xxx` fallback 返回 index.html(SPA history 路由);`/admin` 前缀**不经过租户中间件解析**(豁免规则扩 `/admin`——注意精确:`/admin` 与 `/admin/`,防 `/admin-evil`);dist 不存在时(本地测试环境)`/admin/` 返回 503/404 带说明(开发用 pnpm dev,不炸整个应用) - `GET /admin/` 返回 index.html(dist 存在时);`GET /admin/assets/xxx` 静态可达;未知路径 `/admin/xxx` fallback 返回 index.html(**加固项**:admin 实为 hash 路由非必需,但深链/刷新兜底;**fallback handler 必须只对 /admin 前缀生效,非 /admin 的 404 保持默认 JSON**——防 API 404 被 SPA 吞);`/admin` 前缀**不经过租户中间件解析**(豁免规则扩 `/admin`——精确 `== "/admin" or startswith("/admin/")`,防 `/admin-evil`);dist 不存在时(本地测试环境)`/admin/` 返回 503/404 带说明(开发用 pnpm dev,不炸整个应用)
- **/health 分级(spec §9 明文)**:进程状态 + 各租户 engine 状态 + 域名表版本;单租户库损坏 → 200 带 per-tenant degraded(不整体 500);registry.runtimes() 逐租户探库(轻查询,如 `SELECT 1`)
- [ ] 实现: - [ ] 实现:
- main.py:`ADMIN_DIST = BASE_DIR / "admin" / "dist"`;存在则挂 StaticFiles(directory=dist, html=True) 到 `/admin`——**SPA fallback**:Starlette 静态 html=True 不做 history fallback,需自定义 404 handler 限定 `/admin/*` 非 api 路径回 index.html(读 dist 结构确认 assets 子目录) - main.py:`ADMIN_DIST = BASE_DIR / "admin" / "dist"`;存在则挂 StaticFiles(directory=dist, html=True) 到 `/admin`——SPA fallback:自定义 404 handler 限定 `/admin/*` 回 index.html(读 dist 结构确认 assets 子目录)
- host_middleware 豁免扩 `/admin`(精确 startswith("/admin/") or == "/admin"——防误豁免 `/adminX`) - host_middleware 豁免扩 `/admin`;`/health` 升级为分级响应(上述测试钉死)
- Dockerfile:阶段一 node:20-alpine(WORKDIR /build、拷 admin/、corepack enable pnpm、pnpm install --frozen-lockfile、pnpm build);阶段二 python:3.13-slim(拷 app/ static/ seed_profiles/ scripts/ requirements、pip install、COPY --from=build /build/admin/dist admin/dist);CMD uvicorn --workers 1 - Dockerfile:阶段一 node:20-alpine(WORKDIR /build、拷 admin/、corepack enable + **package.json 的 packageManager 字段钉 pnpm 版本**、pnpm install --frozen-lockfile、`VITE_BASE_URL=/admin/ pnpm build`——**资源前缀必须 /admin/ 否则挂前缀后白屏**);阶段二 python:3.13-slim(拷 app/ static/ seed_profiles/ scripts/ requirements、pip install、COPY --from=build /build/admin/dist admin/dist);CMD uvicorn --workers 1
- docker-compose.yml:`name: all-kefu`(互删教训);单 app 服务;端口 8018:8018;env_file .env;volume `./data:/app/data` + `./tenants:/app/tenants`;TZ=Asia/Shanghai;mem_limit 768m;logging json-file max-size 10m max-file 3;healthcheck curl /health - docker-compose.yml:`name: all-kefu`(互删教训);单 app 服务;端口 8018:8018;env_file .env;volume `./data:/app/data` + `./tenants:/app/tenants`;TZ=Asia/Shanghai;mem_limit 768m;logging json-file max-size 10m max-file 3;healthcheck **CMD-SHELL 用 python urllib(slim 无 curl)**打 /health
- .dockerignore:.venv、node_modules、dist、.worktrees、tests、docs、data、tenants、platform.db*、.git - .dockerignore:.venv、**`**/node_modules`、`admin/dist`**(裸 node_modules/dist 只匹配根)、.worktrees、tests、docs、data、tenants、platform.db*、.git
- [ ] 验收:后端全量(595+新增)+ admin pnpm build 后本地起 FastAPI 验证 /admin/ 服务(冒烟)+ `docker compose build` 成功(本机 Docker 可用则构建验证,不可用则文档注明服务器构建) - [ ] 验收:后端全量(595+新增)+ admin `VITE_BASE_URL=/admin/` 构建后本地起 FastAPI 验证 /admin/ 服务与资源路径(冒烟)+ `docker compose build` 成功(本机 Docker 可用则构建验证,不可用则文档注明服务器构建)
- [ ] 提交 `feat: admin 生产服务(/admin SPA fallback) + Docker两阶段构建 + 生产compose` - [ ] 提交 `feat: admin 生产服务(/admin SPA fallback) + Docker两阶段构建 + 生产compose`
### Task 2: 存量导入脚本 import_legacy.py ### Task 2: 存量导入脚本 import_legacy.py
@@ -36,10 +37,10 @@
**Files:** **Files:**
- Create: `scripts/import_legacy.py`、`tests/test_import_legacy.py` - Create: `scripts/import_legacy.py`、`tests/test_import_legacy.py`
- [ ] 失败测试(用两个老项目的真实 db 副贝造夹具——拷到 tmp): - [ ] 失败测试(老库路径经 env 指路(如 ALL_KEFU_TEST_LEGACY_JC/AUTO),**缺失则 skipif**——机器相关且含真实数据;测试只拷结构+少量行到 tmp 或造脱敏小库):
- 导入 jc 库 → 租户库(bootstrap 后的 jiancai)行数一致(五表对账);**conversation_id 原样**(external_key 连续性);session_id/page_token/status 原样;pending 推送日志 → failed 加注;**幂等**(跑两遍第二遍零变化) - 导入 jc 库 → 租户库(bootstrap 后的 jiancai)行数一致(五表对账 + **最近 10 条会话抽查**【spec §8 原文】);**conversation_id 原样**(external_key 连续性);session_id/page_token/status 原样;pending 推送日志 → failed 加注;**幂等**(跑两遍第二遍零变化)
- auto 库同理 - auto 库同理
- [ ] 实现:读老库(aiosqlite 只读)逐表 SELECT → 按目标租户库 INSERT OR IGNORE(幂等键:conversation_id/表主键);对账报告(每表 行数源/目标/跳过);`--source <db路径> --tenant <slug>` CLI;README 用法段 - [ ] 实现:读老库(aiosqlite 只读)逐表 SELECT → 按目标租户库 INSERT OR IGNORE(幂等键:conversation_id/表主键);对账报告(每表 行数源/目标/跳过 + 抽查 10 条摘要);`--source <db路径> --tenant <slug>` CLI;README 用法段
- [ ] 提交 `feat: 存量导入脚本(保ID/session状态、pending→failed、幂等、对账)` - [ ] 提交 `feat: 存量导入脚本(保ID/session状态、pending→failed、幂等、对账)`
### Task 3: 数据归档脚本 archive.py ### Task 3: 数据归档脚本 archive.py
@@ -60,9 +61,9 @@
1. 环境与前置(服务器、Python/Docker、**pip install -r requirements.txt 含 python-multipart**、**ADMIN_PASSWORD 必设**(默认值启动警告)、TENANT_SOURCE=db) 1. 环境与前置(服务器、Python/Docker、**pip install -r requirements.txt 含 python-multipart**、**ADMIN_PASSWORD 必设**(默认值启动警告)、TENANT_SOURCE=db)
2. 首次部署流程:clone → .env(逐项说明)→ bootstrap_platform.py(超管引导+两租户导入)→ docker compose up → 健康检查 2. 首次部署流程:clone → .env(逐项说明)→ bootstrap_platform.py(超管引导+两租户导入)→ docker compose up → 健康检查
3. 反代配置(1Panel):租户域名→127.0.0.1:8018;**admin 两形态**(独立域名 或 路径分流到 /admin);WS 单套升级头;/static/widget.js no-cache;**/api/public 放行**(头像);主 conf include 行检查;nginx -t 后 reload 3. 反代配置(1Panel):租户域名→127.0.0.1:8018;**admin 两形态**(独立域名 或 路径分流到 /admin);WS 单套升级头;/static/widget.js no-cache;**/api/public 放行**(头像);主 conf include 行检查;nginx -t 后 reload
4. 存量迁移:老项目 db 拷贝 → import_legacy.py → 对账核对 4. 存量迁移:老项目 db 拷贝纪律(**停老容器或 SQLite backup API/VACUUM INTO 导出,禁运行时裸 cp**)→ scp 到新机 → import_legacy.py → 对账核对
5. 切换 runbook(独立节):验证清单(不切域名全功能验证)→ 切反代 → 观察一周(breach_rate、推送成功率、磁盘)→ 老项目停用归档 5. 切换 runbook(独立节):验证清单(不切域名全功能验证)→ 切反代 → 观察一周(breach_rate、推送成功率、磁盘)→ 老项目停用归档
6. 日常运维:备份(platform.db + tenants/ 用 SQLite backup API,**禁运行时直拷**;季度恢复演练)、日志(封顶已配)、归档(archive.py 用法)、升级发版(镜像 tag 回滚 30 秒) 6. 日常运维:备份(platform.db + tenants/ 用 SQLite backup API,**禁运行时直拷**;季度恢复演练)、日志(封顶已配;**PII 对齐**:引擎日志现存会话内容摘录,与 spec §9"不打聊天内容"冲突——手册如实记当前状态并在观察期收口)、归档(archive.py 用法)、升级发版(build 后打版本 tag、compose image: 引用 tag,回滚=改 tag up 30 秒)、**加租户=反代加一条不起容器**、bootstrap 执行位置(`docker compose run --rm app python scripts/bootstrap_platform.py`)
7. 踩坑实录(继承 jc:compose 撞名互删、WS 426、widget 缓存、1Panel include、bottom-left 溢出 + 本项目新增:mem_limit、TZ、/api/public 404) 7. 踩坑实录(继承 jc:compose 撞名互删、WS 426、widget 缓存、1Panel include、bottom-left 溢出 + 本项目新增:mem_limit、TZ、/api/public 404)
- [ ] openresty 模板:完整可抄配置(upstream + 租户 server 块 + admin server 块两形态注释 + WS + 缓存策略) - [ ] openresty 模板:完整可抄配置(upstream + 租户 server 块 + admin server 块两形态注释 + WS + 缓存策略)
- [ ] 提交 `docs: 部署手册(继承jc血泪+切换runbook+备份演练)+ openresty模板` - [ ] 提交 `docs: 部署手册(继承jc血泪+切换runbook+备份演练)+ openresty模板`
@@ -70,7 +71,7 @@
### Task 5: 清理单部署相关批次 ### Task 5: 清理单部署相关批次
**Files:**(按清理单逐项) **Files:**(按清理单逐项)
- resolver 回退链接入 platform_settings(config_views 六个 resolver 读 env 前先查 platform_settings——**功能闭环**,测试覆盖:设置 ai.api_key 后租户空槽取它) - resolver 回退链接入 platform_settings——**架构方向(同步 resolver 无法读异步 DB)**:lifespan 启动把 platform_settings 快照进内存 dict + PUT /settings 提交后刷新快照(workers=1 下与内存 token 表同模式);回退链 = 租户槽位 → 快照 → env;测试覆盖:设置 ai.api_key 后租户空槽取它、PUT 后即时生效、快照缺失回退 env
- email/test 与 push/test 限流(复用 LoginLimiter 模式,5 次/15 分钟每租户) - email/test 与 push/test 限流(复用 LoginLimiter 模式,5 次/15 分钟每租户)
- ECharts 收窄(plugins/echarts.ts 只注册 Bar/Line/Pie + 现用组件)+ 租户切换器 useTenantOptions composable 收拢三页(conversations/config/stats) - ECharts 收窄(plugins/echarts.ts 只注册 Bar/Line/Pie + 现用组件)+ 租户切换器 useTenantOptions composable 收拢三页(conversations/config/stats)
- 锁屏/通知组件代码删除(enabled:false 已不可达——删组件文件与配置项,grep 确认零引用) - 锁屏/通知组件代码删除(enabled:false 已不可达——删组件文件与配置项,grep 确认零引用)