Files
ai-kefu-admin/docs/superpowers/plans/2026-10-14-phase4-deploy-migration.md
T

9.6 KiB
Raw Blame History

Phase 4:部署与迁移实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 交付上线全部工件:Docker 两阶段构建与生产 compose、admin dist 的 FastAPI 服务、存量数据导入脚本、数据归档脚本、部署手册(继承 jc 血泪)、切换 runbook;顺手清 Phase 4 清理单中部署相关项。

Architecture: 单容器(python:3.13-slim + admin dist 拷入);FastAPI 在 /admin/ 前缀服务 SPA(history fallback 限定该前缀)——反代层可走"独立管理域名指到同容器"或"路径分流"两形态(spec §7.1 同源零 CORS);存量导入用 scripts/import_legacy.py(保 conversation_id/session/page_token/status,幂等,对账报告)。

Tech Stack: Docker multi-stage(node:20-alpine 构建 admin → python:3.13-slim);其余同现状。

参照 spec: §8(数据迁移)、§9(部署全部条目——逐项对照落实);CLAUDE.md「Phase 4 部署记档」与「Phase 4 清理单」9 项。

重要边界:本阶段交付部署工件(代码/脚本/文档/配置模板),不执行实际部署。 实际部署到阿里云时按用户全局 SOP 另行确认流程(全局 CLAUDE.md 规程)。


Task 1: admin 生产服务与 Docker 两阶段构建

Files:

  • Modify: app/main.py(挂 /admin/ 静态 + SPA fallback)、Dockerfile(新建两阶段)、docker-compose.yml(新建)、.dockerignore(新建)

  • Test: tests/test_admin_serving.py

  • 失败测试:

    • 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:自定义 404 handler 限定 /admin/* 回 index.html(读 dist 结构确认 assets 子目录)
    • host_middleware 豁免扩 /admin;/health 升级为分级响应(上述测试钉死)
    • 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 **CMD-SHELL 用 python urllib(slim 无 curl)**打 /health
    • .dockerignore:.venv、**/node_modules、admin/dist(裸 node_modules/dist 只匹配根)、.worktrees、tests、docs、data、tenants、platform.db*、.git
  • 验收:后端全量(595+新增)+ admin VITE_BASE_URL=/admin/ 构建后本地起 FastAPI 验证 /admin/ 服务与资源路径(冒烟)+ docker compose build 成功(本机 Docker 可用则构建验证,不可用则文档注明服务器构建)

  • 提交 feat: admin 生产服务(/admin SPA fallback) + Docker两阶段构建 + 生产compose

Task 2: 存量导入脚本 import_legacy.py

Files:

  • Create: scripts/import_legacy.py、tests/test_import_legacy.py

  • 失败测试(老库路径经 env 指路(如 ALL_KEFU_TEST_LEGACY_JC/AUTO),缺失则 skipif——机器相关且含真实数据;测试只拷结构+少量行到 tmp 或造脱敏小库):

    • 导入 jc 库 → 租户库(bootstrap 后的 jiancai)行数一致(五表对账 + 最近 10 条会话抽查【spec §8 原文】);conversation_id 原样(external_key 连续性);session_id/page_token/status 原样;pending 推送日志 → failed 加注;幂等(跑两遍第二遍零变化)
    • auto 库同理
  • 实现:读老库(aiosqlite 只读)逐表 SELECT → 按目标租户库 INSERT OR IGNORE(幂等键:conversation_id/表主键);对账报告(每表 行数源/目标/跳过 + 抽查 10 条摘要);--source <db路径> --tenant <slug> CLI;README 用法段

  • 提交 feat: 存量导入脚本(保ID/session状态、pending→failed、幂等、对账)

Task 3: 数据归档脚本 archive.py

Files:

  • Create: scripts/archive.py、tests/test_archive.py

  • 失败测试:造 >N 天会话 → 归档(导出 JSON 到 data/archives/)→ 清库 → VACUUM → 库文件变小;<N 天不动;--dry-run 只列不动

  • 实现:--tenant slug --days N(默认 365) --dry-run;删除序(lead_push_logs→evaluations→profiles→messages→conversations,倒 FK 序);导出含完整行;VACUUM;报告

  • 提交 feat: 数据归档脚本(导出/清理/VACUUM/dry-run)

Task 4: 部署手册与 openresty 模板

Files:

  • Create: docs/deploy-production.md、deploy/openresty-all-kefu.conf.template

  • deploy-production.md 章节(继承 jc 血泪手册全部条目 + CLAUDE.md Phase 4 记档落位):

    1. 环境与前置(服务器、Python/Docker、pip install -r requirements.txt 含 python-multipart、ADMIN_PASSWORD 必设(默认值启动警告)、TENANT_SOURCE=db)
    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
    4. 存量迁移:老项目 db 拷贝纪律(停老容器或 SQLite backup API/VACUUM INTO 导出,禁运行时裸 cp)→ scp 到新机 → import_legacy.py → 对账核对
    5. 切换 runbook(独立节):验证清单(不切域名全功能验证)→ 切反代 → 观察一周(breach_rate、推送成功率、磁盘)→ 老项目停用归档
    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)
  • openresty 模板:完整可抄配置(upstream + 租户 server 块 + admin server 块两形态注释 + WS + 缓存策略)

  • 提交 docs: 部署手册(继承jc血泪+切换runbook+备份演练)+ openresty模板

Task 5: 清理单部署相关批次

Files:(按清理单逐项)

  • 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 分钟每租户)
  • ECharts 收窄(plugins/echarts.ts 只注册 Bar/Line/Pie + 现用组件)+ 租户切换器 useTenantOptions composable 收拢三页(conversations/config/stats)
  • 锁屏/通知组件代码删除(enabled:false 已不可达——删组件文件与配置项,grep 确认零引用)
  • 每项独立提交;全量回归(后端+vitest+build)
  • 提交(多项分别提交)

Task 6: Phase 4 收尾

  • 全量回归(根+tests/ 双跑;vitest;build;docker compose build 复验)
  • CLAUDE.md/README 终态更新(项目完成状态、清理单结项核对、上线前检查清单浓缩版)
  • 提交 chore: Phase 4 收尾——部署工件齐备

显式不做(Phase 4 范围外)

实际部署执行(按全局 SOP 另行确认);真实域名 DNS 变更;UniService 侧配置;CI/CD 流水线;监控告警系统(推送失败告警二期)。

执行注意

部署工件全部本地可验证(构建/测试/文档);涉及真实服务器的步骤只写进 runbook 不执行。清理单 Task 5 允许按耗时裁剪(resolver 接线若超半日可单独挂回清单——但它是平台设置页的功能闭环,优先保)。