facc04aa65
本提交为 .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-*/
214 lines
7.6 KiB
Plaintext
214 lines
7.6 KiB
Plaintext
# 业务路由推荐 — 类图
|
||
|
||
> 数据模型 + 服务类 + API 类的完整定义
|
||
|
||
```mermaid
|
||
classDiagram
|
||
direction LR
|
||
|
||
%% ===== 数据模型 =====
|
||
class BusinessContact {
|
||
+id: int
|
||
+name: str
|
||
+gender: str
|
||
+department: str
|
||
+position: str
|
||
+responsibility: str
|
||
+extension: str
|
||
+service_area: str
|
||
+wecom_userid: str
|
||
+avatar_url: str
|
||
+business_category: str
|
||
+is_active: bool
|
||
+created_at: datetime
|
||
+updated_at: datetime
|
||
}
|
||
|
||
class RoutingEvent {
|
||
+id: int
|
||
+conversation_id: str
|
||
+employee_id: str
|
||
+message_content: str
|
||
+business_category: str
|
||
+routing_confidence: float
|
||
+contact_id: int
|
||
+contact_name: str
|
||
+is_clicked: bool
|
||
+created_at: datetime
|
||
}
|
||
|
||
%% ===== 服务类 =====
|
||
class RoutingService {
|
||
-ROUTING_PREFILTER_KEYWORDS: list~str~
|
||
-ROUTING_KEYWORD_TO_CATEGORY: dict~str, str~
|
||
-ROUTING_CONFIDENCE_THRESHOLD: float
|
||
+keyword_prefilter(text: str) bool
|
||
+async detect_routing_intent(text: str, employee_id: str) dict
|
||
-async _call_dify_unified_intent(text: str, employee_id: str) dict
|
||
-_fallback_routing_detect(text: str) tuple
|
||
+async get_contact_by_category(category: str, db: AsyncSession) BusinessContact
|
||
+async get_contacts(category: str, keyword: str, db: AsyncSession) list~BusinessContact~
|
||
+async send_contact_card(db, conversation, employee_id, contact, reason) Message
|
||
-async _create_routing_text_message(db, conversation, reason, category) Message
|
||
-async _create_system_hint_message(db, conversation) Message
|
||
+async record_routing_event(db, conversation_id, employee_id, content, category, confidence, contact) RoutingEvent
|
||
-async _push_via_ws(employee_id, conversation, messages) None
|
||
}
|
||
|
||
%% ===== API 类 =====
|
||
class RoutingAPI {
|
||
+GET /api/h5/routing/contact
|
||
+GET /api/routing/contacts
|
||
+POST /api/conversations/{id}/send-contact-card
|
||
}
|
||
|
||
class ApprovalAPI {
|
||
+POST /api/approval/detect-intent
|
||
-async _call_dify_approval_intent(text, employee_id) dict
|
||
}
|
||
|
||
%% ===== Schema 类 =====
|
||
class ApprovalDetectIntentResponse {
|
||
+is_approval_request: bool
|
||
+confidence: float
|
||
+approval_type: str|None
|
||
+source: str
|
||
+intent_type: str
|
||
+business_category: str|None
|
||
+routing_confidence: float
|
||
}
|
||
|
||
class ContactCardResponse {
|
||
+id: int
|
||
+name: str
|
||
+gender: str
|
||
+department: str
|
||
+position: str
|
||
+responsibility: str
|
||
+extension: str
|
||
+service_area: str
|
||
+wecom_userid: str
|
||
+avatar_url: str
|
||
+business_category: str
|
||
}
|
||
|
||
class SendContactCardRequest {
|
||
+contact_id: int
|
||
+reason: str
|
||
}
|
||
|
||
%% ===== 现有类(引用) =====
|
||
class Message {
|
||
+id: str
|
||
+conversation_id: str
|
||
+sender_type: str
|
||
+msg_type: str
|
||
+content: str
|
||
+extra_data: dict
|
||
}
|
||
|
||
class H5AiTask {
|
||
+async process_h5_ai_reply(conversation_id, employee_id, content, dify_conversation_id)
|
||
-async _handle_routing(db, conversation, employee_id, content) bool
|
||
-async _handle_byod_query(db, conversation, employee_id, content)
|
||
-async _persist_and_push(db, conversation, employee_id, content, ...)
|
||
}
|
||
|
||
class WSManager {
|
||
+async broadcast(data: dict)
|
||
+async broadcast_to_employees(employee_ids: list~str~, data: dict)
|
||
}
|
||
|
||
class DifyAPI {
|
||
POST /v1/chat-messages
|
||
+inputs: dict
|
||
+query: str
|
||
+response_mode: "blocking"
|
||
+user: str
|
||
}
|
||
|
||
%% ===== 关系 =====
|
||
BusinessContact "1" --> RoutingEvent : contact_id FK
|
||
|
||
RoutingService --> BusinessContact : 查询/创建
|
||
RoutingService --> RoutingEvent : 记录
|
||
RoutingService --> Message : 创建contact_card消息
|
||
RoutingService --> WSManager : WS推送
|
||
RoutingService --> DifyAPI : 调用统一意图识别
|
||
RoutingService ..> ApprovalDetectIntentResponse : 解析响应
|
||
|
||
RoutingAPI --> RoutingService : 调用
|
||
RoutingAPI ..> ContactCardResponse : 返回
|
||
RoutingAPI ..> SendContactCardRequest : 接收
|
||
|
||
ApprovalAPI ..> ApprovalDetectIntentResponse : 返回(扩展)
|
||
ApprovalAPI --> DifyAPI : 调用(同一Dify应用)
|
||
|
||
H5AiTask --> RoutingService : 调用_handle_routing
|
||
H5AiTask --> Message : 创建AI消息
|
||
H5AiTask --> WSManager : WS推送
|
||
|
||
%% ===== 样式 =====
|
||
style BusinessContact fill:#e8f5e9,stroke:#07C160
|
||
style RoutingEvent fill:#e8f5e9,stroke:#07C160
|
||
style RoutingService fill:#fff3e0,stroke:#ff9800
|
||
style RoutingAPI fill:#e3f2fd,stroke:#1976d2
|
||
style ApprovalAPI fill:#e3f2fd,stroke:#1976d2
|
||
style ApprovalDetectIntentResponse fill:#f3e5f5,stroke:#9c27b0
|
||
style ContactCardResponse fill:#f3e5f5,stroke:#9c27b0
|
||
style SendContactCardRequest fill:#f3e5f5,stroke:#9c27b0
|
||
style Message fill:#f5f5f5,stroke:#9e9e9e
|
||
style H5AiTask fill:#fce4ec,stroke:#e91e63
|
||
style WSManager fill:#f5f5f5,stroke:#9e9e9e
|
||
style DifyAPI fill:#e0f7fa,stroke:#00acc1
|
||
```
|
||
|
||
## 字段详情
|
||
|
||
### BusinessContact(业务联系人)
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| id | int | PK, 自增 |
|
||
| name | str(50) | 联系人姓名 |
|
||
| gender | str(10) | male / female |
|
||
| department | str(100) | 部门名称(如「行政部」) |
|
||
| position | str(100) | 岗位(如「设备管理岗」) |
|
||
| responsibility | str(500) | 负责业务描述 |
|
||
| extension | str(20) | 分机号 |
|
||
| service_area | str(200) | 服务区域/办公地点 |
|
||
| wecom_userid | str(100) | 企微用户ID(openEnterpriseChat 用) |
|
||
| avatar_url | str(500) | 头像URL(空则用姓名首字渲染) |
|
||
| business_category | str(50) | 行政/人力资源/财务/法务/行政-物业 |
|
||
| is_active | bool | 是否启用(默认 true) |
|
||
| created_at | datetime | 创建时间 |
|
||
| updated_at | datetime | 更新时间 |
|
||
|
||
### RoutingEvent(路由命中统计 — P1)
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| id | int | PK, 自增 |
|
||
| conversation_id | str(36) | FK → conversations.id |
|
||
| employee_id | str(64) | 员工企微 UserID |
|
||
| message_content | str(500) | 触发路由的员工消息(截断) |
|
||
| business_category | str(50) | 识别的业务类别 |
|
||
| routing_confidence | float | 路由置信度 |
|
||
| contact_id | int | FK → business_contacts.id |
|
||
| contact_name | str(50) | 联系人姓名(冗余存储) |
|
||
| is_clicked | bool | 员工是否点击「联系TA」 |
|
||
| created_at | datetime | 创建时间 |
|
||
|
||
### RoutingService 方法说明
|
||
|
||
| 方法 | 签名 | 说明 |
|
||
|------|------|------|
|
||
| keyword_prefilter | (text: str) → bool | 非IT关键词预过滤,命中返回True |
|
||
| detect_routing_intent | async (text: str, employee_id: str) → dict | 调用Dify统一意图识别,返回路由判断结果 |
|
||
| _call_dify_unified_intent | async (text: str, employee_id: str) → dict | 调用Dify原生API(私有方法) |
|
||
| _fallback_routing_detect | (text: str) → tuple | Dify不可用时关键词降级兜底(私有) |
|
||
| get_contact_by_category | async (category: str, db) → BusinessContact | 按业务类别查询第一个有效联系人 |
|
||
| get_contacts | async (category: str, keyword: str, db) → list[BusinessContact] | 查询联系人列表(坐席端用) |
|
||
| send_contact_card | async (db, conversation, employee_id, contact, reason) → Message | 发送名片三段式消息(路由文本+名片+系统提示) |
|
||
| record_routing_event | async (db, ...) → RoutingEvent | 记录路由命中事件(P1统计) |
|