Files
wecom_it_smart_desk/docs/02-技术文档/技术方案-REQ-通用-002-快速回复规则后台管理.md
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

447 lines
16 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.
# 技术方案 - 快速回复规则后台管理
> **关联PRD**: `01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md`
> **版本**: v1.2
> **日期**: 2026-07-27(初版) / 2026-07-28v1.2 调整)
> **作者**: Simon
---
## 1. 技术架构设计
### 1.1 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ ITAgent 管理后台 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ /quick-rules /quick-rules/greeting │ │
│ │ /quick-rules/contacts /quick-rules/routing │ │
│ │ /quick-rules/targets │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│ HTTP API
┌────────────────────────┴────────────────────────────────────┐
│ FastAPI 后端 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ QuickRuleService (规则加载 + 缓存) │ │
│ │ - get_greeting_keywords() │ │
│ │ - get_contact_keywords() │ │
│ │ - get_routing_keywords() │ │
│ │ - get_routing_targets() │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ QuickRuleCRUD API │ │
│ │ - /api/admin/quick-rules (CRUD) │ │
│ │ - /api/admin/quick-rules/refresh (热刷新) │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘
┌────────────────────────┴────────────────────────────────────┐
│ PostgreSQL 数据库 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ quick_rules 表 │ │
│ │ - id / rule_type / category / keyword / ... │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 1.2 模块设计
| 模块 | 职责 | 文件位置 |
|------|------|---------|
| `QuickRule` | 数据库模型 | `app/models/quick_rule.py` |
| `quick_rules_api` | 管理API | `app/api/admin/quick_rules.py` |
| `QuickRuleService` | 规则加载服务 | `app/services/quick_rule_service.py` |
---
## 2. 数据库设计
### 2.1 quick_rules 表
```sql
CREATE TABLE quick_rules (
id SERIAL PRIMARY KEY,
rule_type VARCHAR(50) NOT NULL,
-- 枚举: greeting, routing_prefilter, routing_target
category VARCHAR(50),
-- 业务分类: IT服务, 行政, 人力资源, 财务, 法务, 行政-物业
keyword TEXT NOT NULL,
-- 关键词内容 或 名称
priority INTEGER DEFAULT 0,
-- 优先级,匹配时按优先级排序
response_template TEXT,
-- 回复模板(可选)
extra_data JSONB,
-- 扩展字段(用于kfid等配置)
is_active BOOLEAN DEFAULT TRUE,
-- 是否启用
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_rule_type_keyword UNIQUE (rule_type, keyword)
);
CREATE INDEX idx_quick_rules_type_active ON quick_rules(rule_type, is_active);
CREATE INDEX idx_quick_rules_category ON quick_rules(category);
```
### 2.2 初始数据
```sql
-- 打招呼关键词(从 ai_handler.py 迁移)
INSERT INTO quick_rules (rule_type, keyword, priority, is_active) VALUES
('greeting', '你好', 10, true),
('greeting', '您好', 9, true),
('greeting', 'hi', 8, true),
('greeting', 'hello', 7, true),
('greeting', '在吗', 6, true),
('greeting', '在么', 5, true);
-- 路由预过滤关键词(从 routing_service.py 迁移)
INSERT INTO quick_rules (rule_type, category, keyword, priority, is_active) VALUES
('routing_prefilter', 'IT服务', '打印机', 10, true),
('routing_prefilter', 'IT服务', '打印', 9, true),
('routing_prefilter', 'IT服务', '电脑', 8, true),
('routing_prefilter', '行政', '复印机', 7, true),
('routing_prefilter', '人力资源', '考勤', 8, true),
('routing_prefilter', '人力资源', '入职', 7, true),
('routing_prefilter', '财务', '报销', 10, true),
('routing_prefilter', '财务', '发票', 9, true),
('routing_prefilter', '法务', '合同', 10, true);
-- 路由目标配置
INSERT INTO quick_rules (rule_type, category, keyword, extra_data, is_active) VALUES
('routing_target', '行政', '机票酒店前台', '{"service_name": "机票酒店前台", "description": "受理机票、酒店、快递、访客等", "url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAV00zNJGfmuA0aG1c2qCDnQ"}', true),
('routing_target', '人力资源', '人力资源共享服务咨询', '{"service_name": "人力资源共享服务咨询", "description": "工牌/考勤/入离职/社保", "url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAvSRL5i5b_Xia8vCmFc2gRw"}', true);
```
---
## 3. API 设计
### 3.1 管理接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/admin/quick-rules` | GET | 列表查询(支持 rule_type、category、keyword 筛选) |
| `/api/admin/quick-rules` | POST | 创建规则 |
| `/api/admin/quick-rules/{id}` | PUT | 更新规则 |
| `/api/admin/quick-rules/{id}` | DELETE | 删除规则 |
| `/api/admin/quick-rules/batch` | POST | 批量导入 |
| `/api/admin/quick-rules/refresh` | POST | 热刷新缓存 |
| `/api/admin/quick-rules/stats` | GET | 统计各类型规则数量 |
### 3.2 请求/响应示例
**GET /api/admin/quick-rules?rule_type=greeting&page=1&size=20**
```json
{
"total": 6,
"page": 1,
"size": 20,
"items": [
{
"id": 1,
"rule_type": "greeting",
"keyword": "你好",
"priority": 10,
"is_active": true,
"created_at": "2026-07-27T10:00:00Z",
"updated_at": "2026-07-27T10:00:00Z"
}
]
}
```
**POST /api/admin/quick-rules**
```json
{
"rule_type": "routing_prefilter",
"category": "IT服务",
"keyword": "投影仪",
"priority": 5,
"is_active": true
}
```
---
## 4. 服务设计
### 4.1 QuickRuleService
```python
class QuickRuleService:
"""规则加载服务 - 单例模式"""
_cache: dict = {}
@classmethod
async def load_all(cls) -> None:
"""启动时加载所有规则到缓存"""
rules = await db.query(QuickRule).where(QuickRule.is_active == True).all()
cls._cache = {
'greeting': [r.keyword for r in rules if r.rule_type == 'greeting'],
'contact': [r.keyword for r in rules if r.rule_type == 'contact'],
'routing_prefilter': {r.keyword: r.category for r in rules if r.rule_type == 'routing_prefilter'},
'routing_target': {r.category: r.extra_data for r in rules if r.rule_type == 'routing_target'},
}
@classmethod
async def refresh(cls) -> None:
"""热刷新缓存"""
await cls.load_all()
@classmethod
def get_greeting_keywords(cls) -> list[str]:
"""获取打招呼关键词列表"""
return cls._cache.get('greeting', [])
@classmethod
def get_contact_keywords(cls) -> list[str]:
"""获取联系人关键词列表"""
return cls._cache.get('contact', [])
@classmethod
def get_routing_keywords(cls) -> dict[str, str]:
"""获取路由预过滤关键词 {keyword: category}"""
return cls._cache.get('routing_prefilter', {})
@classmethod
def get_routing_targets(cls) -> dict[str, dict]:
"""获取路由目标 {category: {service_name, description, url}}"""
return cls._cache.get('routing_target', {})
```
---
## 5. 代码改造计划
### 5.1 新增文件
| 文件 | 说明 |
|------|------|
| `app/models/quick_rule.py` | QuickRule SQLAlchemy 模型 |
| `app/api/admin/quick_rules.py` | CRUD API |
| `app/services/quick_rule_service.py` | 规则加载服务 |
| `migrations/versions/xxx_quick_rules.py` | Alembic 迁移脚本 |
### 5.2 改造文件
| 文件 | 改造内容 |
|------|---------|
| `app/services/ai_handler.py` | 从 QuickRuleService 加载打招呼关键词 |
| `app/api/contacts.py` | 从 QuickRuleService 加载联系人配置 |
| `app/services/routing_service.py` | 从 QuickRuleService 加载路由配置 |
### 5.3 改造示例
**改造前 (ai_handler.py)**:
```python
_GREETING_KEYWORDS = ["你好", "您好", "hi", "hello", "在吗", "在么"]
def check_greeting(text: str) -> bool:
return any(kw in text for kw in _GREETING_KEYWORDS)
```
**改造后 (ai_handler.py)**:
```python
from app.services.quick_rule_service import QuickRuleService
def check_greeting(text: str) -> bool:
keywords = QuickRuleService.get_greeting_keywords()
return any(kw in text for kw in keywords)
```
---
## 6. 前端实现
### 6.1 路由配置
```typescript
// router/index.ts
{
path: '/quick-rules',
component: Layout,
children: [
{ path: '', component: () => import('@/views/quick-rules/index.vue') },
{ path: 'greeting', component: () => import('@/views/quick-rules/greeting.vue') },
{ path: 'contacts', component: () => import('@/views/quick-rules/contacts.vue') },
{ path: 'routing', component: () => import('@/views/quick-rules/routing.vue') },
{ path: 'targets', component: () => import('@/views/quick-rules/targets.vue') },
]
}
```
### 6.2 页面结构
| 页面 | 组件 |
|------|------|
| `quick-rules/index.vue` | 规则管理主页(标签导航 + count 徽标 + 筛选区 + 规则表格) |
| `quick-rules/greeting.vue` | 打招呼关键词管理 |
| `quick-rules/contacts.vue` | 业务联系人管理 |
| `quick-rules/routing.vue` | 路由关键词管理 |
| `quick-rules/targets.vue` | 路由目标配置 |
### 6.3 v1.2 UI 调整
移除 `src/frontend-admin/src/views/quick-rules/index.vue` 顶部 `.stats-row` 三张统计卡片;标签导航 `.tab-bar` 保留 `count` 徽标,作为规则数量的唯一页面展示来源。卡片原有点击行为仅调用 `switchTab`,标签导航已完整承担相同切换能力,因此删除不影响路由切换、筛选、批量删除、编辑、启停、分页和搜索。后端 `getQuickRuleStats` 接口保留;`quick-rules/audit.vue` 的 4 张审计统计卡片不在本次调整范围。
---
## 7. 部署与运维
### 7.1 部署顺序
1. 执行数据库迁移脚本(创建 quick_rules 表 + 初始数据)
2. 部署后端代码
3. 部署前端代码
4. 验证功能
### 7.2 监控指标
| 指标 | 说明 |
|------|------|
| `quick_rules_query_duration` | 规则查询耗时 |
| `quick_rules_cache_hit_rate` | 缓存命中率 |
| `quick_rules_api_requests` | 管理API请求数 |
### 7.3 回滚方案
- 数据库:保留迁移脚本,可降级表
- 代码:旧页面保留,新页面上线后观察一周再下线旧页面
---
## 8. 验收检查点
| 检查点 | 验证方式 |
|--------|---------|
| 数据库迁移成功 | 确认 quick_rules 表存在且数据正确 |
| API 可用 | curl 测试 CRUD 接口 |
| 规则加载正确 | 调用服务方法验证返回值 |
| 前端页面可访问 | 浏览器访问 /quick-rules |
| 现有功能无影响 | 发送消息验证打招呼/路由仍正常工作 |
| 热刷新生效 | 修改规则后无需重启即可生效 |
---
## 9. v2.0 增量设计 — 智能体自动优化 + 导入导出
### 9.1 新增 API 端点
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/admin/quick-rules/import` | POST | 批量导入(JSON/Excel |
| `/api/admin/quick-rules/export` | GET | 批量导出(JSON/Excel |
| `/api/admin/quick-rules/batch-delete` | POST | 批量删除(按ID列表) |
| `/api/admin/quick-rules/agent-update` | POST | 智能体专用更新(带置信度) |
| `/api/admin/quick-rules/audit-log` | GET | 规则修改审计日志 |
| `/api/admin/quick-rules/stats` | GET | 规则统计(命中率、误判率) |
### 9.2 智能体专用更新请求
```json
POST /api/admin/quick-rules/agent-update
{
"rule_type": "routing_prefilter",
"category": "IT服务",
"keyword": "投影仪",
"priority": 5,
"confidence": 0.92,
"reason": "误判率分析:最近30天5条'投影仪'相关消息被误判为闲聊",
"agent_id": "rule_optimizer_v1",
"execution_id": "exec_2026-07-27_001"
}
```
**置信度阈值**
- ≥ 0.85:自动应用并记录审计
- 0.5 ~ 0.85:进入待审核队列
- < 0.5:拒绝(不入库)
### 9.3 批量导入导出
**导入格式(JSON**
```json
{
"rules": [
{
"rule_type": "greeting",
"keyword": "你好",
"priority": 10,
"is_active": true
}
],
"mode": "skip_duplicates" // skip_duplicates | overwrite | fail
}
```
**导出格式**:支持 JSON 和 Excel 两种格式
### 9.4 审计日志表
```sql
CREATE TABLE quick_rule_audit_log (
id SERIAL PRIMARY KEY,
rule_id INTEGER,
rule_type VARCHAR(50),
action VARCHAR(20), -- create/update/delete/batch
actor_type VARCHAR(20), -- human/agent
actor_id VARCHAR(100), -- agent_id 或 坐席ID
old_value JSONB,
new_value JSONB,
confidence FLOAT,
reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 9.5 规则统计
```python
GET /api/admin/quick-rules/stats
{
"total_rules": 45,
"hit_rate": 0.78,
"false_positive_rate": 0.05,
"active_rules": 42,
"by_type": {
"greeting": 6,
"routing_prefilter": 25,
"routing_target": 14
},
"top_unkonwn_keywords": ["云盘权限", "VPN", "VPN登录"]
}
```
### 9.6 实施步骤
| 步骤 | 内容 | 预估时间 |
|------|------|---------|
| 1 | 批量导入/导出 API | 1h |
| 2 | 批量删除 API | 0.5h |
| 3 | 智能体专用 API(带置信度) | 1.5h |
| 4 | 审计日志表 + 记录逻辑 | 1.5h |
| 5 | 规则统计 API | 1h |
| 6 | 前端导入/导出按钮 | 1h |
| 7 | 智能体调用脚本示例 | 0.5h |
| **总计** | | **7h** |
---
## 10. 变更记录
| 日期 | 版本 | 变更内容 | 变更人 | 变更原因 | 影响范围 |
|------|------|---------|-------|----------|----------|
| 2026-07-27 | v1.0 | 初始版本 | Simon | 快速回复规则后台管理技术设计建立 | 后端、数据库及管理后台 |
| 2026-07-27 | v1.1 | 新增第9章 v2.0 增量设计(智能体自动优化 + 导入导出) | Simon | 补充智能体扩展设计 | API、审计及导入导出 |
| 2026-07-28 | v1.2 | 移除 `index.vue` 顶部 `.stats-row` 三张重复统计卡片,保留 `.tab-bar` 的 count 徽标;统计接口与审计页卡片保持不变 | Simon | 页面信息冗余 | 管理后台 `/quick-rules` 页面 |