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

19 KiB
Raw Blame History

技术方案 - REQ-通用-004 敏感词检测

版本: v1.0 日期: 2026-07-28 REQ编号: REQ-通用-004 关联PRD: 01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.0.md 状态: v0.7.1 已上线(部分达标) 作者: 宋献 关联原型: 01-产品文档/00-产品规划/PRD-REQ-集成-002-管理后台-v1.0.html("代答排除规则"页,含敏感词转移人工/财务敏感问题规则,可作为后续词库管理 UI 雏形)


目录

  1. 设计概述
  2. 现状分析
  3. 架构设计
  4. 关键模块设计
  5. 数据模型
  6. 接口设计
  7. 部署与灰度
  8. 测试策略
  9. 风险与降级
  10. 关联文档

1. 设计概述

1.1 背景

坐席发送消息的事前内容审核机制,包含两个独立检测模块:

模块 检测目标
敏感词检测 wordfilter 0.2.7(基于 DFA 服务用语不当(脏话/推诿/反问)
隐私字段检测 Python re 标准库 手机/身份证/银行卡/个人邮箱

v0.7.1 已上线运行,但有 3 项已知不达标(看板验真测试报告 ⑤节),本文档同步记录现状与改进方向。

1.2 设计目标

目标 描述
G1 覆盖完整 服务用语 + 隐私字段双覆盖
G2 低侵入 不阻塞主消息发送流程(异步/降级)
G3 可扩展 词库/正则可运营(v1.1 计划)
G4 可审计 关键命中可追溯(v1.1 计划)

1.3 设计原则

原则 说明
P1 纵深防御 词库(DFA)+ 正则(pattern)双引擎,单一引擎失败不致命
P2 数字边界 所有数字相关正则必须用 (?<!\d)/(?!\d)禁止\bPython 3 re 对中文失效)
P3 异步可降级 审核失败时降级为 PASS,不阻断发送
P4 集中配置 词库/正则集中在一个 service,修改影响范围可控

1.4 适用范围

是否审核 触发位置
坐席端发送消息 后端 agent_send_message 接口前
员工端(H5)消息 走企微原生反垃圾 + AI Wingman
AI 回复 由 Dify 自带的内容安全机制处理
系统消息 内部通知,无需审核

2. 现状分析

2.1 已有实现(v0.7.1 已上线)

src/backend/
├── app/services/
│   ├── content_moderation_service.py    # 核心:审核 + 隐私检测
│   └── admin/admin_moderation_service.py  # 仅有快速回复审核,**未做敏感词管理**
└── tests/
    └── test_content_moderation.py        # 13 用例,11 通过 2 失败
模块 实现 行数
ContentModerationService 单例服务,wordfilter + 自定义词库 331 行
ModerationAction Enum: pass/warn/block 5 行
ModerationCategory Enum: profanity/politics/porn/ad/privacy/other 8 行
ModerationResult dataclass 返回值 13 行
moderate(text) 主审核入口 50 行
check_privacy_leak(text) 隐私正则检测 33 行

2.2 看板验真测试报告(2026-07-07)— 3 项不达标

# 问题 根因 现状 计划
1 隐私正则 \b1[3-9]\d{9}\b 中文场景失效 Python 3 re 将中文字符视为 \w\b 边界失效 已修复(用 (?<!\d)/(?!\d) 数字边界) -
2 命中动作固定 WARN,不 BLOCK 业务决策(2026-07-08 评审) 🟡 维持 WARN v1.1 可配置
3 自定义词库写死 4 条,未接 system_config 开发未完成 🔴 未修复 v1.1 入库

2.3 测试覆盖(11/13 通过)

用例类型 数量 通过 失败 说明
敏感词命中 WARN 4 4 0 4 条基础词均命中
正常文本 PASS 2 2 0 包括空字符串
隐私字段检测 3 3 0 手机/身份证/正常
自定义词库断言 1 1 0 确认 4 条词存在
默认值 WARN 1 0 1 test_default_action_is_warn_not_block2026-07-08 决策后视为通过
隐私正则边界 2 1 1 待补充中文+号码场景

3. 架构设计

3.1 总体流程

坐席端输入文本
    │
    ▼
[1] 后端 agent_send_message 接口接收
    │
    ▼
[2] 调用 ContentModerationService.moderate(text)
    │
    ├──▶ [2a] wordfilter 检测(DFA 匹配)
    │       │
    │       ├── 命中 → matched_words 非空
    │       └── 未命中 → matched_words 为空
    │
    ├──▶ [2b] _classify(matched) → 分类(当前仅 profanity
    │
    ├──▶ [2c] 决策 action = WARNv1.0 固定)
    │
    ├──▶ [2d] _generate_suggestion(category) → 建议文案
    │
    ▼
[3] 返回 ModerationResult{action, category, matched_words, suggestion}
    │
    ▼
[4] 前端根据 action 渲染:
    ├── PASS → 直接发送
    └── WARN → 黄色提示条 + 仍可发送

3.2 隐私检测独立流程

文本输入
    │
    ▼
check_privacy_leak(text)
    │
    ├──▶ [A] 手机号正则 (?<!\d)1[3-9]\d{9}(?!\d)
    │       └── 命中 → leaked.append("phone")
    │
    ├──▶ [B] 身份证号正则 (?<!\d)\d{17}[\dXx](?!\d)
    │       └── 命中 → leaked.append("id_card")
    │
    ├──▶ [C] 银行卡号正则 (?<!\d)\d{16,19}(?!\d)
    │       └── 命中 → leaked.append("bank_card")
    │
    ├──▶ [D] 个人邮箱正则(排除公司域名)
    │       └── 命中 → leaked.append("personal_email")
    │
    ▼
返回 List[str](如 ["phone", "id_card"]

注意v1.0 check_privacy_leak独立方法未接入 moderate 主流程!前端需单独调用。

3.3 关键时序

sequenceDiagram
    participant 坐席
    participant 前端
    participant 后端
    participant Moderation as ContentModerationService
    participant DB

    坐席->>前端: 输入消息文本
    前端->>后端: POST /api/agent/send {text}
    后端->>Moderation: moderate(text)
    Moderation->>Moderation: wordfilter.blacklisted(text)
    alt 命中
        Moderation->>Moderation: _extract_matched → 4 条基础词循环
        Moderation->>Moderation: _classify → PROFANITY
        Moderation->>Moderation: action = WARN (固定)
        Moderation-->>后端: ModerationResult{WARN, profanity, [...]}
    else 未命中
        Moderation-->>后端: ModerationResult{PASS, None, []}
    end
    后端-->>前端: {action: WARN, suggestion: "..."}
    alt WARN
        前端->>坐席: 显示黄色提示条 + 仍可点"发送"
        坐席->>前端: 点发送
        前端->>后端: 确认发送
    else PASS
        前端->>后端: 直接发送
    end
    后端->>DB: 写 messages 表
    后端-->>前端: 发送成功

4. 关键模块设计

4.1 敏感词检测

4.1.1 核心实现

# content_moderation_service.py
class ContentModerationService:
    def __init__(self):
        self.wf = Wordfilter()  # wordfilter 0.2.7DFA 算法
        self.custom_sensitive_words: List[str] = [
            "投诉我",          # 暗示员工投诉自己
            "你爱找谁找谁",    # 不当推诿
            "自己不会百度吗",  # 不当反问
            "这点小事",        # 轻视员工问题
        ]
        if self.custom_sensitive_words:
            self.wf.addWords(self.custom_sensitive_words)

4.1.2 关键修复(v0.7.1 部署)

BUGFIX: \b(?<!\w) 对中文均失效(Python 3 \w 含中文), 改用 (?<!\d) / (?!\d) 检查数字边界——"电话13800138000" 可正确匹配。

# ❌ 错误写法(Python 3 中文失效)
re.search(r"\b1[3-9]\d{9}\b", "我的电话13800138000")  # 命中失败
# 因为 Python 3 re 中 \w 包含中文,"电话"和"138"之间没有 \b

# ✅ 正确写法(数字边界)
re.search(r"(?<!\d)1[3-9]\d{9}(?!\d)", "我的电话13800138000")  # 命中成功

4.1.3 词库加载策略(v1.1 计划)

服务启动 (FastAPI lifespan)
    │
    ▼
[1] 从 system_config 读取 sensitive_words 键值(JSON 数组)
    │
    ├── 存在 → 解析 + 加载到 Wordfilter
    └── 不存在 → 降级为写死 4 条
    │
    ▼
[2] 同样从 system_config 读取 privacy_patterns
    │
    ├── 存在 → 加载到正则列表
    └── 不存在 → 降级为内置 4 条正则

4.2 隐私字段检测

检测项 正则 说明
phone (?<!\d)1[3-9]\d{9}(?!\d) 11 位 1 开头手机号
id_card (?<!\d)\d{17}[\dXx](?!\d) 18 位身份证(末位 X 兼容)
bank_card (?<!\d)\d{16,19}(?!\d) 16-19 位银行卡
personal_email (?<!\w)[a-zA-Z0-9._%+-]+@(?!servyou-it\.com|servyou\.com\.cn)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?!\w) 排除公司域名

设计要点

  • 所有正则必须用 (?<!\d) / (?!\d) 数字边界,禁止\b
  • 邮箱正则用 (?<!\w) / (?!\w)(邮箱前缀是 ASCII,前面不会是中文)

4.3 命中动作决策

# v1.0 固定 WARN
action = ModerationAction.WARN

未来扩展(v1.1

# 根据 category + severity 决策
SEVERITY_MAP = {
    ModerationCategory.PROFANITY: 1,   # WARN
    ModerationCategory.PRIVACY: 3,     # BLOCK
    ModerationCategory.POLITICS: 3,    # BLOCK
    ModerationCategory.PORN: 3,        # BLOCK
    ModerationCategory.AD: 2,          # WARN + 审计
    ModerationCategory.OTHER: 1,       # WARN
}
action = BLOCK if SEVERITY_MAP[category] >= 3 else WARN

4.4 修改建议生成

SUGGESTION_MAP = {
    ModerationCategory.PROFANITY: "建议改为更专业的表达,例如:「我理解您的问题,我们一起想办法解决」",
    ModerationCategory.POLITICS: "请避免讨论政治话题,保持服务专业性",
    ModerationCategory.PORN: "请使用正式语言",
    ModerationCategory.AD: "请勿发送广告内容",
    ModerationCategory.PRIVACY: "请勿发送员工隐私信息(电话/身份证),如需联系请走企微",
    ModerationCategory.OTHER: "请检查并修改表达",
}

5. 数据模型

5.1 v1.0 现状(无数据库)

词库写死在 __init__ 方法,无 schema。

5.2 v1.1 计划 schema

5.2.1 sensitive_words

CREATE TABLE sensitive_words (
    id           SERIAL PRIMARY KEY,
    word         VARCHAR(100) NOT NULL,
    category     VARCHAR(50) NOT NULL DEFAULT 'profanity',  -- profanity/politics/porn/ad/other
    severity     SMALLINT NOT NULL DEFAULT 1,                -- 1=WARN, 2=WARN+audit, 3=BLOCK
    is_active    BOOLEAN NOT NULL DEFAULT TRUE,
    created_at   TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at   TIMESTAMP NOT NULL DEFAULT NOW(),
    UNIQUE(word, category)
);

CREATE INDEX idx_sensitive_words_active ON sensitive_words(is_active);

5.2.2 privacy_patterns

CREATE TABLE privacy_patterns (
    id           SERIAL PRIMARY KEY,
    name         VARCHAR(50) NOT NULL UNIQUE,  -- phone/id_card/bank_card/personal_email
    pattern      TEXT NOT NULL,
    description  TEXT,
    is_active    BOOLEAN NOT NULL DEFAULT TRUE,
    created_at   TIMESTAMP NOT NULL DEFAULT NOW()
);

5.2.3 moderation_logs 表(v1.1 计划)

CREATE TABLE moderation_logs (
    id             BIGSERIAL PRIMARY KEY,
    message_id     BIGINT REFERENCES messages(id),
    agent_id       INTEGER REFERENCES agents(id),
    matched_words  JSONB,                -- 命中的词列表
    pattern_names  JSONB,                -- 命中的正则名列表
    category       VARCHAR(50),
    action         VARCHAR(20),          -- pass/warn/block
    text_excerpt   TEXT,                 -- 文本前 100 字(避免存全量)
    created_at     TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_moderation_logs_agent ON moderation_logs(agent_id, created_at);
CREATE INDEX idx_moderation_logs_action ON moderation_logs(action, created_at);

5.3 Alembic 迁移

  • 新建迁移:alembic/versions/055_add_moderation_tables.py
  • 初始化 SQLscripts/init_moderation.sql(4 条基础词 + 4 条隐私正则 + 索引)

6. 接口设计

6.1 v1.0 服务层 API(已实现)

class ContentModerationService:
    def moderate(self, text: str) -> ModerationResult: ...
    def check_privacy_leak(self, text: str) -> List[str]: ...
    def add_custom_word(self, word: str) -> None: ...
    def remove_custom_word(self, word: str) -> None: ...

6.2 v1.0 路由层(未实现

v1.0 没有 HTTP API 暴露词库管理。admin_moderation_service.py 仅做快速回复审核。

6.3 v1.1 计划路由层

接口 方法 说明 权限
/api/admin/sensitive-words GET 列表查询 admin
/api/admin/sensitive-words POST 添加 admin
/api/admin/sensitive-words/{id} PUT 更新 admin
/api/admin/sensitive-words/{id} DELETE 删除 admin
/api/admin/sensitive-words/test POST 测试输入文本(不入库) admin
/api/admin/privacy-patterns GET/POST 隐私正则管理 admin
/api/admin/privacy-patterns/{id} PUT/DELETE 更新/删除 admin
/api/admin/moderation-config GET/PUT 命中动作配置 admin
/api/admin/moderation-logs GET 审计日志查询 admin
/api/admin/moderation-stats GET 命中统计(高频词/误判率) admin

参考实现:PRD-REQ-通用-002 快速回复规则后台管理 §3.2 管理 API架构可复用


7. 部署与灰度

7.1 v0.7.1 部署现状

状态
容器 wecom_it_backend 镜像内已包含 wordfilter 库
启动 FastAPI lifespan 钩子不调用(v1.0 词库写死,无需加载)
配置 无需环境变量
灰度 无灰度(全量默认开启)
监控 无专门监控(依赖应用日志)

7.2 v1.1 部署计划

计划
灰度开关 MODERATION_ENABLED(默认 true),关闭时返回 PASS
词库热加载 MODERATION_HOT_RELOAD(默认 false),开启时每 60s 重新读取
数据库迁移 Alembic 055 + init SQL
监控指标 moderation_warn_total{category} / moderation_block_total{category}

7.3 配置同步铁律

来自工作记忆:修改 config.py 默认值 ≠ 配置生效,必须同步更新服务器 .env 文件和 docker-compose.ymlenvironment 段。

新增 3 个配置项时需同步:

  1. src/backend/app/core/config.py 字段
  2. docker-compose.ymlbackend.environment
  3. .env.example 模板

8. 测试策略

8.1 单元测试(已存在,11/13 通过)

src/backend/tests/test_content_moderation.py 13 用例:

模块 用例数 状态
敏感词命中 WARN 4 4/4
正常文本 PASS 2 2/2
隐私字段检测 3 3/3
自定义词库断言 1 1/1
默认值 WARN反转测试 1 ⚠️ 1/1(标记为"反决策"2026-07-08 决策后通过)
隐私正则边界 2 ⚠️ 1/2(中文+号码场景仍待补)

8.2 v1.1 测试计划

  • 功能测试:CRUD(6)+ 词库热加载(2)+ 命中动作配置(3)= 11 条
  • E2E 测试:坐席端发消息命中 WARN 场景(2 条)
  • 回归测试:现有 13 用例全部通过
  • 性能测试10000 字文本审核 < 50ms

8.3 关键测试场景

# 中文+手机号场景(修复验证)
def test_chinese_phone_boundary():
    leaked = service.check_privacy_leak("我的电话13800138000")
    assert "phone" in leaked  # \b 失效场景,数字边界修复

# 反向:WARN 不阻断(产品决策保护)
def test_warn_does_not_block():
    r = service.moderate("自己不会百度吗")
    assert r.action == ModerationAction.WARN
    assert r.action != ModerationAction.BLOCK  # 保护坐席自主权

9. 风险与降级

风险 等级 降级措施
wordfilter 库未安装 🟡 __init__ 异常时降级为纯正则匹配(仅隐私)
服务挂掉 🟢 审核失败不抛异常,返回 PASS(设计 §1.3 P3)
词库加载失败 🟡 降级为写死 4 条 + 日志告警
误报高频 🟡 词库极小(4 条),运营可手动加白名单(v1.1)
漏报(新词未入库) 🟡 依赖运营定期 review;可加 hit-miss 反馈机制
命中动作升级为 BLOCK 误伤 🟠 维持 WARN 直到 1.1 引入可配置化(评审已确认)

9.1 降级路径

ContentModerationService.moderate(text)
    │
    ├── 正常 → WARN/PASS
    │
    ├── 词库未加载 → 仅返回空 PASS + 日志告警
    │
    └── 异常(如 wordfilter 库缺失)
            ↓
        降级为 PrivacyOnlyModeration(仅隐私正则)
            ↓
        仍异常 → 直接返回 PASS(不阻断)

10. 关联文档

文档 位置 关联点
关联 PRD 01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.0.md 需求来源
看板验真测试报告 03-测试文档/04-版本测试报告/看板验真-测试报告-20260707.md 历史基线(11/13 通过)
内容审核服务源码 src/backend/app/services/content_moderation_service.py 当前实现
内容审核测试源码 src/backend/tests/test_content_moderation.py 13 用例基线
快速回复规则后台管理 PRD 01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md 同类功能(运营后台词库管理),v1.1 架构可复用
知识库迭代技术方案 §4 代答排除规则 02-技术文档/技术架构/技术方案-REQ-知识-001-知识库迭代-v1.0.md 关键词匹配机制参考
管理后台原型图("代答排除规则"页) 01-产品文档/08-集成生态/原型-REQ-集成-002-管理后台-v1.0.html 后续 v1.1 词库管理 UI 雏形
项目主文档 #81 07-项目管理/任务说明书/IT智能服务台-项目管理主文档.md v0.7.1 已上线
Python 3 re 中文失效 https://docs.python.org/3/library/re.html \b 边界对中文失效背景

11. 变更日志

版本 日期 变更 变更人
v1.0 2026-07-28 首次整理:v0.7.1 上线内容回溯为正式技术方案 宋献