40 KiB
00 · 标准故障排查手册
版本: v1.9 | 日期: 2026-07-16 | 维护人: 宋献 / 助理 定位: 所有故障排查前首先查看本手册。 最新: 新增 CASE-20260716-01(员工端 /h5/ 404 — nginx 反向代理配置错误)
| v1.6 | 2026-07-14 | 新增 CASE-20260714-01(前端 bind mount 未生效导致 403)+ 错误码速查更新 | | v1.7 | 2026-07-14 | 新增 CASE-20260714-02(员工端 /h5/ 404 — nginx 配置错误)| | v1.9 | 2026-07-16 | 新增 CASE-20260716-01(员工端 /h5/ 404 — nginx 反向代理配置错误)| | v1.8 | 2026-07-15 | 新增 CASE-20260715-01(员工端审批模板ID不正确 — 企微审批模板失效)| | v1.7 | 2026-07-14 | 新增 CASE-20260714-01(前端 bind mount 未生效导致 403)+ 错误码速查更新 | | v1.6 | 2026-07-14 | 新增 CASE-20260714-02(员工端 /h5/ 404 — nginx 配置错误)| | v1.5 | 2026-07-13 | 新增 CASE-20260713-01(nginx rewrite 不全导致 502)+ CASE-20260713-02(@require_role 装饰器参数缺失) | | v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
前置阅读: 运维手册(部署/回滚/备份/应急) · SOP-04 应急响应
0 文档说明与版本
0.1 为什么要有这本手册
原先"故障排查"主题散落在 9 份文档中(500 诊断、服务器端诊断、故障排查指南、4 份修复记录、通讯链路、deploy/02 手册、调试验证指南),内容重复且存在断链。任何故障都应先翻这一本,按决策树定位,再查案例库。
0.2 ⛔ 验证完成硬规则(最重要)
宣布"已修复 / 已完成"之前,必须提供真实可验证证据,不得仅凭 curl / 日志 / "我认为":
- 前端 / 登录类问题:真实浏览器登录或操作截图(用真实 Chromium / Playwright 打开页面、输入凭据、完成动作、进入目标页的截图)。
- API / 后端类问题:端到端调用证据(curl 真实返回 + 必要时代码层拦截响应体)。
- 禁止:只因
docker logs无报错就断言修复;只因"我认为应该好了"就宣布完成。
0.3 版本历史
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0 | 2026-07-07 | 整合 9 份散落文档 + 新增 CASE-20260707-01(Redis urlparse 挂起) |
| v1.1 | 2026-07-08 | 新增 CASE-20260708-01~06 + nginx 急救流程 + 端到端验证更新 |
| v1.2 | 2026-07-10 | 新增 CASE-20260710-01(CSP 拦截内联 JS)+ Step 0 响应头检查 + sed -i inode 教训 |
| v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
| v1.3 | 2026-07-10 | 新增 CASE-20260710-02(镜像缺文件导致 API 404)+ 判定矩阵扩展 + 部署前同步检查清单 |
1 快速诊断决策树
1.1 三步隔离法(通用)
任何"页面打不开 / 网络连接失败 / 接口无响应 / 422"都先用三步隔离,定位是 nginx、后端、还是依赖(DB / Redis)的问题。
Step 0:HTTP 响应头检查(页面 200 但功能异常时首先执行)
# 检查响应头,特别关注 Content-Security-Policy / X-Frame-Options / X-Content-Type-Options
curl -ksI https://itsupport.servyou.com.cn/h5/ | grep -iE 'content-security|x-frame|content-type'
为什么要有 Step 0:页面返回 HTTP 200、HTML 正常加载,但 JavaScript 不执行——这类问题无法被三步隔离法捕获。根因通常是 CSP(Content-Security-Policy)头限制了 script-src,导致内联 <script> 被浏览器静默拦截。详见 CASE-20260710-01。
# 第1步:nginx 层可达性(在服务器执行;浏览器走 HTTPS,故用 https 而非 localhost)
curl -ksI https://itsupport.servyou.com.cn/itadmin/ | head -5
curl -ksI https://itsupport.servyou.com.cn/api/health | head -5
# 第2步:直连后端(绕过 nginx,确认后端本身)
docker compose exec backend curl -s http://localhost:8000/health
# 或容器外:
docker exec wecom_it_backend curl localhost:8000/health
# 第3步:依赖可达性
docker compose exec redis redis-cli ping # 期望 PONG
docker compose exec postgres pg_isready -U wecom # 期望 accepting
判定矩阵:
| 现象 | 第1步 | 第2步 | 第3步 | 定位 |
|---|---|---|---|---|
| 浏览器"网络连接失败"、curl 永远不返回 | ✅200 | ✅200 | ❌挂起 | 依赖挂起(如 Redis 连到错误 host) |
| 全站 500 | ❌500 | ✅/❌ | — | 后端异常,看 backend 日志 |
| 某端点 502 | ❌502 | ❌后端 down | — | 后端未起 / 缺 PYTHONPATH=/app |
| /itdesk/ 200 但 /api/... 404 | ✅ | — | — | nginx 代理路径不匹配 |
| 422 | ✅ | API 校验失败 | — | 请求体缺字段(见 §2) |
| 页面 200 但 JS 不执行 | ✅200 | — | — | CSP 拦截内联 JS(检查 Content-Security-Policy 头的 script-src 是否含 'unsafe-inline',见 Step 0 / CASE-20260710-01) |
| API 404 但本地代码存在 | ✅200 | ❌404 | — | ./app/ 代码完整性(见 CASE-20260710-02 / §1.4) |
1.2 关键陷阱:URL 特殊字符导致依赖"静默挂起"
详见案例 CASE-20260707-01。密码含 @ # 时,urlparse 把它们当 URL 分隔符,连到不存在的 host,连接无限挂起(浏览器表现为"网络连接失败",curl 永远等不到返回)。这是最隐蔽的一类故障——容器全 Up、nginx 全 200、唯独业务接口卡死。
1.3 在服务器跑诊断的 3 种方式(经堡垒机)
公司服务器只能经堡垒机(sxn@10.212.189.210:2222 → ssh sxn@10.90.5.110)操作,无法本地 scp。推荐用 jumpserver-ops 工具自动执行:
# 本地(Windows)用 jumpserver-ops 跑(自动复用会话,~2-3s/条):
python jms_ops.py exec -c "docker compose ps" -c "curl -ksI https://itsupport.servyou.com.cn/api/health" --reuse
原"服务器端跑诊断"的 3 种手工方式(PuTTY 跳堡垒机 / scp 上传 / 服务器下载)已不推荐,统一用上述 jumpserver-ops 自动化。
1.4 部署前检查清单(卷挂载模式 — 方案 C)
触发条件:任何涉及后端代码变更的部署(新增/修改 .py 文件、新增 Python 依赖)。
⛔ 硬规则:后端代码部署方式(方案 C 卷挂载,2026-07-10 上线)
| 变更类型 | 正确命令 | ❌ 禁止操作 | 耗时 |
|---|---|---|---|
.py 文件变更 |
cd /opt/wecom-it-desk && docker compose restart backend |
❌ docker compose build |
~15-30 秒 |
requirements.txt 变更 |
docker compose build backend && docker compose up -d backend |
— | ~60-90 秒 |
.env / docker-compose.yml 变更 |
docker compose up -d backend |
— | ~10 秒 |
代码通过
./app:/app/appvolume 挂载到容器,不烘焙进镜像。改代码只需 restart 让 uvicorn 重新加载,无需重建镜像。docker compose build只在 Python 依赖变化时才需要。
背景:方案 C(卷挂载)已于 2026-07-10 上线。代码不再烘焙进 Docker 镜像,而是通过 ./app:/app/app volume 挂载到容器。backend/app/ 旧代码目录已删除。部署前只需验证 ./app/ 代码目录完整性和 volume 挂载状态。
⚠️ 历史背景(已消除):方案 C 前,服务器存在两份代码目录
app/和backend/app/,不同步导致镜像缺文件(见 CASE-20260710-02)。方案 C 消除了此根因。
检查清单(经堡垒机执行):
# 1. 验证 ./app/ 关键文件存在
for f in app/__init__.py app/main.py app/api/auth.py; do
[ -f "/opt/wecom-it-desk/$f" ] && echo "PASS: $f" || echo "FAIL: $f missing"
done
# 2. 验证 volume 挂载正常(容器内可访问代码)
docker exec wecom_it_backend ls /app/app/main.py
# 3. 验证代码一致性(宿主机与容器内 MD5 一致)
HOST=$(md5sum /opt/wecom-it-desk/app/main.py | awk '{print $1}')
CONTAINER=$(docker exec wecom_it_backend md5sum /app/app/main.py | awk '{print $1}')
[ "$HOST" = "$CONTAINER" ] && echo "PASS: code match" || echo "FAIL: code mismatch"
# 4. 如有新依赖,检查 requirements.txt
# diff 本地 requirements.txt 与服务器上的(如需要)
# 5. 代码更新后只需重启(无需重建镜像)
cd /opt/wecom-it-desk && docker compose restart backend
简化版(用 jumpserver-ops 一键检查):
python jms_ops.py exec \
-c "for f in app/__init__.py app/main.py app/api/auth.py; do [ -f /opt/wecom-it-desk/\$f ] && echo \"PASS: \$f\" || echo \"FAIL: \$f\"; done" \
-c "docker exec wecom_it_backend ls /app/app/main.py && echo PASS_mount" \
--reuse
⚠️ 代码更新后
docker compose restart即可(15-30 秒)。仅当requirements.txt有变化时才需要docker compose build。
前端部署检查清单(bind mount 模式)
触发条件:任何前端文件更新(frontend-*/dist/ 目录变更)。
# 1. 验证宿主机源目录有文件
ls -la /opt/wecom-it-desk/frontend-agent/dist/ # 坐席端
ls -la /opt/wecom-it-desk/frontend-h5/dist/ # H5 端
# 2. 验证容器内挂载成功(关键!)
docker exec wecom_it_nginx ls /usr/share/nginx/html/itagent/ # 坐席端
docker exec wecom_it_nginx ls /usr/share/nginx/html/h5/ # H5 端
# 3. 如容器内为空,强制重建 nginx 容器
cd /opt/wecom-it-desk
docker compose stop nginx
docker compose rm -f nginx
docker compose up -d nginx
# 4. 验证外部可访问
curl -sI https://itsupport.servyou.com.cn/itagent/ | head -3
⚠️ bind mount 有时不生效:即使 docker-compose.yml 配置正确,挂载也可能失效。前端部署后务必验证容器内文件存在(步骤2),否则会 403。
2 常见错误码速查(E5xx)
| 错误码 | 含义 | 首选排查 |
|---|---|---|
| E500 | 后端未捕获异常 / 缺列 / 缺依赖 / 装饰器参数不匹配 | docker compose logs backend --tail=200 | grep -i error;查数据库缺列 / 缺 Python 依赖;检查 @require_role 等装饰器是否与函数签名不匹配(见 CASE-20260713-02) |
| E502 | nginx 连不到后端 / 路由不匹配 | 后端容器 unhealthy?docker logs wecom_it_backend;检查 nginx rewrite 是否正确剥离 /api/ 前缀(见 CASE-20260713-01) |
| E503 | 服务过载 / 维护 | docker stats;docker inspect ... Health |
| E403 | IP 白名单 / 无权限 / 前端挂载未生效 | grep allow /opt/wecom-it-desk/nginx/nginx.conf;admin 角色不足;前端部署后检查 docker exec wecom_it_nginx ls <挂载路径> 确认容器内文件存在(见 CASE-20260714-01) |
| E422 | 请求体校验失败(Pydantic) | 确认必填字段齐全(如登录需 user_id+name) |
| 网络失败 / 连接挂起 | 依赖不可达(最常见 Redis 配置错) | 见 §1.2 / CASE-20260707-01 |
| 页面 200 但 JS 不执行 | CSP 头 script-src 缺 'unsafe-inline',内联 <script> 被浏览器静默拦截 |
curl -ksI <URL> | grep content-security;检查 script-src 是否含 'unsafe-inline'(见 CASE-20260710-01) |
| API 404 但本地代码存在 | ./app/ 是否有该文件 |
docker exec <容器> ls /app/app/api/auth.py;docker exec <容器> md5sum /app/app/main.py 对比宿主机(见 §1.4 / CASE-20260710-02) |
2.1 E500 常见根因速查
- 数据库缺列 →
ALTER TABLE ... ADD COLUMN IF NOT EXISTS ... - 缺 Python 依赖 →
requirements.txt补依赖后重构建(如wordfilter) - 代码签名不匹配(如缺
current_agent参数)→ 修函数签名 import aioredis与 Python 3.12 冲突 → 改redis.asyncio,设PYTHONPATH=/app
2.2 E422 登录场景
登录端点 /api/agents/login 要求 user_id(必填) + name(必填);缺字段直接 422。前端 admin.ts 用 name: inputUserId 发送。
3 诊断脚本与命令
3.1 一键系统状态
#!/bin/bash
echo "==== 容器状态 ===="; docker compose ps
echo "==== 端口 ===="; netstat -tlnp | grep -E "80|443|5432|6379|8000"
echo "==== 前端文件 ===="; ls -la /opt/wecom-it-desk/html/itdesk/ 2>/dev/null | head
echo "==== backend 错误 ===="; docker compose logs --tail=20 backend 2>&1 | grep -i error
echo "==== Redis ===="; docker compose exec redis redis-cli ping
echo "==== PG ===="; docker compose exec postgres pg_isready -U wecom
3.2 500 错误快速对照
| 现象 | 诊断 |
|---|---|
ls .../frontend-h5/dist/ No such file |
部署包未含 dist |
nginx 容器内 ls /usr/share/nginx/html/itdesk/ 失败 |
挂载路径错 |
| curl /itdesk/ 返回 500 | 后端代理或 SPA 内部错 |
| /itportal/ 200 但 /itdesk/ 500 | H5 端特定问题 |
nginx 日志有 proxy_pass 错 |
后端未起 / 端口不通 |
nginx 日志 rewrite ... cycle |
try_files 死循环,修 nginx 配置 |
3.3 通讯链路检查点(用户 ↔ 坐席 ↔ 企微)
- 用户→系统:企微回调
/wecom/callback→message_router→ 消息入库 → 坐席 WS / 轮询 - 系统→用户:坐席 POST
/conversations/{id}/messages→wecom_service.send_text_message()(errcode=0)→ 用户收到 - 已知风险:非文本消息(图片/文件)不推送;
dev_mode跳过企微推送;企微 API 失败静默(仅日志) - 检查点文件:
wecom_callback.py/message_router.py/messages.py/wecom_service.py/ws_manager.py
3.4 WebSocket 失败
grep -r 'websocket' /opt/wecom-it-desk/nginx/nginx.conf # 需 proxy_http_version 1.1 + Upgrade/Connection
docker logs wecom_it_backend | grep -i websocket
4 案例库(倒序,编号 CASE-YYYYMMDD-序号)
CASE-20260716-01 · 员工端 /h5/ 404 — nginx 反向代理配置错误 ⭐⭐
- 现象:企微工作台 → IT智能服务 →
https://itsupport.servyou.com.cn/h5/返回{"detail":"Not Found"}(FastAPI 404) - 根因:nginx 配置中
/h5/被错误配置为反向代理到后端 API(proxy_pass http://backend_api/),而不是静态文件服务。后端没有/h5/路由,返回 404 - 诊断:
docker logs wecom_it_nginx | grep /h5/→ 显示后端返回 404docker exec wecom_it_nginx cat /etc/nginx/nginx.conf | grep -A5 'location /h5/'→ 发现proxy_pass http://backend_api/- 确认 H5 静态文件已正确挂载:
docker exec wecom_it_nginx ls /usr/share/nginx/html/h5/
- 修复:
- docker-compose.yml:新增 H5 挂载到
/h5/- ./frontend-h5/dist:/usr/share/nginx/html/h5:ro - nginx/nginx.conf:将
/h5/从反向代理改为静态文件服务location /h5/ { alias /usr/share/nginx/html/h5/; index index.html; try_files $uri /h5/index.html; } - 部署:上传配置后重建 nginx 容器
cd /opt/wecom-it-desk docker compose stop nginx && docker compose rm -f nginx && docker compose up -d nginx
- docker-compose.yml:新增 H5 挂载到
- 验证:
- 容器内
curl -sI http://localhost/h5/→ HTTP 200 - 浏览器访问
https://itsupport.servyou.com.cn/h5/→ 正常显示 H5 页面
- 容器内
- ⚠️ 教训:
- H5 前端是静态文件应用,nginx 应配置
alias或root提供静态文件,而不是proxy_pass到后端 - 这是第二次出现同类问题:上次 CASE-20260714-02 修复后,今天再次出现,可能是上次修复未同步到服务器或配置被覆盖
- bind mount 有时不生效:即使 docker-compose.yml 配置正确,挂载也可能失效。前端部署后务必验证容器内文件存在
- 建议:在 nginx 配置中添加注释说明
/h5/是静态文件服务,避免未来误改
- H5 前端是静态文件应用,nginx 应配置
CASE-20260715-01 · 员工端审批"获取审批流程失败,审批模板ID不正确" ⭐⭐
- 现象:员工端H5点击"IT资产升级"、"设备申请"、"商业软件申请"等审批卡片时,提示"获取审批流程失败,审批模板ID不正确"。
- 根因(两层代码都需要修复):
- 前端:
RecommendCard.vue中APPROVAL_URL_MAP的asset_upgrade映射到企微审批模板Bs7ucTGs...(已失效),商业软件申请映射到企微审批模板3TmACf8D...(也已失效),第298-299行硬编码 fallback 跳转到失效的企微审批URL - 后端:
backend/app/api/approval.py中APPROVAL_TEMPLATES字典的asset_upgrade和software_service同样映射到失效的企微审批模板(第81行和第115行)
- 前端:
- 诊断:
- 浏览器F12查看网络请求,确认调用的是企微审批模板URL
- 检查前端
RecommendCard.vue和后端approval.py中的URL映射 - 对比
ApprovalCardModal.vue中的正确映射(已改用ITSM)
- 修复:
- 前端:
frontend-h5/src/components/assistant/RecommendCard.vue- 将
asset_upgrade改为ITSM工单系统URL - 将
商业软件申请改为ITSM工单系统URL - 修改
handleActionClick()的fallback逻辑,移除硬编码的失效URL - 构建:
npm run build→ 部署到/opt/wecom-it-desk/frontend-h5/dist/
- 将
- 后端:
backend/app/api/approval.py- 将
asset_upgrade的url改为ITSM工单系统URL,location改为"运维平台" - 将
software_service的url改为ITSM工单系统URL,location改为"运维平台" - 上传到服务器:
/opt/wecom-it-desk/app/api/approval.py - 重启后端:
docker compose restart backend
- 将
- 部署:
docker exec wecom_it_nginx nginx -s reload
- 前端:
- 验证:浏览器访问H5,点击审批卡片,跳转到ITSM工单系统而非企微审批(已失效模板)
- ⚠️ 教训:
- 审批入口有两层:左侧"企微-审批"入口数据来自后端API
/approval/links,AI推荐卡片来自前端代码,两者都需要修复 - 企微审批模板会失效:审批模板在企微后台可能被删除或变更,前端不应硬编码模板ID
- ITSM是更稳定的方案:ITSM工单系统URL更稳定,不受企微审批模板变更影响
- 推荐做法:所有审批类型统一使用ITSM工单系统,前后端都要同步修改
- 审批入口有两层:左侧"企微-审批"入口数据来自后端API
CASE-20260714-01 · 坐席端 403 错误 — 前端 bind mount 未生效 ⭐⭐
-
现象:坐席端
/itagent/返回 403 Forbidden,浏览器控制台显示Failed to load resource: the server responded with a status of 403。 -
根因:前端文件虽然上传到服务器
/opt/wecom-it-desk/frontend-agent/dist/,但 nginx 容器的 bind mount 没有正确工作,容器内/usr/share/nginx/html/itagent/目录为空。 -
诊断:
docker exec wecom_it_nginx ls -la /usr/share/nginx/html/itagent/→ 容器内目录为空docker inspect wecom_it_nginx | grep itagent→ 挂载配置存在且正确(Source: /opt/wecom-it-desk/frontend-agent/dist)ls -la /opt/wecom-it-desk/frontend-agent/dist/→ 宿主机源目录有文件- nginx 日志显示
directory index of "/usr/share/nginx/html/itagent/" is forbidden
-
修复:
# 重建 nginx 容器让 bind mount 生效 cd /opt/wecom-it-desk docker compose stop nginx docker compose rm -f nginx docker compose up -d nginx -
验证:
docker exec wecom_it_nginx ls -la /usr/share/nginx/html/itagent/→ 容器内文件存在;外部访问/itagent/→ HTTP 200 -
⚠️ 教训:
- bind mount 有时不生效:即使 docker-compose.yml 配置正确,挂载也可能失效。重建容器是常用解决方法。
- 容器内目录为空但宿主机有文件 ≠ 挂载成功:必须进入容器内验证,不能只看宿主机。
- 前端部署后务必验证容器内文件:用
docker exec <容器> ls <挂载路径>确认。 - 此问题可能复发:任何前端部署(dist 目录更新)后,建议重建 nginx 容器确保挂载生效。
-
复发记录:
- 2026-07-16:再次发生,返回 500(rewrite 循环
internal redirection cycle while internally redirecting to "/itage"),重建 nginx 容器修复
- 2026-07-16:再次发生,返回 500(rewrite 循环
CASE-20260714-02 · 员工端 /h5/ 404 — nginx 配置错误 ⭐⭐
- 现象:员工端打开报错
Failed to load resource: the server responded with a status of 404 (Not Found)。 - 根因:nginx 配置中
/h5/被错误配置为反向代理到后端(proxy_pass http://backend_api/),而不是静态文件服务。后端没有/h5/路由,导致返回 404。 - 诊断:
docker logs wecom_it_nginx | grep /h5/→ 显示GET /h5/ HTTP/1.1" 404docker exec wecom_it_nginx cat /etc/nginx/nginx.conf | grep -A10 'location /h5/'→ 发现proxy_pass http://backend_api/- 确认静态文件已正确挂载:
docker exec wecom_it_nginx ls /usr/share/nginx/html/h5/
- 修复:修改
nginx/nginx.conf,将/h5/从反向代理改为静态文件服务:部署:location /h5/ { alias /usr/share/nginx/html/h5/; index index.html; try_files $uri /h5/index.html; }docker compose restart nginx - 验证:浏览器访问
https://itsupport.servyou.com.cn/h5/→ HTTP 200,显示"仅限企业微信访问"页面 - ⚠️ 教训:
- H5 前端是静态文件应用,nginx 应配置
alias或root提供静态文件,而不是proxy_pass到后端。 - 本地 nginx.conf 与服务器不同步:发现问题后,检查本地配置是否已修复(本次修复已同步到本地
nginx/nginx.conf)。 - 常见混淆:
/itdesk/和/h5/都指向 H5 应用,但只有/itdesk/是在企微工作台中配置的入口。两者都应配置为静态文件服务。
- H5 前端是静态文件应用,nginx 应配置
CASE-20260713-02 · 坐席端 502 错误 — @require_role 装饰器参数缺失 ⭐⭐
- 现象:坐席端
/itagent/全部 API 返回 502,后端日志显示TypeError: list_agents() got an unexpected keyword argument 'current_user'。 - 根因:
@require_role装饰器在调用被装饰函数时强制传递current_user参数,但部分使用该装饰器的函数未声明此参数。 - 诊断:
docker logs wecom_it_backend | grep -i error查看具体错误;定位缺少参数的函数。 - 修复:
backend/app/api/agents.py第 425 行:添加current_user: UserInfo = Depends(get_current_user)backend/app/api/otp.py第 348 行和第 389 行:同样添加current_user参数- 部署:
docker compose restart backend
- 验证:后端
/health→ 200 OK;/agents→ 200 OK - ⚠️ 教训:
- 所有使用
@require_role装饰器的函数都必须声明current_user参数,装饰器会自动注入此参数。 - 排查 502 错误时,先检查后端日志中的
TypeError错误,可能是装饰器参数不匹配。 - 建议在代码中添加类型注解和 lint 规则,提前发现此类问题。
- 所有使用
CASE-20260713-01 · 坐席端 502 错误 — nginx rewrite 规则不完整 ⭐⭐
- 现象:部分 API(如
/api/auth/qrcode)返回 502,但/api/agents返回 200。 - 根因:nginx 配置只对
/api/admin/路径做 rewrite 剥离前缀,其他路径(如/api/auth/)未处理,导致后端收到/api/auth/qrcode而非/auth/qrcode,路由不匹配返回 404。 - 诊断:
docker logs wecom_it_nginx | grep 502查看哪些路径返回 502docker exec wecom_it_backend curl http://localhost:8000/auth/qrcode直接测试后端(绕过 nginx)- 检查 nginx 配置
location /api/是否正确 rewrite
- 修复:修改
nginx/nginx.conf,对所有/api/路径统一做 rewrite:部署:location /api/ { # 剥离 /api/ 前缀,使后端收到 /auth/... 而非 /api/auth/... rewrite ^/api/(.*)$ /$1 break; proxy_pass http://backend_api; ... }docker restart wecom_it_nginx - 验证:外部访问
/api/auth/qrcode→ 200 OK - ⚠️ 教训:
- nginx rewrite 规则必须覆盖所有需要剥离前缀的路径,不能只处理特定路径(如
/api/admin/)。 - 判定矩阵更新:
/xxx/ 200 但 /api/... 404→ 检查 nginx 是否正确 rewrite。 - 部署新功能后,务必用真实浏览器测试所有关键路径,不能只测"看起来正常"的端点。
- nginx rewrite 规则必须覆盖所有需要剥离前缀的路径,不能只处理特定路径(如
CASE-20260710-02 · 坐席端/管理端登录"获取二维码失败"(Docker 镜像缺文件)⭐⭐
- 现象:坐席端和管理端登录均报"获取二维码失败",浏览器控制台显示
WebSocket connection failed+/api/auth/qrcode返回 404。 - 误判历程:
- 误判为 Nginx 未 reload →
nginx -s reload后/auth_qrcode/create返回 200,但前端实际调用的是/auth/qrcode(不同路由) - 尝试
docker cp临时复制auth.py到容器 → 重启后文件丢失(临时文件系统) - 发现连锁依赖:
auth.py依赖schemas/auth.py→services/auth_service.py→models/login_log.py,全部缺失 - 真正根因:服务器存在两份代码——
/opt/wecom-it-desk/app/(较新,开发用)和/opt/wecom-it-desk/backend/app/(较旧,Docker 构建用)。新增的auth.py只放到了app/,未同步到backend/app/,导致镜像构建时打包的是旧代码。
- 误判为 Nginx 未 reload →
- 根因:服务器上
app/(开发目录)与backend/app/(Docker 构建目录)不同步,docker compose build打包了旧代码。同时requirements.txt也未同步(新增neo4j依赖),首次重建后容器因缺包 crash。 - 修复:
- 用 Python 脚本同步
/opt/wecom-it-desk/app/→/opt/wecom-it-desk/backend/app/ - 上传最新
requirements.txt(含neo4j>=5.26.0)到/opt/wecom-it-desk/backend/ docker compose build backend(约 65s)docker compose up -d backend- 验证:后端日志显示
GET /auth/qrcode → 200 OK,容器状态 healthy
- 用 Python 脚本同步
- ⚠️ 教训:
- 部署新功能时必须检查两份代码一致性:
app/和backend/app/不同步 = 镜像缺文件。 docker cp是临时方案:容器重启后丢失。永久修复必须重建镜像。- 全链路检查:代码同步 → requirements 更新 → 镜像重建 → 容器重启,四步缺一不可。
- 部署新功能时必须检查两份代码一致性:
- 防护措施:见 §1.4 部署前检查清单。
- ✅ 根因已消除(2026-07-10):方案 C(卷挂载)已上线。代码不再烘焙进 Docker 镜像,通过
./app:/app/appvolume 挂载到容器。backend/app/旧代码目录已删除,不再存在"两份代码不同步"问题。此案例保留作为历史参考。
CASE-20260710-01 · 扫码登录成功页 JS 不执行(CSP 拦截内联脚本)⭐⭐
- 现象:企微扫码登录成功后,"登录成功"页面显示但不会自动关闭。页面停在"JS加载中…",所有内联
<script>从未执行。后端返回 HTTP 200,HTML 内容正确。 - 误判历程(5 轮,供反思):
- 误判为未引入企微 JS-SDK → 添加后仍不工作
- 误判为 JSAPI 签名 URL 协议不一致(HTTP vs HTTPS)→ 修复
X-Forwarded-Proto后仍不工作 - 误判为外部 JS-SDK 加载失败 → 移除外部依赖改用
WeixinJSBridge仍不工作 - 误判为
WeixinJSBridgeReady事件未触发 → 改为轮询检测仍不工作 - 真正根因:Nginx CSP 头
script-src 'self' 'unsafe-eval' https://res.wx.qq.com缺少'unsafe-inline',所有内联<script>被浏览器静默拦截。JS 从未执行过——前面 4 轮的代码修改全部白费。
- 根因:
Content-Security-Policy的script-src指令未包含'unsafe-inline',浏览器根据 CSP 规范静默拦截所有内联脚本(<script>...</script>和onclick=等内联事件处理器)。页面 HTML 正常返回,但 JS 引擎从未解析执行任何脚本。 - 修复:Nginx 配置 CSP 头
script-src加入'unsafe-inline':修改后add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://res.wx.qq.com; ..." always;docker restart wecom_it_nginx(不能用reload,见下方教训)。 - 验证:用户手机企微扫码 → 显示"登录成功"→ 1 秒后自动关闭返回企微 ✅
- ⚠️ 教训:
- 页面 200 + HTML 正确 ≠ JS 会执行。排查前端功能异常时,Step 0 应检查 HTTP 响应头(CSP / X-Frame-Options),而非直接深入代码逻辑。
- CSP 拦截是静默的——浏览器控制台报错但页面无任何错误提示,后端日志也完全正常。如果不主动检查响应头,会一直在代码层面打转。
- 5 轮误判的共同盲点:每次都在"JS 代码为什么不对"上做文章,从未怀疑"JS 根本没执行"。正确的排查路径应该是先在浏览器 F12 控制台输入
1+1确认 JS 引擎可用,再看 Console 是否有 CSP 报错。
CASE-20260707-01 · 管理后台登录"网络连接失败"(Redis 密码 URL 解析挂起)⭐
- 现象:浏览器登录
/itadmin/一直转圈 / "网络连接失败";API 永远不返回;curl 超时。 - 根因:
REDIS_URL=redis://:R3d!s@2026#Secure@redis:6379/0,密码含@和#。urlparse()把#当 fragment、@当 host 分隔符 → 解析出 host=2026、password=R3d!s→ 连到不存在的 host → 无限挂起。后端token_service.create_token()调redis.setex时卡死。 - 修复:
docker-compose.yml后端REDIS_URL改为 URL-encoded:redis://:R3d%21s%402026%23Secure@redis:6379/0backend/app/config.py的create_redis_client增加unquote()解码 +socket_connect_timeout=5/socket_timeout=5- redis 服务
--requirepass与 healthcheck 保持明文R3d!s@2026#Secure(与后端解码后的明文一致) - 重建 backend + redis 容器
- 验证:Redis
PING→PONG;curl登录/api/agents/login返回HTTP 200, 0.64s, role:admin;真实浏览器登录截图进入 dashboard 成功(见 §5)。 - ⚠️ 同类复发防护:本项目 Redis 密码含特殊字符,改 docker-compose 密码时两处必须一致(后端
REDIS_URL用 encoded,redis--requirepass用明文);且config.py必须unquote。
CASE-20260705-01 · 502 Bad Gateway(后端启动失败 / aioredis + PYTHONPATH)
- 现象:坐席端登录失败
502,后端容器unhealthy。 - 根因:旧镜像
import aioredis与 Python 3.12 冲突(TypeError: duplicate base class TimeoutError);且未设PYTHONPATH=/app致ModuleNotFoundError: No module named 'app.core'。 - 修复:Dockerfile 改
import redis.asyncio as aioredis;docker-compose.yml设PYTHONPATH=/app;重建后端。
CASE-20260705-02 · 坐席端 4 个问题(消息列表 500 / 页面抖动 / 发送失败 / 文档缺失)
- #1 消息列表 500:
list_messages()缺current_agent: Agent = Depends(get_current_agent)参数。修messages.py+ 重启。 - #2 页面短暂不可用:容器重启波动,自愈。
- #3 发送失败
ModuleNotFoundError: wordfilter:requirements.txt缺wordfilter==0.2.7,容器内pip install临时修 + 同步 requirements。 - #4 文档补"Python 依赖管理"章节(服务器部署手册)。
CASE-20260613-01 · H5 消息 500(缺列 + AIHandler 签名)
- 现象:
POST /api/h5/.../messages500:column conversations.impact_scope does not exist+AIHandler.__init__() missing 'ai_service'。 - 根因:DB 缺 4 列(
impact_scope/is_blocking/emotion_state/dify_conversation_id);dependencies.py两处AIHandler()未传ai_service。 - 修复:
ALTER TABLE补列;dependencies.py改AIHandler(ai_service=AIService())。
附:企微工作台"加载失败 / 无限加载"(2026-07-04)
- 根因1:nginx 未正确挂载
nginx.conf→ API 404,重建 nginx 容器。 - 根因2:后端
h5.py存在NameError: _require_wework_ua→ 代码未同步最新,复制最新h5.py+ 重启。
CASE-20260708-01 · 后端路由 404 — OTP 统一路由未注册 ⭐
- 现象:
POST /api/auth/otp-bind返回 404;前端 OTP 绑定面板密钥和二维码不显示。 - 根因:部署
otp.py时漏部署router.py,api_router.include_router(otp_router)未执行。本地router.py包含服务器不存在的模块(knowledge_iteration/approval_queue/vision/ragflow_ingestion/automation),导入失败导致整个router.py加载失败。 - 修复:上传
router.py,注释掉服务器上不存在的模块导入;同时修复otp.py中@require_role("admin")装饰器与显式current_user参数的冲突(服务器旧版require_role会自动追加current_user)。 - 教训:修改路由时务必同步部署
router.py,否则新端点虽然代码存在但永远不会注册。
CASE-20260708-02 · 管理端 API 全部 404 — nginx 正则 location proxy_pass 缺 rewrite
- 现象:
/api/admin/roles、/api/admin/dashboard/overview等全部返回 404。 - 根因:nginx 配置中
location ~ ^/api/admin/(正则匹配)内proxy_pass http://backend_api;不带尾部斜杠,导致/api/前缀未剥离,后端收到/api/admin/roles而非/admin/roles。带尾部斜杠又会报错"proxy_pass" cannot have URI part in location given by regular expression。 - 修复:去掉嵌套正则 location,改为统一
location /api/→proxy_pass http://backend_api/;(尾部斜杠剥离 /api/ 前缀)。 - 教训:nginx 中正则 location 不能直接用
proxy_pass带 URI;需要时用rewrite剥离前缀。
CASE-20260708-03 · sxn 扫码登录无 admin 权限 — QR 扫描写死 roles=["agent"]
- 现象:管理后台扫码登录后,OTP 管理/角色管理返回 403/无权限提示。
sxn账密登录正常。 - 根因:
auth_qrcode.py扫码自动确认逻辑中,create_token写死了roles=["agent"],未调用get_user_roles()。 - 修复:扫码确认改为调用
RoleMappingService.get_user_roles()获取真实角色。 - 教训:所有登录路径(账密/扫码/OAuth)的角色获取必须统一走
get_user_roles()。
CASE-20260708-04 · 用户角色列表为空 — get_user_roles() 只查 user_roles 表
- 现象:管理后台角色管理页"用户角色分配"表格为空;OTP 管理 API 403。
- 根因:
get_user_roles()仅查询user_roles表,但旧数据(包括 sxn 的 admin)只存在于agents.role字段。user_roles表为空时返回["user"]。 - 修复:
get_user_roles()增加agents.role回退查询;新增GET /admin/roles/user-roles端点;前端Roles.vue加载用户角色分配数据。 - 教训:新旧数据迁移时需确保角色数据完整同步到
user_roles表。
CASE-20260708-05 · 企微工作台点"IT支持服务"进坐席登录页 — 员工端口缺失
- 现象:企微工作台 → IT支持服务 → 显示坐席扫码登录页,而非员工 H5 页面。
- 根因:(1) nginx
/itdesk/被错误配置为 301 重定向到/itagent/;(2) H5 构建文件未挂载到容器(docker-compose.yml缺少./html/h5挂载);(3) 改重定向到/h5/后仍失败,因为 H5vite base是/itdesk/,OAuth 回调依赖此路径。 - 修复:
/itdesk/直接 alias 到 H5 构建目录(/usr/share/nginx/html/h5/),不再 301 跳转;docker-compose.yml添加 h5 volume 挂载;根路径/改为 302 →/h5/。 - nginx 关键配置(alias + try_files SPA 模式):
注意:fallback 用
location /itdesk/ { alias /usr/share/nginx/html/h5/; index index.html; try_files $uri $uri/ /index.html; }/index.html而非/itdesk/index.html——alias 会自动映射。 - 教训:前端
vite base路径必须与 nginx 服务路径一致;OAuth 回调路径不能用 301 重定向。
CASE-20260708-06 · 管理端 OTP 用户列表返回错误结构
- 现象:OTP 管理页面提示加载失败,
/auth/otp-admin-users返回 403。 - 根因:(1)
admin_list_otp_users返回普通数组,前端期望{total, items}结构;(2)@require_role("admin")因 token 不含 admin 角色返回 403(见 CASE-03/04)。 - 修复:后端改为分页查询返回
{total, items};增加 keyword/bound/page/page_size 参数支持。
附:nginx 配错急救流程(2026-07-08 实战总结)
| 步骤 | 操作 |
|---|---|
| 1 | 备份:sudo cp /opt/wecom-it-desk/nginx/nginx.conf /tmp/nginx.bak |
| 2 | 修改宿主文件后必须 docker stop nginx && docker start nginx(reload 有时不生效) |
| 3 | 验证容器内配置已同步:docker exec wecom_it_nginx grep 关键字 /etc/nginx/nginx.conf |
| 4 | 确认语法:docker exec wecom_it_nginx nginx -t |
| 5 | 检查容器状态:docker ps --filter name=wecom_it_nginx |
| 6 | 用 agent-browser 打开 URL 做端到端验证 |
| 7 | 避免用 sed 修改 nginx 配置——$uri/$host 等变量会被 shell 解释;用 Python 脚本或直接上传文件 |
| 8 | ⚠️ sed -i 会创建新 inode:Docker bind mount 不跟踪新 inode,导致容器内看到的仍是旧文件。用 sed -i 修改后必须 docker restart(而非 nginx -s reload),或改用 sed 不带 -i + 重定向写入原文件(保持 inode 不变) |
5 端到端验证完成标准(原《调试验证指南》整合)
宣布完成前,按 §0.2 提供真实证据。
5.1 管理后台验证(最常见)
| 步骤 | 操作 | 预期 |
|---|---|---|
| 1 | 浏览器开 https://itsupport.servyou.com.cn/itadmin/ |
登录页 |
| 2 | 输入 sxn / test123 登录 |
进入 dashboard,右上角显示"宋" |
| 3 | 仪表盘数据渲染 | 在线坐席 / 今日会话 / 平均响应 / AI 命中率 有值 |
| 4 | API 拦截 POST /api/agents/login |
HTTP 200 + role:admin + token |
5.2 通用验证清单(P0 必须通过)
- H5 登录流程正常(企微 OAuth 跳转 → 回跳 → 欢迎)
- 坐席登录正常,可接单
- 消息收发双向正常(文本 / 图片 / 文件)
- 邀请功能闭环
- 管理后台可访问且数据正常
5.3 真实浏览器证据获取(推荐 Playwright)
本机 Windows 可直接访问服务器(TCP 443 通)。用 playwright-core 驱动已安装的 Chromium(路径 ~/.agent-browser/browsers/chrome-*/chrome.exe),拦截 API 响应作为证据,避免 CLI 工具 IPC 不稳。
6 升级与应急(交叉引用,不重复)
- 回滚方案 → 见 运维手册·第六章
- 备份恢复 → 见 运维手册·第七章
- 应急响应(P0/P1 分级、止血、通知) → 见 SOP-04 应急响应
- 本手册只负责"定位 + 修复",变更管理与事故流程以上述文档为准。
7 参考文档索引
| 文档 | 说明 |
|---|---|
01-项目总览/01-智能IT服务系统运维手册-20260704.md |
部署 / 回滚 / 备份 / 应急(故障排查章已并入本手册) |
10-项目管理/SOPs-标准流程/SOP-04-应急响应.md |
应急响应 SOP |
09-部署运维/deploy/01-部署指南.md |
部署操作 |
09-部署运维/deploy/03-版本记录.md |
版本与修复记录索引 |
09-部署运维/deploy/服务器部署手册.md |
服务器部署细节 |
06-测试质量/testing-测试/E2E-CHECKLIST-v0.7.0.md |
E2E 验收清单 |
维护说明: 本手册为故障排查唯一入口。新增案例请按
CASE-YYYYMMDD-序号倒序追加到 §4;改动需同步本文件版本号与日期。