跳转至

听云 OCM 执行任务单 V3 · MFR 开放报价上线(双实例分流架构)

字段
任务编号 OCL-DEPLOY-20260827-03(V3 · 承接用户批准的 P1+P2 决策)
签发方 听写 DT
执行方 听云 OCM
签发时间 2026-08-27
关联 DTL L-20260827-01(根因)/ L-20260827-02 §十二(D01 回归 3/4 + 两缺口确诊)
用户决策 P1+P2 都做(2026-08-27),约束=仅上 MFR 开放报价,其余功能零扰动
状态 🟦 本单已被等效实现取代,留档作废(2026-08-27 深夜收口):T-A → 被 OCL L-20260827-04 S8-v3 Referer 兜底等效覆盖且更优(零侵入 OPR);T-B/T-C → 由 WDE-TASK-L-20260827-01 升级版「HEAD 基线替换」承接(HEAD 含 v1+v2 双路由族,单实例接管 :8000,无需另起 :8010 分流实例)。详见 DTL L-20260827-02 §十六。

⚠️ 零、为何不能"直接换掉旧后端"(必读)

黑盒证据(DTL L-20260827-02 §十二.3):生产 OpenAPI 64 条路径全部 v1、零 v2;而本地新代码只有 v2、零 v1。 → 若用新代码整体替换 :8000,OPR/WKR/CST/DL 四端与所有存量调用方会集体断链,直接违反"其余不动"约束。

因此本单采用双实例并存 + 网关按前缀分流

                 ┌───────────────────────────────┐
公网 :80 Caddy ──┤ /_next/*          → :3000 (MFR前端)   ← T-A 改上游
                 │ /api/v2/*         → :8010 (新后端·v2) ← T-C 新增
                 │ /api/*(其余v1)    → :8000 (旧后端·原样)← 不碰
                 │ 其他全部           → 原样              ← 不碰
                 └───────────────────────────────┘
  • 新后端仅暴露 /api/v2/* 一个入口面 → 影响面被网关强制圈定
  • 旧 :8000 后端进程/容器一个字节都不动
  • 全程可回滚:Caddy bak 一键还原

一、T-A(P1)Caddy 根路径 /_next/* 上游切换 3100→3000

目的:MFR 页面 JS/CSS 从白屏变可用。依据:OPR 当前页面 0 处引用根 /_next/*(实测 15 处全在 /admin/_next/)

# A-1 备份
cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak.$(date +%Y%m%d%H%M%S)

# A-2 仅替换这一处目标端口(3100→3000,行内容含 "_next" 注释那一块)
sed -i '/handle \/_next\/\*/,+2s/127\.0\.0\.1:3100/127.0.0.1:3000/' /etc/caddy/Caddyfile
# 验证改动命中:应看到 /_next/* 块里是 :3000,而 /admin/* 与 兜底仍是 :3100
grep -n -A1 "handle /_next/\|handle /admin/\|^  handle {" /etc/caddy/Caddyfile | head -12

# A-3 校验+重载(容器化部署者请用你们的 caddy reload 惯例)
caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy || \
{ echo "validate/reload 失败→回滚"; cp $(ls -t /etc/caddy/Caddyfile.bak.* | head -1) /etc/caddy/Caddyfile; }

# A-4 断言(T-A 通过标准)
echo "A7 根chunk ="; curl -s -o /dev/null -w "%{http_code} %{content_type}\n" http://127.0.0.1/_next/static/chunks/webpack-2e534b1aef5f9925.js   # 期望 200 application/javascript
echo "A8 admin页 ="; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/admin/login                                                        # 期望 200
echo "A9 mfr页  ="; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/mfr/                                                                # 期望 200

二、T-B(P2)新后端实例:从听写暂存区取包并启动

B-1 取包坐标(听写已于今日打包完成并核验)

本地暂存区(你的电脑上) d:\AC\TF\hk2026\ocm\private\dip1\code\dip1\
本次增量 backend/(app=225 + tests=81 + alembic=6 + 配置示例 = ~408 文件 / 4.0MB)
包内证据锚点 backend/app/interfaces/api/qsv_router.py L131 @router.post("/api/v2/qsv/quote/mfr-open")
私有文件保护 本次同步为 robocopy /E 非镜像,你维护的私有生产配置未被动过;生产配置请继续沿用你 OCL 里现行的那套

关键环境变量(与旧实例同值,字段模板见 backend/.env.prod.local.example): DATABASE_URL(同库或独立只读副本均可)、Keycloak/JWT 三项、DIP1_DISABLED_MODULES 留空=5服务全挂(反正网关只放行 /api/v2/*)。

B-2 ECS 启动第二实例(建议 host 端口 127.0.0.1:8010,不得占用 8000/3000/3100~3102/9100)

部署形态(docker run / compose 第二 service / systemd uvicorn)遵循你 OCL 现行惯例即可;以下是通用最小命令参考:

# 方式一:裸机 uvicorn 参考(在解包目录)
cd <解包后 backend 目录>
DATABASE_URL="<同旧实例>" JWT_SECRET="<同>" python -m uvicorn app.main:app --host 127.0.0.1 --port 8010
# 生产请用你现行的常驻方式承载(docker/compose/systemd 均可),务必 bind 127.0.0.1 而非 0.0.0.0

# B-2 实例自检(必须逐条过)
curl -s http://127.0.0.1:8010/health                        # 期望 200 {"status":"ok","db":"ok"...}
curl -s http://127.0.0.1:8010/openapi.json | grep -c '"mfr-open"'   # 期望 >= 1

B-3 数据库迁移检查点

新实例如报缺表/缺列(尤其 SCAT/P10 相关),执行 alembic upgrade head(alembic.ini 随包);迁移报错原文整体回传,不要自行改表。


三、T-C(P2)Caddy 分流:新增 handle /api/v2/*

插入位置必须紧贴在现有 handle /api/*上方

cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak2.$(date +%Y%m%d%H%M%S)
gawk -i inplace '
/handle \/api\/\*\t?\s*\{/ && !done {
    print "  # ========== API v2 新后端实例(MFR 开放报价专用分流) =========="
    print "  handle /api/v2/* {"
    print "    reverse_proxy http://127.0.0.1:8010"
    print "  }"
    print ""
    done=1
}
{print}
' /etc/caddy/Caddyfile
grep -n "handle /api" /etc/caddy/Caddyfile      # 顺序应为 /api/v2/* 在前、/api/* 在后
caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy

T-C 断言(期望值=通且不串门)

echo "C1 v2路由存在性:"; curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1/api/v2/qsv/quote/mfr-open -H 'Content-Type: application/json' -d '{}'
#     期望 ∈ {400,401,403,422}(只要不是 404/502 即代表到达新实例业务层)
echo "C2 GET应405:"; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/api/v2/qsv/quote/mfr-open        # 期望 405
echo "C3 旧v1不受扰:"; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/api/v1/qsv/quotes               # 期望 与改造前一致(非404即原样)
echo "C4 三端仍在:"; for p in admin/login wkr/ cst/ dl/; do curl -s -o /dev/null -w "$p=%{http_code} " http://127.0.0.1/$p; done; echo


四、回执模板(做完或卡住都先回,10 行内)

[听云回执 OCL-DEPLOY-20260827-03]
T-A: A7=__ A8=__ A9=__
T-B: health=__ openapi含mfr-open=__ 迁移是否执行=Y/N 报错=N/A
T-C: C1=__ C2=__ C3=__ C4(admin/wkr/cst/dl)=__
OCL编号=L-YYYYMMDD-NN  时间戳=__ 备注=__

五、禁令清单(越权=立即叫停)

  1. ❌ 不重启/不修改/不删除 :8000 旧后端任何东西
  2. ❌ 不改 admin/wkr/cst/dl 四端的 systemd/nginx/config
  3. ❌ Caddy 只允许两处改动:A-2 的端口替换、T-C 的新增块;其他行一概不碰
  4. ❌ 新实例只 bind 127.0.0.1,禁止 0.0.0.0 对外
  5. ❌ 不手动 UPDATE/DELETE 数据库数据;migration 只允许 alembic 工具执行

— V3 单签发完毕。三步全部断言通过后通知听写,由听写重跑 D01(4 项)+ D02(8 项)收官 —