Files
wecom_it_smart_desk/docs/09-部署运维/00-标准故障排查手册.md
Simon 400ce3ddcb feat: OTP首次绑定 + 三端登录修复 + 管理端权限修复 (2026-07-08)
OTP首次绑定:
- 新增统一 OTP 路由 /auth/otp-* (otp.py + router.py)
- 坐席端 OTP 绑定面板 (OtpBindPanel.vue)
- 管理端 OTP 管理列表 (MfaManage.vue)
- agent_login 签发半认证 token 支持首次绑定流程

三端登录修复:
- 坐席/管理端去掉'返回扫码登录'按钮
- 管理端改为二维码始终可见+轮询扫码状态
- 员工端 /itdesk/ 改为 alias 直接服务 H5 (不再301重定向)
- docker-compose 添加 h5 volume 挂载

管理端权限修复:
- 扫码登录改用 get_user_roles() 替代写死 roles=['agent']
- get_user_roles() 增加 agents.role 回退
- 新增 GET /admin/roles/user-roles 端点
- 角色管理页加载用户角色分配数据

文档更新:
- OTP PRD + 系统设计文档
- 故障排查手册 v1.1 (新增6案例)
- nginx 生产基准配置
2026-07-08 21:54:57 +08:00

272 lines
18 KiB
Markdown
Raw Permalink 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.1 | **日期**: 2026-07-08 | **维护人**: 宋献 / 助理
> **定位**: 所有故障排查前**首先查看本手册**。
> **最新**: 新增 CASE-20260708-01~06OTP 路由404 / nginx 404 / 扫码角色 / 用户角色 / 员工端路由 / OTP列表结构)+ nginx 配错急救流程
| v1.1 | 2026-07-08 | 新增 6 天 7.8 案例 + nginx 急救流程 + 端到端验证更新 |
> **前置阅读**: [运维手册(部署/回滚/备份/应急)](../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 挂起)|
---
## 1 快速诊断决策树
### 1.1 三步隔离法(通用)
任何"页面打不开 / 网络连接失败 / 接口无响应 / 422"都先用三步隔离,定位是 nginx、后端、还是依赖(DB / Redis)的问题:
```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)|
### 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 自动化。
---
## 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 |
### 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-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 脚本或直接上传文件 |
---
## 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;改动需同步本文件版本号与日期。