# 部署手册:扫码登录 + OTP 二次认证(Phase 1+2) > 创建:2026-06-21 > 适用版本:v0.7.0+ (Phase 1+2) > 部署顺序:后端 → 前端 4 端 → nginx → 数据库 migration → 验收 --- ## 🎯 部署目标 从 v0.6.x 的"企微 OAuth + SMS 2FA"升级到 v0.7.0 的"扫码登录 + OTP TOTP + SMS 备用"。 涉及后端变更: - 新增 `/api/auth_qrcode/*` 4 个端点(扫码登录) - 新增 `/api/mfa/*` 6 个端点(OTP 二次认证) - 新增 `/api/admin/mfa/reset/{employee_id}`(管理员重置) - 新增 `/api/admin/high-risk/*` 演示端点 + require_high_risk_otp 守卫 - 新增 2 个数据库字段: `users.mfa_secret`, `users.mfa_enabled`, `users.mfa_bound_at`, `users.mfa_last_verified_at` 涉及前端变更: - frontend-agent:Login.vue 重写(扫码 UI)+ 新增 MfaBind.vue + useHighRiskOtp - frontend-portal:新增 QrcodeLogin.vue + 默认路由 - frontend-admin:新增 MfaManage.vue(管理员 MFA 重置 UI) - frontend-h5:**不变**(仍走企微 OAuth) 涉及 nginx 变更: - `/itportal/` 新增 location(扫码入口) - 其余 4 个 location 已有,配置按 docs/NGINX-DOMAIN-ROUTING.md --- ## 📋 部署前检查 ### 1. 后端镜像依赖 `backend/requirements.txt` 必须包含: ``` pyotp==2.9.0 # TOTP 生成 qrcode[pil]==7.4.2 # 二维码生成 redis==5.0.7 # 已存在 ``` ### 2. 数据库迁移文件 确认以下 migration 已存在: - `backend/alembic/versions/023_mfa_fields.py`(加 4 个 MFA 字段) - `backend/alembic/versions/024_*.py`(可选:其他变更) ### 3. 配置文件 `backend/.env` 确认: ```bash # 新增(扫码登录) WECOM_OAUTH_REDIRECT_URI=https://itsupport.servyou.com.cn/itportal/qrcode-callback WECOM_CORP_ID=ww1234567890abcdef WECOM_AGENT_ID=1000002 # 已有(OTP) SMS_2FA_ENABLED=true # 蜂鸟 SMS 备用通道 ``` ### 4. 域名 / DNS - `itsupport.servyou.com.cn`(主域名,已有) - 子路径:`/itportal/` `/itagent/` `/itadmin/` `/itdesk/`(同一域名,nginx 分发) - 证书:`itsupport.servyou.com.cn.crt`(公司统一管理) --- ## 🚀 部署步骤 ### 步骤 1:部署后端(注意 RO bind mount) ```bash # 1. 上传新 backend 包到堡垒机 scp backend-v070-p1.tar.gz user@bastion:/tmp/ # 2. 通过堡垒机 PuTTY(不用 ssh -J)登录生产服务器 # 参考:feedback-putty-not-openssh.md # 3. 解压并复制到 backend 目录(走宿主机路径,避开 RO bind mount 陷阱) cd /opt/wecom-it-desk/ tar -xzf /tmp/backend-v070-p1.tar.gz # 注意:用 cp -r 不是 docker cp(避开 RO bind mount 假成功陷阱) sudo cp -r backend-v070-p1/* backend/ # 4. 数据库 migration cd /opt/wecom-it-desk/backend sudo docker exec wecom_it_backend alembic upgrade head # 验证: sudo docker exec wecom_it_backend alembic current # 期望:023_mfa_fields (head) # 5. 重启 backend(注意:backend 不在 compose 里,直接 docker restart) sudo docker restart wecom_it_backend # 验证:等待 ~30s,看健康检查 curl http://localhost:8000/health # 期望:{"status":"ok",...} ``` ### 步骤 2:部署前端 4 端 ```bash # 1. 各前端 build(本地) cd frontend-agent && npm run build cd frontend-portal && npm run build cd frontend-admin && npm run build # frontend-h5 不变,不用 build # 2. 上传 dist 到生产服务器 scp -r frontend-agent/dist user@bastion:/tmp/agent-dist/ scp -r frontend-portal/dist user@bastion:/tmp/portal-dist/ scp -r frontend-admin/dist user@bastion:/tmp/admin-dist/ # 3. 通过堡垒机,复制到 nginx 容器挂载的目录 # 路径可能是 /opt/wecom-it-desk/frontend-*/ sudo cp -r /tmp/agent-dist/* /opt/wecom-it-desk/frontend-agent/dist/ sudo cp -r /tmp/portal-dist/* /opt/wecom-it-desk/frontend-portal/dist/ sudo cp -r /tmp/admin-dist/* /opt/wecom-it-desk/frontend-admin/dist/ # 4. 验证:curl HTML 文件 curl -I https://itsupport.servyou.com.cn/itportal/ # 期望:200 OK,content-type: text/html ``` ### 步骤 3:更新 nginx 配置 ```bash # 1. 上传新 nginx 配置 # 新增 /itportal/ location,更新其他 location # 参考:docs/NGINX-DOMAIN-ROUTING.md # 2. 验证配置(在 nginx 容器里) sudo docker exec wecom_it_nginx nginx -t # 注意容器名是 wecom_it_nginx 不是 wecom-nginx # 期望:nginx: configuration file /etc/nginx/nginx.conf test is successful # 3. reload(不重启容器) sudo docker exec wecom_it_nginx nginx -s reload ``` ### 步骤 4:验收测试 按 docs/NGINX-DOMAIN-ROUTING.md 末"验证清单"逐条测试。 --- ## 🔄 回滚方案 ### 后端回滚 ```bash # 1. 用上次 patch1 备份 sudo cp -r /opt/wecom-it-desk/backend-v070-patch1/* /opt/wecom-it-desk/backend/ sudo docker restart wecom_it_backend # 2. 数据库回滚(谨慎!) sudo docker exec wecom_it_backend alembic downgrade -1 # 注意:只能降 1 个版本,如果已经升到 023,降到 022 ``` ### 前端回滚 ```bash # 直接覆盖 dist sudo cp -r /opt/wecom-it-desk/frontend-*-bak/* /opt/wecom-it-desk/frontend-*/dist/ ``` ### nginx 回滚 ```bash # 容器内 sed -i 改回旧配置(避开 RO bind mount 假成功陷阱) sudo docker exec wecom_it_nginx cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf sudo docker exec wecom_it_nginx nginx -s reload ``` --- ## ⚠️ 已知风险 | 风险 | 影响 | 缓解 | |---|---|---| | OTP 二维码渲染失败(后端 base64 生成出错) | 用户绑不上 OTP | 前端降级显示 qrcode_url 让用户手动复制 | | pyotp 库版本升级导致不兼容 | OTP 验证失败 | 锁版本 pyotp==2.9.0,生产前跑 pytest | | Admin MFA 重置端点被未授权访问 | 安全 | require_admin + 后续可加 IP 白名单 | | 蜂鸟 SMS API 未上线 | 备用通道不可用 | 不影响 OTP 主通道,先上线 OTP,后接 SMS | | nginx IP 白名单临时全开 | 安全 | v1.0 前必须收窄(task #48) | --- ## 📊 部署后验证 ### 业务指标 - [ ] 扫码登录成功率 > 95% - [ ] OTP 验证成功率 > 99% - [ ] 高危操作 OTP 触发率 100% - [ ] 蜂鸟 SMS fallback 触发 < 5%(绝大多数人用 OTP) ### 技术指标 - [ ] 扫码登录端到端 < 5s(从扫码到进入工作台) - [ ] OTP 验证 < 500ms - [ ] 高危操作 OTP 弹窗响应 < 200ms --- ## 📚 相关文档 - [USER-GUIDE-QRCODE-MFA.md](./USER-GUIDE-QRCODE-MFA.md) — 用户手册 - [NGINX-DOMAIN-ROUTING.md](./NGINX-DOMAIN-ROUTING.md) — nginx 域名分发 - [v070-alpha-deploy-runbook.md](../memory/v070-alpha-deploy-runbook.md) — v0.7.0-alpha 总览 - [docker-cp-readonly-bind-mount-fake-success.md](../memory/docker-cp-readonly-bind-mount-fake-success.md) — RO bind mount 陷阱 - [nginx-container-name-wecom-it-nginx.md](../memory/nginx-container-name-wecom-it-nginx.md) — 容器名坑 - [feedback-putty-not-openssh.md](../memory/feedback-putty-not-openssh.md) — 堡垒机 PuTTY --- **变更历史**: - 2026-06-21 创建(Phase 1+2 部署手册)