Files
wecom_it_smart_desk/docs/03-测试文档/05-缺陷单/BUG-通用-快速回复规则-路由目标筛选500-002.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

238 lines
9.8 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.
# 缺陷单:快速回复规则「路由目标」筛选分类下拉菜单 500 错误
> **缺陷编号**: BUG-通用-002
> **版本**: v1.0
> **状态**: [已关闭]
> **优先级**: P1-High
> **发现日期**: 2026-07-28
> **发现人**: 宋献
> **指派人**: 宋献
> **修复人**: Duckula (AI助手)
> **关闭日期**: 2026-07-28
> **处理方式**: 自动处理和验证
> **关联需求**: REQ-通用-002(快速回复规则后台管理)
> **关联文档**:
> - PRD: `docs/01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md`
> - 技术方案: `docs/02-技术文档/技术方案-REQ-通用-002-快速回复规则后台管理.md`
> - 任务说明书: `docs/07-项目管理/任务说明书/任务说明书-131-快速回复规则后台管理.md`
> - 故障手册: `docs/04-运维文档/部署运维/00-标准故障排查手册.md`CASE-20260728-05
---
## 1. 基本信息
| 字段 | 内容 |
|------|------|
| 缺陷标题 | 管理后台 → 快速回复规则 → "路由目标" tab → 点击"业务分类"下拉菜单 → 提示"服务器内部错误,请稍后重试或联系管理员" |
| 影响范围 | 管理员/运营人员使用「路由目标」筛选的全部场景(共 6 条 routing_target 规则无法按分类筛选) |
| 涉及模块 | 管理后台(frontend-admin+ 后端 APIbackend FastAPI |
| 涉及文件 | `src/backend/app/api/admin/quick_rules.py`QuickRuleResponse 模型)<br>`src/backend/scripts/init_quick_rules.sql`routing_target 初始数据) |
| 触发条件 | 1. 登录管理后台 → `/itadmin/quick-rules`<br>2. 切换到"路由目标" Tab<br>3. 点击"业务分类"下拉菜单(前端会带 `?rule_type=routing_target&category=XXX``/api/admin/quick-rules` |
| 预期行为 | 下拉菜单正常加载所有 6 条 routing_target 规则,下拉可选项覆盖已配置的 6 个分类 |
| 实际行为 | 下拉菜单触发 API 调用,后端返回 1005 通用错误,UI 弹窗"服务器内部错误,请稍后重试或联系管理员";其他 Tabgreeting、routing_prefilter)正常 |
---
## 2. 复现步骤
1. 用管理员账号登录管理后台 `https://itsupport.servyou.com.cn/itadmin/`
2. 进入「快速回复规则」页面(路由 `/itadmin/quick-rules`
3. 切换到 **"路由目标"** Tab(默认显示 6 条记录)
4. 点击任意筛选条件中的 **"业务分类"** 下拉菜单
5. 观察前端弹窗 → 显示「服务器内部错误,请稍后重试或联系管理员」
6. 检查浏览器 F12 → Network → `/api/admin/quick-rules?rule_type=routing_target&category=XXX` → HTTP 200 但 `code:1005`
### 影响截图
- 页面:快速回复规则 → 路由目标 Tab
- 区块:筛选条件行
- 元素:业务分类下拉菜单
---
## 3. 根因分析
### 三层根因(缺一不可)
#### 3.1 数据层(DB):routing_target 记录 priority 字段为 NULL
`scripts/init_quick_rules.sql` 第 80 行的 `routing_target` INSERT 语句 **未指定 `priority` 列**
```sql
-- 错误(routing_target 段):没有 priority 字段
INSERT INTO quick_rules (rule_type, category, keyword, extra_data, is_active) VALUES
('routing_target', '行政', '机票酒店前台', '{...}', true),
('routing_target', '人力资源', '人力资源共享服务咨询', '{...}', true),
...
```
`greeting``routing_prefilter` 段正确指定了 `priority`
```sql
-- 正确(greeting 段)
INSERT INTO quick_rules (rule_type, keyword, priority, is_active) VALUES
('greeting', '你好', 10, true),
...
```
**结果**DB 中 6 条 `routing_target` 记录 `priority``NULL`
#### 3.2 模型层(Pydantic):QuickRuleResponse.priority 声明为非 Optional
`app/api/admin/quick_rules.py:77` 原始定义:
```python
class QuickRuleResponse(BaseModel):
"""规则响应"""
id: int
rule_type: str
category: Optional[str] = None
keyword: str
priority: int # ← 未声明 Optional
response_template: Optional[str] = None
extra_data: Optional[dict] = None
is_active: bool
created_at: str
```
Pydantic 序列化时严格校验 `priority` 必须是 `int`,遇到 `None` → 抛 `ValidationError`
#### 3.3 中间件层:catch_errors_and_log 捕获所有异常后返回 1005
全局 `catch_errors_and_log` 中间件捕获 Pydantic ValidationError 后,**统一返回 1005 通用错误**,前端无法区分 Pydantic 校验失败与其他 500 错误,被迫显示"服务器内部错误"。
### 错误日志(诊断依据)
```
pydantic_core._pydantic_core.ValidationError: 1 validation error for QuickRuleResponse
priority
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
```
### 额外发现(隐藏 bug
排查中用 AST 校验发现,本地 `quick_rules.py` 上线前已存在 **3 处遗留语法错误**
- 2 处 `))` 文本多余括号
- 1 处文件结尾 `)` 缺失
若直接 upload,容器内 Python 启动会 SyntaxError。
---
## 4. 处理办法
| 项目 | 内容 |
|------|------|
| 处理策略 | 自动处理和验证 |
| 执行时机 | 立即(影响正常业务使用) |
| 处理流程 | 1. 修复 `QuickRuleResponse.priority` 字段类型 + 5 个字段补 Optional<br>2. 修复 `init_quick_rules.sql` 补齐 `priority` 列<br>3. DB 实时回填 6 条 NULL 记录<br>4. 修复本地遗留语法错误<br>5. AST 静态校验 + 端到端 API 验证 |
| 验证方式 | 8 个 API 测试用例(rule_type + 6 个分类 + 1 个不存在的分类) |
| 回滚方案 | `priority` 字段从 v1.0`int`)回退到破坏前的状态会重新触发 bug;回滚 = 同步回退模型定义、SQL 脚本、DB 数据,仅建议在测试环境尝试 |
---
## 5. 修复方案
### 5.1 代码修复(3 处)
#### 修复 1`app/api/admin/quick_rules.py`(第 77-89 行)
```python
class QuickRuleResponse(BaseModel):
"""规则响应"""
id: int
rule_type: str
category: Optional[str] = None
keyword: str
# priority 在 DB 中允许 NULLrouting_target 类型的 SQL 初始数据未指定 priority
# 序列化时若为 None 则按 0 处理,避免 Pydantic ValidationError
priority: Optional[int] = None
response_template: Optional[str] = None
extra_data: Optional[dict] = None
is_active: bool
created_at: str
```
#### 修复 2`scripts/init_quick_rules.sql`(第 80 行)
```sql
-- 修复前
INSERT INTO quick_rules (rule_type, category, keyword, extra_data, is_active) VALUES
-- 修复后
INSERT INTO quick_rules (rule_type, category, keyword, extra_data, priority, is_active) VALUES
```
并为 6 条 routing_target 记录添加 `0` 作为 priority 值。
#### 修复 3DB 实时回填
```sql
UPDATE quick_rules SET priority = 0 WHERE priority IS NULL;
```
#### 修复 4(隐藏):本地语法错误
清理 2 处 `))` 和 1 处 `)`
---
## 6. 验证结果
### 6.1 自动化 API 验证(8/8 通过)
| 测试用例 | 期望 | 实际 | 结果 |
|----------|------|------|------|
| `rule_type=routing_target` | 6 条 | 6 条 | ✅ PASS |
| `category=行政` | 1 条 | 1 条 | ✅ PASS |
| `category=人力资源` | 1 条 | 1 条 | ✅ PASS |
| `category=财务` | 1 条 | 1 条 | ✅ PASS |
| `category=法务` | 1 条 | 1 条 | ✅ PASS |
| `category=行政-物业` | 1 条 | 1 条 | ✅ PASS |
| `category=IT服务` | 1 条 | 1 条 | ✅ PASS |
| `category=不存在的分类` | 0 条 | 0 条 | ✅ PASS |
### 6.2 后端日志验证
- 修复前:`pydantic_core._pydantic_core.ValidationError: ... priority Input should be a valid integer`
- 修复后:`GET /admin/quick-rules?rule_type=routing_target&category=... HTTP/1.1 200 OK`(无 ValidationError
### 6.3 真实浏览器验证
待用户在管理后台手动点击下拉菜单复测(端到端 UI 验证)。
### 6.4 AST 静态校验
- 本地:`python -c "import ast; ast.parse(open('quick_rules.py', encoding='utf-8').read())"` → ✅ OK
- 容器内:`python3 /tmp/check_syntax.py /app/app/api/admin/quick_rules.py` → ✅ OK
---
## 7. 关联信息
- **关联需求**: REQ-通用-002(快速回复规则后台管理)
- **关联 PRD**: `docs/01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md`
- **关联技术方案**: `docs/02-技术文档/技术方案-REQ-通用-002-快速回复规则后台管理.md`
- **关联任务说明书**: `docs/07-项目管理/任务说明书/任务说明书-131-快速回复规则后台管理.md`v1.3
- **关联故障手册**: `docs/04-运维文档/部署运维/00-标准故障排查手册.md`CASE-20260728-05
- **修复代码文件**:
- `src/backend/app/api/admin/quick_rules.py`QuickRuleResponse 模型)
- `src/backend/scripts/init_quick_rules.sql`routing_target 初始数据)
- **API 测试脚本**: `scripts/verify_api.py`
---
## 8. 变更记录
| 日期 | 版本 | 变更内容 | 变更人 | 变更原因 | 影响范围 |
|------|------|----------|--------|----------|----------|
| 2026-07-28 | v1.0 | 创建缺陷单,记录快速回复规则"路由目标"分类筛选 500 错误 | 宋献 / Duckula | 用户反馈业务查询无法使用 | 管理后台 "快速回复规则" 页面 |
| 2026-07-28 | v1.0 | 修复 QuickRuleResponse.priority 字段类型 + 修复 SQL 初始数据 + DB 实时回填 | Duckula | 数据库字段为 NULL 但 Pydantic 模型要求非空 | 后端 API + 数据库 |
| 2026-07-28 | v1.0 | 修复本地遗留语法错误(2 处 `))` + 1 处 `)` | Duckula | AST 校验发现隐藏 bug | quick_rules.py |
| 2026-07-28 | v1.0 | 端到端 API 验证(8/8 通过)+ AST 静态校验 | Duckula | 确认修复实际生效 | 验证脚本与容器部署 |
| 2026-07-28 | v1.0 | 文档规范化整改:故障手册新增 CASE-20260728-05、任务说明书 v1.2→v1.3、缺陷单 README 追加清单 | Duckula | 规范化要求 BUG 修复需同步周边文档 | 文档体系 |
---
> **缺陷已关闭**。本案例作为"CASE-20260728-05"收录于故障排查手册,供后续排查参考。