Files
wecom_it_smart_desk/docs/09-部署运维/00-标准故障排查手册.md
T
Simon bea288e414 feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复
== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
2026-07-11 23:13:10 +08:00

382 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 00 · 标准故障排查手册
> **版本**: v1.4 | **日期**: 2026-07-10 | **维护人**: 宋献 / 助理
> **定位**: 所有故障排查前**首先查看本手册**。
> **最新**: 方案 C(卷挂载)已上线,§1.4 更新为 volume 挂载验证 + CASE-20260710-02 标注根因已消除 + 错误码速查更新
| v1.4 | 2026-07-10 | 方案 C 上线:§1.4 改为 volume 挂载验证 + 错误码速查更新 + CASE-20260710-02 根因已消除标注 |
> **前置阅读**: [运维手册(部署/回滚/备份/应急)](../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-01Redis urlparse 挂起)|
| v1.1 | 2026-07-08 | 新增 CASE-20260708-01~06 + nginx 急救流程 + 端到端验证更新 |
| v1.2 | 2026-07-10 | 新增 CASE-20260710-01CSP 拦截内联 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 0HTTP 响应头检查(页面 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 不执行——这类问题无法被三步隔离法捕获。根因通常是 CSPContent-Security-Policy)头限制了 `script-src`,导致内联 `<script>` 被浏览器静默拦截。详见 CASE-20260710-01。
```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)|
| 页面 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|
### 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 自动化。
### 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`。
---
## 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 |
| **页面 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|
### 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-序号)
### 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 报错。
### 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` 用 encodedredis `--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
- 根因1nginx 未正确挂载 `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/` 后仍失败,因为 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 脚本或直接上传文件 |
| 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 升级与应急(交叉引用,不重复)
- **回滚方案** → 见 [运维手册·第六章](../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;改动需同步本文件版本号与日期。