Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/技术方案-REQ-通用-004-敏感词检测-v1.1.1.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

341 lines
12 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.
# 技术方案 - REQ-通用-004 敏感词检测 v1.1.1(鉴权安全补漏)
> **版本**: v1.1.1v1.1 的安全补丁 / PATCH 级别)
> **日期**: 2026-08-05
> **REQ编号**: REQ-通用-004
> **状态**: [待评审] → 待部署
> **作者**: 宋献 / Duckula
> **关联PRD**: `docs/01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.1.1.md`
> **关联缺陷单**: `docs/03-测试文档/05-缺陷单/BUG-通用-004-敏感词API无鉴权-001.md`
> **关联任务说明书**: `docs/07-项目管理/任务说明书/任务说明书-REQ-通用-004-敏感词检测-v1.1.1.md`
> **关联整改记录**: `docs/04-运维文档/部署运维/00-文档规范化整改记录.md`(#5 整改)
> **前置版本**: `技术方案-REQ-通用-004-敏感词检测-v1.0.archive.md`
---
## 目录
1. [设计概述](#1-设计概述)
2. [根因定位](#2-根因定位)
3. [修复方案](#3-修复方案)
4. [代码变更清单](#4-代码变更清单)
5. [测试策略](#5-测试策略)
6. [部署与回滚](#6-部署与回滚)
7. [风险与豁免](#7-风险与豁免)
8. [关联文档](#8-关联文档)
9. [变更日志](#9-变更日志)
---
## 1. 设计概述
### 1.1 背景
v1.02026-07-28)已规定所有 admin 命名空间下的端点需要 `admin` 角色;v1.12026-07-28)落地了 13 个 admin 端点(`sensitive_words` / `privacy_patterns` / `moderation_logs` / `moderation_config`),但实施时 `src/backend/app/api/admin/sensitive_words.py``APIRouter` 创建时**未挂 `dependencies=[Depends(require_admin)]`**13 端点全部无鉴权。
v1.1.1 作为 PATCH 级别的安全补漏,**仅做鉴权修复**,不改任何业务逻辑、API 契约、数据库 schema。
### 1.2 设计目标
| 目标 | 描述 |
|---|---|
| **G1 一行全覆盖** | 修改 APIRouter 配置 + 一行 import13 端点同时获得 require_admin 依赖 |
| **G2 与现有模式一致** | 复用 `app/api/admin_api.py:50` 已定义的 `require_admin` 函数(不重复实现) |
| **G3 行为对齐** | 与 `admin_api.py` 已加鉴权的同类端点行为完全一致:非 admin 返回 403 + AppException(1004, "无管理权限") |
| **G4 零业务影响** | admin 用户的 13 端点调用全部仍返回 200,业务流程不受影响 |
| **G5 与 v1.2 解耦** | 不动 AI 工作流 / Dify 集成 / service 层逻辑;可独立部署 |
### 1.3 不做
- 不重构 service 层(`SensitiveWordService`
- 不引入新依赖
- 不修改任何端点的请求/响应 schema
- 不修改数据库 schema
- 不加 operator_id 审计字段(列入 v1.1.2 或 v1.2
- 不加正则复杂度限制(列入 v1.1.2)
- 不影响 v1.2 AI 化草案
---
## 2. 根因定位
### 2.1 三层根因
#### 2.1.1 实现层(直接原因)
`sensitive_words.py:44`
```python
# ❌ 当前实现(v1.1 落地时漏写)
router = APIRouter(prefix="/admin", tags=["敏感词管理(v1.1"])
```
应写为:
```python
# ✅ v1.1.1 修复后
from app.api.admin_api import require_admin # 第 1 处变更:顶部 imports
router = APIRouter(
prefix="/admin",
tags=["敏感词管理(v1.1"],
dependencies=[Depends(require_admin)], # 第 2 处变更:router 配置
)
```
#### 2.1.2 规范层(间接原因)
技术方案 v1.0 §6.3 "v1.1 计划路由层" 表格中 11 个端点已**明确列出"权限:admin"列**
| 接口 | 方法 | 说明 | 权限 |
|------|------|------|------|
| `/api/admin/sensitive-words` | GET | 列表查询 | **admin** |
| `/api/admin/sensitive-words` | POST | 添加 | **admin** |
| ... | ... | ... | **admin** |
| `/api/admin/moderation-logs` | GET | 审计日志查询 | **admin** |
| `/api/admin/moderation-stats` | GET | 命中统计 | **admin** |
但 v1.1 实施时未对照此规范逐项实现。
#### 2.1.3 流程层(系统原因)
任务说明书 v1.1 §5"输出成果要求"未列"鉴权维度验收"。TC-通用-004 31 条用例全在功能维度,无安全维度。
### 2.2 同类对照(不应发生但确实发生了)
| 文件 | 鉴权声明 | 端点数 |
|---|---|---|
| `admin_api.py` | ✅ 每端点 `Depends(require_admin)` | 30+ |
| `admin_roles.py` | ✅ 已加 | 5 |
| `admin_users.py` | ✅ 已加 | 8 |
| `welcome.py` | ✅ 已加 | 3 |
| `quiz_admin.py` | ✅ 已加 | 5 |
| `troubleshooting_templates.py` | ✅ 已加 | 4 |
| **`admin/sensitive_words.py`** | ❌ **0** | **13** |
### 2.3 鉴权依赖来源
`app/api/admin_api.py:50-66`
```python
async def require_admin(
agent: Agent = Depends(get_current_agent),
) -> Agent:
"""管理后台权限校验:仅 role='admin' 可访问。"""
if agent.role != "admin":
raise AppException(1004, "无管理权限")
return agent
```
可直接复用,**不重复实现**。
---
## 3. 修复方案
### 3.1 修改点(共 2 处)
#### 3.1.1 顶部 importsL33-41 后追加)
```python
from app.api.admin_api import require_admin # v1.1.1 鉴权补漏
```
#### 3.1.2 APIRouter 配置(L44 替换)
```python
router = APIRouter(
prefix="/admin",
tags=["敏感词管理(v1.1"],
dependencies=[Depends(require_admin)], # v1.1.1 鉴权补漏
)
```
### 3.2 行为变化
| 调用方 | v1.1 行为 | v1.1.1 行为 |
|---|---|---|
| 无 Authorization | 200 OK(裸奔) | 401 Unauthorized |
| 普通坐席 token | 200 OK(裸奔) | 403 + `code:1004 无管理权限` |
| 管理员 token | 200 OK | 200 OK(不变) |
| 重载/正则测试等写操作 | 任意 token 可触发 | 仅 admin 可触发 |
### 3.3 鉴权链路(修复后)
```
HTTP Request
[1] Depends(require_admin)
├──▶ Depends(get_current_agent) ─── 解析 JWT ───▶ 401 (token 无效)
[2] if agent.role != "admin": raise AppException(1004, "无管理权限")
[3] 进入 sensitive_words endpoint handler
[4] handler 返回 success_response(...)
```
---
## 4. 代码变更清单
| 文件 | 变更类型 | 行号 | 内容 |
|---|---|---|---|
| `src/backend/app/api/admin/sensitive_words.py` | 修改 | 顶部 imports | 新增 `from app.api.admin_api import require_admin` |
| `src/backend/app/api/admin/sensitive_words.py` | 修改 | L44 | APIRouter 加 `dependencies=[Depends(require_admin)]` |
| `src/backend/tests/test_sensitive_words_auth.py` | 新增 | - | 6 条鉴权测试用例 |
无 DB 迁移、无 frontend 改动、无 nginx 改动、无 docker-compose 改动。
---
## 5. 测试策略
### 5.1 新增自动化测试
`src/backend/tests/test_sensitive_words_auth.py`
| 用例 | 输入 | 预期 |
|---|---|---|
| test_no_token_returns_401 | 无 Authorization | 401 |
| test_invalid_token_returns_401 | Bearer "invalid" | 401 |
| test_agent_role_returns_403 | 普通坐席 token | 403 + `code:1004` |
| test_admin_role_returns_200_list | admin GET /sensitive-words | 200 |
| test_admin_role_returns_200_reload | admin POST /sensitive-words/reload | 200 |
| test_admin_role_returns_200_pattern_test | admin POST /privacy-patterns/{id}/test | 200 |
### 5.2 容器内端到端验证
`deploy-troubleshoot` 铁律:**宣布修复前必须端到端验证**。
```bash
# 1) 进入容器 grep 验证源码
docker compose exec backend grep -n "require_admin" app/api/admin/sensitive_words.py
# 预期输出:两行(import + dependencies
# 2) 普通坐席 token curl 13 端点(应全部 403
TOKEN_AGENT="<普通坐席 token>"
for endpoint in \
"GET /api/admin/sensitive-words" \
"POST /api/admin/sensitive-words/test" \
"POST /api/admin/sensitive-words/reload" \
"GET /api/admin/privacy-patterns" \
"POST /api/admin/privacy-patterns/1/test" \
"GET /api/admin/moderation-logs" \
"GET /api/admin/moderation-logs/stats" \
"GET /api/admin/moderation-config"; do
METHOD=$(echo $endpoint | awk '{print $1}')
PATH_=$(echo $endpoint | awk '{print $2}')
curl -sS -X $METHOD "http://localhost:8000$PATH_" \
-H "Authorization: Bearer $TOKEN_AGENT" -w "\nHTTP %{http_code}\n"
done
# 预期:全部 HTTP 403 + code:1004
# 3) admin token curl 13 端点(应全部 200
TOKEN_ADMIN="<admin token>"
# 同上循环,预期全部 HTTP 200
```
### 5.3 回归测试
- TC-通用-004 既有 23/31 通过用例不变
- 内容审核业务(坐席发消息审核)流程不变
---
## 6. 部署与回滚
### 6.1 部署步骤(按 jumpserver-V2 铁律)
1. **本地构建产物**:无需前端构建;后端无新依赖,`pip install` 不变
2. **本地源码变更同步到 ASCII 构建路径**(按 §11.7 多路径铁律):
- 中文路径:`D:\资料\03-项目开发\wecom_it_smart_desk\src\backend\app\api\admin\sensitive_words.py`
- ASCII 路径:`D:\dev\wecom\src\backend\app\api\admin\sensitive_words.py`
- **两边都改**
3. **上传到生产**
```bash
# 走 psftp 通道(按部署铁律)
psftp> put sensitive_words.py /<资产>/<系统用户>/
```
4. **容器内操作**
```bash
# 远程禁用 $(),按铁律
docker cp /tmp/sensitive_words.py wecom_it_backend:/app/app/api/admin/sensitive_words.py
docker compose restart backend
```
5. **验证**:跑 §5.2 端到端 curl
### 6.2 回滚预案(30 min 可逆)
```bash
# 1) 恢复源码(git checkout HEAD~1 或手动删除 dependencies 那行)
docker cp /tmp/sensitive_words.py.bak wecom_it_backend:/app/app/api/admin/sensitive_words.py
# 2) 重启
docker compose restart backend
# 3) 验证:admin 仍可访问 13 端点(恢复裸奔状态)
```
回滚代价:再次陷入 13 端点裸奔状态(但仅 30 min 内应急,业务可接受)。
### 6.3 灰度开关
不需要灰度。修复本身就是"从危险→安全"的方向,**无需开关**。
---
## 7. 风险与豁免
| 风险 | 等级 | 缓解 |
|---|---|---|
| admin 误锁(token 过期) | 🟢 低 | 401 不是 403,前端可正常重登录 |
| 修复不彻底(漏掉新端点) | 🟢 低 | 本次仅 1 个文件 13 端点,APIRouter `dependencies=` 一行全覆盖 |
| 性能损耗 | 🟢 低 | `get_current_agent` 缓存 JWT 解析;< 1ms 开销 |
| require_admin 函数被改名 | 🟢 低 | `admin_api.py:50` 已是稳定函数,6 个同类文件已引用 |
| 与 v1.2 部署冲突 | 🟢 低 | 独立部署,CI 可串行 |
| 修复后没跑端到端 | 🟠 高 | 强制走 §5.2 容器内 curl 验证,不信源码 grep |
### 7.1 豁免清单
| 端点 | 豁免原因 |
|---|---|
| 无(13 端点全部要求 admin | - |
---
## 8. 关联文档
| 文档 | 位置 | 关联点 |
|---|---|---|
| 前置技术方案 v1.0 | `02-技术文档/技术架构/技术方案-REQ-通用-004-敏感词检测-v1.0.archive.md` | §6.3 路由层表格已规定 admin |
| 关联 PRD v1.1.1 | `01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.1.1.md` | 需求来源 |
| 关联缺陷单 | `03-测试文档/05-缺陷单/BUG-通用-004-敏感词API无鉴权-001.md` | 触发 |
| 关联任务说明书 v1.1.1 | `07-项目管理/任务说明书/任务说明书-REQ-通用-004-敏感词检测-v1.1.1.md` | 任务清单 |
| 测试用例 | `03-测试文档/03-功能测试用例/TC-通用-004-敏感词检测.md` | 加 §10 鉴权章节 |
| 整改记录 | `04-运维文档/部署运维/00-文档规范化整改记录.md` | #5 整改 |
| 鉴权依赖定义 | `src/backend/app/api/admin_api.py:50-66` | 复用 |
| 同类已加鉴权文件 | `admin_api.py` / `admin_roles.py` / `admin_users.py` / `welcome.py` / `quiz_admin.py` / `troubleshooting_templates.py` | 对照实现 |
| 项目规范 | `docs/00-产品开发流程与文档管理规范.md` v1.9 | § 4.3 文档更新时机 + § 11 整改实践 |
| 多路径铁律 | `docs/00-产品开发流程与文档管理规范.md` § 11.7 | 中文 + ASCII 双改 |
| 部署铁律 | `docs/00-产品开发流程与文档管理规范.md` § 11.5-11.8 | 验证三证据链 |
| 部署排查手册 | `docs/04-运维文档/部署运维/00-标准故障排查手册.md` | 容器内操作参考 |
---
## 9. 变更日志
| 版本 | 日期 | 变更 | 变更人 |
|---|---|---|---|
| v1.0 | 2026-07-28 | 首次整理:v0.7.1 上线内容回溯为正式技术方案 | 宋献 |
| v1.1 | 2026-07-28 | DB化 + 后台 UI + 13 端点实施(**实施时漏加鉴权**,BUG-通用-004) | 宋献 |
| **v1.1.1** | **2026-08-05** | **PATCH 级别安全补漏:APIRouter 加 `dependencies=[Depends(require_admin)]`13 端点全覆盖** | **宋献 / Duckula** |
---
> **关键决策记录**
> - **2026-08-05**v1.1.1 作为 PATCH 级别安全补丁;不阻塞 v1.2 AI 化草案;operator_id 审计 / 正则复杂度限制列入 v1.1.2 或 v1.2 跟进
> - **强制规范沉淀**:任何 `APIRouter(prefix="/admin", ...)` **必须**声明 `dependencies=[Depends(require_admin)]`,无显式豁免不得省略