2026-07-07 21:52:11 +08:00
# 00 · 标准故障排查手册
2026-07-11 23:13:10 +08:00
> **版本**: v1.4 | **日期**: 2026-07-10 | **维护人**: 宋献 / 助理
2026-07-08 21:54:57 +08:00
> **定位**: 所有故障排查前**首先查看本手册**。
2026-07-11 23:13:10 +08:00
> **最新**: 方案 C(卷挂载)已上线,§1.4 更新为 volume 挂载验证 + CASE-20260710-02 标注根因已消除 + 错误码速查更新
2026-07-08 21:54:57 +08:00
2026-07-11 23:13:10 +08:00
| v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
2026-07-07 21:52:11 +08:00
> **前置阅读**: [运维手册(部署/回滚/备份/应急)](../01-项目总览/01-智能IT服务系统运维手册-20260704.md) · [SOP-04 应急响应](../10-项目管理/SOPs-标准流程/SOP-04-应急响应.md)
---
## 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 挂起)|
2026-07-11 23:13:10 +08:00
| 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)+ 判定矩阵扩展 + 部署前同步检查清单 |
2026-07-07 21:52:11 +08:00
---
## 1 快速诊断决策树
### 1.1 三步隔离法(通用)
2026-07-11 23:13:10 +08:00
任何"页面打不开 / 网络连接失败 / 接口无响应 / 422"都先用三步隔离,定位是 nginx、后端、还是依赖(DB / Redis)的问题。
#### Step 0: HTTP 响应头检查(页面 200 但功能异常时首先执行)
``` bash
# 检查响应头,特别关注 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。
2026-07-07 21:52:11 +08:00
``` bash
# 第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)|
2026-07-11 23:13:10 +08:00
| 页面 200 但 JS 不执行 | ✅200 | — | — | **CSP 拦截内联 JS ** (检查 `Content-Security-Policy` 头的 `script-src` 是否含 `'unsafe-inline'` ,见 Step 0 / CASE-20260710-01) |
| API 404 但本地代码存在 | ✅200 | ❌404 | — | ~~镜像缺文件~~ (方案 C 前根因)。卷挂载模式下检查 volume 挂载 + `./app/` 代码完整性(见 CASE-20260710-02 / §1.4) |
2026-07-07 21:52:11 +08:00
### 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 工具自动执行:
``` powershell
# 本地(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 自动化。
2026-07-11 23:13:10 +08:00
### 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/app` volume 挂载到容器,不烘焙进镜像。改代码只需 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 消除了此根因。
**检查清单(经堡垒机执行) ** :
``` bash
# 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 一键检查) ** :
``` bash
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`。
2026-07-07 21:52:11 +08:00
---
## 2 常见错误码速查(E5xx)
| 错误码 | 含义 | 首选排查 |
|--------|------|---------|
| **E500 ** | 后端未捕获异常 / 缺列 / 缺依赖 | `docker compose logs backend --tail=200 \| grep -i error` ;查数据库缺列 / 缺 Python 依赖 |
| **E502 ** | nginx 连不到后端 | 后端容器 `unhealthy` ? `docker logs wecom_it_backend` ;是否缺 `PYTHONPATH=/app` |
| **E503 ** | 服务过载 / 维护 | `docker stats` ; `docker inspect ... Health` |
| **E403 ** | IP 白名单 / 无权限 | `grep allow /opt/wecom-it-desk/nginx/nginx.conf` ; admin 角色不足 |
| **E422 ** | 请求体校验失败(Pydantic)| 确认必填字段齐全(如登录需 `user_id` +`name` ) |
| **网络失败 / 连接挂起 ** | 依赖不可达(最常见 Redis 配置错)| 见 §1.2 / CASE-20260707-01 |
2026-07-11 23:13:10 +08:00
| **页面 200 但 JS 不执行 ** | CSP 头 `script-src` 缺 `'unsafe-inline'` ,内联 `<script>` 被浏览器静默拦截 | `curl -ksI <URL> \| grep content-security` ;检查 `script-src` 是否含 `'unsafe-inline'` (见 CASE-20260710-01) |
| **API 404 但本地代码存在 ** | ~~镜像缺文件~~ (方案 C 前根因已消除)。卷挂载模式下检查:volume 是否正常挂载 + `./app/` 是否有该文件 | `docker exec <容器> ls /app/app/api/auth.py` ; `docker exec <容器> md5sum /app/app/main.py` 对比宿主机(见 §1.4 / CASE-20260710-02) |
2026-07-07 21:52:11 +08:00
### 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 一键系统状态
``` bash
#!/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 失败
``` bash
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-序号)
2026-07-11 23:13:10 +08:00
### CASE-20260710-02 · 坐席端/管理端登录"获取二维码失败"(Docker 镜像缺文件)⭐⭐
- **现象**:坐席端和管理端登录均报"获取二维码失败",浏览器控制台显示 `WebSocket connection failed` + `/api/auth/qrcode` 返回 404。
- **误判历程**:
1. 误判为 Nginx 未 reload → `nginx -s reload` 后 `/auth_qrcode/create` 返回 200,但前端实际调用的是 `/auth/qrcode` (不同路由)
2. 尝试 `docker cp` 临时复制 `auth.py` 到容器 → 重启后文件丢失(临时文件系统)
3. 发现连锁依赖:`auth.py` 依赖 `schemas/auth.py` → `services/auth_service.py` → `models/login_log.py` ,全部缺失
4. **真正根因 ** :服务器存在两份代码——`/opt/wecom-it-desk/app/` (较新,开发用)和 `/opt/wecom-it-desk/backend/app/` (较旧,Docker 构建用)。新增的 `auth.py` 只放到了 `app/` ,未同步到 `backend/app/` ,导致镜像构建时打包的是旧代码。
- **根因**:服务器上 `app/` (开发目录)与 `backend/app/` ( Docker 构建目录)不同步,`docker compose build` 打包了旧代码。同时 `requirements.txt` 也未同步(新增 `neo4j` 依赖),首次重建后容器因缺包 crash。
- **修复**:
1. 用 Python 脚本同步 `/opt/wecom-it-desk/app/` → `/opt/wecom-it-desk/backend/app/`
2. 上传最新 `requirements.txt` (含 `neo4j>=5.26.0` )到 `/opt/wecom-it-desk/backend/`
3. `docker compose build backend` (约 65s)
4. `docker compose up -d backend`
5. 验证:后端日志显示 `GET /auth/qrcode → 200 OK` ,容器状态 healthy
- **⚠️ 教训**:
1. **部署新功能时必须检查两份代码一致性 ** : `app/` 和 `backend/app/` 不同步 = 镜像缺文件。
2. * * `docker cp` 是临时方案**:容器重启后丢失。永久修复必须重建镜像。
3. **全链路检查 ** :代码同步 → requirements 更新 → 镜像重建 → 容器重启,四步缺一不可。
- **防护措施**:见 §1.4 部署前检查清单。
- **✅ 根因已消除**( 2026-07-10):方案 C(卷挂载)已上线。代码不再烘焙进 Docker 镜像,通过 `./app:/app/app` volume 挂载到容器。`backend/app/` 旧代码目录已删除,不再存在"两份代码不同步"问题。此案例保留作为历史参考。
### CASE-20260710-01 · 扫码登录成功页 JS 不执行(CSP 拦截内联脚本)⭐⭐
- **现象**:企微扫码登录成功后,"登录成功"页面显示但不会自动关闭。页面停在"JS加载中…",所有内联 `<script>` 从未执行。后端返回 HTTP 200,HTML 内容正确。
- **误判历程**(5 轮,供反思):
1. 误判为未引入企微 JS-SDK → 添加后仍不工作
2. 误判为 JSAPI 签名 URL 协议不一致(HTTP vs HTTPS)→ 修复 `X-Forwarded-Proto` 后仍不工作
3. 误判为外部 JS-SDK 加载失败 → 移除外部依赖改用 `WeixinJSBridge` 仍不工作
4. 误判为 `WeixinJSBridgeReady` 事件未触发 → 改为轮询检测仍不工作
5. **真正根因 ** : 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 秒后自动关闭返回企微 ✅
- **⚠️ 教训**:
1. **页面 200 + HTML 正确 ≠ JS 会执行**。排查前端功能异常时,Step 0 应检查 HTTP 响应头(CSP / X-Frame-Options),而非直接深入代码逻辑。
2. **CSP 拦截是静默的**——浏览器控制台报错但页面无任何错误提示,后端日志也完全正常。如果不主动检查响应头,会一直在代码层面打转。
3. **5 轮误判的共同盲点**:每次都在"JS 代码为什么不对"上做文章,从未怀疑"JS 根本没执行"。正确的排查路径应该是先在浏览器 F12 控制台输入 ` 1+1` 确认 JS 引擎可用,再看 Console 是否有 CSP 报错。
2026-07-07 21:52:11 +08:00
### 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` 时卡死。
- **修复**:
1. ` docker-compose.yml` 后端 ` REDIS_URL` 改为 URL-encoded: ` redis://:R3d%21s%402026%23Secure@redis:6379/0 `
2. ` backend/app/config.py` 的 ` create_redis_client` 增加 ` unquote()` 解码 + ` socket_connect_timeout=5` / ` socket_timeout=5`
3. redis 服务 ` --requirepass` 与 healthcheck **保持明文** ` R3d!s@2026 #Secure `(与后端解码后的明文一致)
4. 重建 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/.../messages` 500: ` 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` + 重启。
---
2026-07-08 21:54:57 +08:00
### 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/` 后仍失败,因为 H5 ` vite base` 是 ` /itdesk/`, OAuth 回调依赖此路径。
- **修复**: ` /itdesk/` 直接 alias 到 H5 构建目录(` /usr/share/nginx/html/h5/`),不再 301 跳转;` docker-compose.yml` 添加 h5 volume 挂载;根路径 ` /` 改为 302 → ` /h5/`。
- **nginx 关键配置**( alias + try_files SPA 模式):
` ``
location /itdesk/ {
alias /usr/share/nginx/html/h5/;
index index.html;
try_files $uri $uri/ /index.html;
}
` ``
注意:fallback 用 ` /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 脚本或直接上传文件 |
2026-07-11 23:13:10 +08:00
| 8 | ⚠️ **` sed -i` 会创建新 inode**: Docker bind mount 不跟踪新 inode,导致容器内看到的仍是旧文件。用 ` sed -i` 修改后必须 ` docker restart`(而非 ` nginx -s reload`),或改用 ` sed` 不带 ` -i` + 重定向写入原文件(保持 inode 不变) |
2026-07-08 21:54:57 +08:00
---
2026-07-07 21:52:11 +08:00
## 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 升级与应急(交叉引用,不重复)
- **回滚方案** → 见 [运维手册·第六章](../01-项目总览/01-智能IT服务系统运维手册-20260704.md#六回滚方案)
- **备份恢复** → 见 [运维手册·第七章](../01-项目总览/01-智能IT服务系统运维手册-20260704.md#七备份恢复)
- **应急响应(P0/P1 分级、止血、通知)** → 见 [SOP-04 应急响应](../10-项目管理/SOPs-标准流程/SOP-04-应急响应.md)
- 本手册只负责"定位 + 修复",变更管理与事故流程以上述文档为准。
---
## 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;改动需同步本文件版本号与日期。