# 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 --tenant ` 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 → 库文件变小;