baodan/docs/开发计划.md

1111 lines
32 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.

# 保险智能客服系统 — 完整开发计划
> **基于**:需求文档 V1.0 + API 文档 V1.1 + 前端开发计划 + BaoDan 源码分析
> **当前状态**后端空壳49 行注释)+ 前端骨架743 行)+ SQL 建表112 行)
> **预计工期**10 个工作日(一人全栈)
> **核心原则**BaoDan 已有的绝不重复造轮子
---
## 零、已确认不需要自研的功能(用 BaoDan 原生)
| 功能 | BaoDan 提供的 | 对应需求编号 |
|------|-----------|:---:|
| LLM 模型配置 CRUD | `ModelProviderCredentialApi` 后台可视化配置 | 6.1 / A6.1.1-A6.1.2 |
| Prompt 编辑/版本/测试 | App 配置页 + Annotation 系统 | 6.2 / A6.1.3-A6.1.4 |
| 对话日志查看/筛选 | BaoDan 后台 /logs 页面 | 4.1.1-4.1.2 |
| 负反馈管理 | `MessageFeedbackExportApi` + Annotation | 4.1.4 |
| 点赞/点踩 | BaoDan WebApp 内置 | 1.4.1 |
| 推荐追问 | `MessageSuggestedQuestionApi` | 1.2.4 / A2.2.2 |
| 来源引用标注 | BaoDan RAG 自动返回 sources | 1.2.3 |
| 会话标题自动命名 | BaoDan 自动命名 | 1.1.5 |
| 知识库测试入口hit testing | `HitTestingApi` | 3.4.1 |
| FAQ 手动条目 | BaoDan Annotation Reply = FAQ | 3.4.3 |
| 文档上传/列表/删除/重试 | BaoDan `/datasets` 后台 | 3.1.1-3.1.2 / 3.1.5 / 3.2.x |
| Markdown 渲染 | BaoDan WebApp 内置 | 1.2.1 |
| 多轮对话上下文 | BaoDan conversation 机制 | 1.1.3 |
| 流式输出 | BaoDan WebApp iframe | 1.1.2 |
**以上功能不需要写后端接口,不需要写前端页面。**
---
## 一、后端自研接口清单25 个,对齐 API 文档 V1.1
### 1.1 认证鉴权4 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 1 | POST | /api/auth/wework-login | 企微 OAuth 登录 → JWT |
| 2 | POST | /api/auth/password-login | 账密登录 → JWT |
| 3 | POST | /api/auth/refresh-token | 刷新 Token |
| 4 | POST | /api/auth/logout | 退出Token 黑名单) |
### 1.2 智能问答5 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 5 | POST | /api/chat/message | 发送消息SSE 流式,调 BaoDan Chat API |
| 6 | GET | /api/chat/sessions | 会话列表(封装 BaoDan API |
| 7 | POST | /api/chat/sessions | 创建会话 |
| 8 | DELETE | /api/chat/sessions/{id} | 删除会话 |
| 9 | GET | /api/chat/sessions/{id}/messages | 消息记录(含来源引用) |
### 1.3 反馈 + 检索2 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 10 | POST | /api/chat/messages/{id}/feedback | 提交反馈(封装 BaoDan API |
| 11 | POST | /api/retrieval/search | 向量检索(封装 BaoDan API |
### 1.4 产品推荐4 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 12 | POST | /api/recommend/generate | 生成方案(调 BaoDan Workflow |
| 13 | GET | /api/recommend/generate/{task_id} | 查询任务状态 |
| 14 | POST | /api/recommend/{id}/export | 导出 PDF/PPT/Word |
| 15 | POST | /api/recommend/{id}/share | 生成分享链接 |
### 1.5 知识库增强6 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 16 | POST | /api/kb/documents/upload | 上传文档(附标签,调 BaoDan API |
| 17 | GET | /api/kb/documents | 文档列表(带自定义标签) |
| 18 | GET | /api/kb/documents/{id}/status | 文档处理状态 |
| 19 | PATCH | /api/kb/documents/{id} | 更新元数据(编号/标签) |
| 20 | DELETE | /api/kb/documents/{id} | 删除文档 |
| 21-24 | * | /api/kb/datasources/* | 保司数据源 CRUD + 同步 + 日志4 个) |
### 1.6 用户权限3 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 25 | CRUD | /api/admin/users | 用户管理(含停用吊销 Token |
| 26 | POST | /api/admin/users/batch-import | 批量导入 |
| 27 | CRUD | /api/admin/roles | 角色管理 |
### 1.7 系统日志 + 统计4 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 28 | GET | /api/admin/logs/system | 系统操作日志 |
| 29 | GET | /api/stats/overview | 使用概览 |
| 30 | GET | /api/stats/trend | 趋势数据 |
| 31 | GET | /api/stats/kb-health | 知识库健康度 |
### 1.8 企微回调4 个)
| # | 方法 | URL | 说明 |
|:---:|:---:|-----|------|
| 32 | GET | /api/wecom/callback | 企微 URL 验证 |
| 33 | POST | /api/wecom/callback | 企微消息接收 |
| 34 | GET | /api/wecom/oauth | OAuth 入口 |
| 35 | GET | /api/wecom/oauth/callback | OAuth 回调 |
---
## 二、前端自研页面清单
### 2.1 用户端5 个页面 + 5 个组件)
| 页面 | 路径 | 说明 | 对应需求 |
|------|------|------|---------|
| LoginPage.vue | /login | 企微 OAuth + 账密登录 | 无(全新) |
| ChatPage.vue | /chat | iframe 嵌入 BaoDan WebApp | M1 |
| ChatFilters.vue | — | 险种/保司筛选器组件 | 1.3.1-1.3.3 |
| ChatCopyButton.vue | — | 复制回答按钮组件 | 1.2.5 |
| RecommendPage.vue | /recommend | 推荐方案表单 + 结果 | M2.1-M2.2 |
| RecommendForm.vue | — | 客户信息表单组件 | 2.1.1-2.1.6 |
| RecommendResult.vue | — | 方案展示组件 | 2.2-2.3 |
| RecommendHistory.vue | /recommend/history | 历史方案列表 | 2.5.1-2.5.4 |
| RecommendDetail.vue | /recommend/detail/:id | 方案详情 + 导出 | 2.3-2.4 |
### 2.2 管理端6 个页面)
| 页面 | 路径 | 说明 | 对应需求 |
|------|------|------|---------|
| DashboardPage.vue | /admin | 管理后台首页(跳转入口) | — |
| UsersPage.vue | /admin/users | 用户 CRUD + 批量导入 | 5.1-5.2 |
| PermissionsPage.vue | /admin/roles | 角色权限配置 | 5.2 |
| StatsPage.vue | /admin/stats | 数据统计仪表盘 | M7 |
| KBManagePage.vue | /admin/kb | 文档管理 + 标签 + 编号 | 3.1.3-3.1.4 |
| DataSourcePage.vue | /admin/datasource | 保司数据源配置 | 3.3 |
> 知识库主界面、对话日志、Prompt 管理、模型配置 → 跳转 BaoDan 后台,不自研。
---
## 三、数据库表3 张自研表,已建好)
| 表名 | 用途 | 当前状态 |
|------|------|:---:|
| wecom_user_mapping | 企微用户 ↔ BaoDan 用户映射 | 已建 |
| recommendation_records | 推荐方案记录 | 已建 |
| system_operation_logs | 系统操作审计日志 | 已建 |
可能需要追加的表:
| 表名 | 用途 | 何时建 |
|------|------|--------|
| document_metadata | 文档编号/标签/险种/保司 | Phase 7 KB 增强时 |
| datasource_configs | 保司 API 数据源配置 | Phase 7 KB 增强时 |
| datasource_sync_logs | 同步日志 | Phase 7 KB 增强时 |
| share_tokens | 方案分享链接 Token | Phase 3 产品推荐时 |
| token_blacklist | JWT 黑名单(或用 Redis | Phase 1 认证时 |
---
## 四、分阶段开发计划
---
### Phase 0项目脚手架0.5 天)
**目标**BaoDan + 自研代码能跑起来,路由能通。
#### 后端任务
**0.1 创建 insurance/ 模块骨架**
```
api/insurance/
├── __init__.py # 空
├── config.py # 配置常量JWT Secret、企微配置等
├── utils/
│ ├── __init__.py
│ ├── response.py # 统一响应封装success_response() / error_response()
│ ├── auth.py # JWT 签发/校验/装饰器
│ └── db.py # 复用 BaoDan 的 dbfrom extensions.ext_database import db
├── wecom/
│ ├── __init__.py
│ ├── routes.py # Blueprint: wecom_bp
│ ├── wecom_bot.py # 消息回调处理
│ ├── wecom_oauth.py # OAuth 登录
│ └── wecom_api.py # 企微 API 封装(发消息/获取 Token
├── recommend/
│ ├── __init__.py
│ ├── routes.py # Blueprint: recommend_bp
│ └── workflow_helper.py # BaoDan Workflow 调用封装
├── permissions/
│ ├── __init__.py
│ ├── routes.py # Blueprint: permissions_bp
│ └── middleware.py # 权限校验中间件
├── stats/
│ ├── __init__.py
│ └── routes.py # Blueprint: stats_bp
└── db/
├── __init__.py
├── models.py # SQLAlchemy 模型3 张自研表)
└── init.sql # 建表脚本(已有)
```
每个 `routes.py` 先放一个 health check 占位接口。
**0.2 注册路由到 BaoDan**
修改 `baodan-main/api/app_factory.py`(或 `main.py`),在 app 创建后加:
```python
try:
from insurance.utils.response import InsuranceResponse
from insurance.wecom.routes import wecom_bp
from insurance.recommend.routes import recommend_bp
from insurance.permissions.routes import permissions_bp
from insurance.stats.routes import stats_bp
app.register_blueprint(wecom_bp, url_prefix='/api/wecom')
app.register_blueprint(recommend_bp, url_prefix='/api/recommend')
app.register_blueprint(permissions_bp, url_prefix='/api/admin')
app.register_blueprint(stats_bp, url_prefix='/api/stats')
except ImportError:
pass
```
**0.3 修改 BaoDan 前端 iframe 配置**
`web/.env.local` 添加:
```
NEXT_PUBLIC_ALLOW_EMBED=true
```
#### 前端任务
**0.4 确认前端项目能启动**
已有 `package.json`、`router`、`App.vue`。安装依赖后 `pnpm dev` 验证。
**0.5 统一响应拦截器**
确保 `src/utils/api.ts` 的 axios 拦截器处理 `code !== 0` 的情况。
#### 验证标准
```bash
# 后端
curl http://localhost:5001/api/health # 返回 {"status":"ok"}
curl http://localhost:5001/api/auth/password-login # 返回参数错误(不是 404
# 前端
pnpm dev → 浏览器打开 localhost:3000 → 显示登录页
```
---
### Phase 1认证系统1 天)
**目标**能登录、能鉴权、Token 能刷新。
#### 后端0.5 天)
**1.1 config.py — 配置项**
```python
JWT_SECRET = os.getenv('JWT_SECRET', 'your-secret-key')
JWT_EXPIRE_HOURS = 2
REDIS_TOKEN_PREFIX = 'token:blacklist:'
WECOM_CORP_ID = os.getenv('WECOM_CORP_ID', '')
WECOM_AGENT_SECRET = os.getenv('WECOM_AGENT_SECRET', '')
WECOM_TOKEN = os.getenv('WECOM_TOKEN', '')
WECOM_AES_KEY = os.getenv('WECOM_AES_KEY', '')
```
**1.2 utils/auth.py — JWT 工具**
- `generate_token(user_id, username, role, department)` → 签发 JWT
- `decode_token(token)` → 解码 + 校验有效期
- `login_required` 装饰器 → 从 Header 提取 JWT → 解码 → 注入 current_user
- `admin_required` 装饰器 → 额外校验 role in [super_admin, admin]
**1.3 utils/response.py — 统一响应**
```python
def success(data=None, message="success"):
return {"code": 0, "message": message, "data": data}
def error(code, message):
return {"code": code, "message": message, "data": None}
```
**1.4 POST /api/auth/password-login**
```
输入:{ username, password }
处理:
1. 参数校验3-64字符8-128字符
2. 查 wecom_user_mapping 表找用户
3. bcrypt 校验密码(需在用户表加 password_hash 字段)
4. 签发 JWT
5. 记录 system_operation_logsaction=login
输出:{ token, expires_in, user }
```
> 注意wecom_user_mapping 表需要加 `password_hash` 字段ALTER TABLE
**1.5 POST /api/auth/refresh-token**
```
处理:
1. 校验旧 Token 有效性(未过期、未在黑名单)
2. 签发新 Token
3. 旧 Token 加入 Redis 黑名单EX 7200
输出:{ token, expires_in }
```
**1.6 POST /api/auth/logout**
```
处理:
1. 当前 Token 加入 Redis 黑名单
2. 记录操作日志
输出:{ code: 0 }
```
**1.7 POST /api/auth/wework-login**
```
输入:{ code, state }
处理:
1. 用 code 调企微 gettoken API → 拿 access_token
2. 用 access_token + code 调企微 auth/get_user_info → 拿 userid/name/department
3. 查/建 wecom_user_mapping 记录
4. 签发 JWT
输出:{ token, expires_in, user }
```
#### 前端0.5 天)
**1.8 LoginPage.vue**
- 企微登录按钮 → `window.location.href = '/api/wecom/oauth'`
- 账密登录表单 → POST /api/auth/password-login → 存 token → 跳 /chat
- 表单校验:用户名必填,密码必填
**1.9 路由守卫**
- `router.beforeEach` 检查 `localStorage.token`
- 无 token → 跳 /login
**1.10 Token 自动刷新**
- api.ts 拦截器:响应 401 → 尝试 refresh-token → 成功则重试原请求 → 失败跳登录页
#### 验证标准
```bash
# 后端
curl -X POST http://localhost:5001/api/auth/password-login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"test1234"}'
# → 返回 JWT token
curl -H "Authorization: Bearer <token>" http://localhost:5001/api/auth/refresh-token
# → 返回新 token
curl -H "Authorization: Bearer <old_token>" http://localhost:5001/api/auth/refresh-token
# → 旧 token 已失效
```
---
### Phase 2对话页面1 天)
**目标**:用户能在 iframe 中跟 BaoDan 对话,筛选器可用。
#### 后端0.5 天)
**2.1 POST /api/chat/messageSSE 流式)**
这是核心接口,封装 BaoDan Chat API
```
输入:{ session_id, message, filters }
处理:
1. JWT 鉴权
2. 参数校验
3. 根据 filters.险种 选择对应的 BaoDan App不同险种不同知识库
- 无险种 → 全库 App
- 重疾险 → 重疾险专用 App
- 以此类推
4. 调 BaoDan Chat APIblocking 模式):
POST http://localhost:5001/v1/chat-messages
Headers: Authorization: Bearer app-xxxxxxxx
Body: { query, user, conversation_id, response_mode: "blocking" }
5. 解析 BaoDan 响应 → 提取 answer + retriever_resources
6. 返回 SSE 流式数据
输出SSE
data: {"type":"source","data":{...}}
data: {"type":"delta","data":"根据"}
data: {"type":"done","data":{"message_id":"...","conversation_id":"..."}}
```
> 关键点BaoDan 本身已有 SSE我们可以直接用 blocking 模式拿到完整回答后转 SSE或用 streaming 模式透传。先做 blocking 模式(简单),后续优化为 streaming。
**2.2 GET /api/chat/sessions**
```
处理:
1. 调 BaoDan API: GET /v1/conversations?user={user_id}&page=1&limit=20
2. 转换格式为 API 文档定义的结构
输出:{ total, items: [{ id, title, created_at, updated_at, message_count }] }
```
**2.3 POST /api/chat/sessions**
```
处理:调 BaoDan API 创建空会话(或前端直接新建)
```
**2.4 DELETE /api/chat/sessions/{id}**
```
处理:调 BaoDan API 删除会话
```
**2.5 GET /api/chat/sessions/{id}/messages**
```
处理:调 BaoDan API 获取消息列表 → 转换格式
```
#### 前端0.5 天)
**2.6 ChatPage.vue**
```
结构:
┌─────────────────────────────┐
│ ChatFilters筛选器
├─────────────────────────────┤
│ │
│ iframeBaoDan WebApp
│ src="/baodan/chat/{app_id}" │
│ │
├─────────────────────────────┤
│ ChatCopyButton + Suggestions │
└─────────────────────────────┘
```
- ChatEmbed 组件iframe 加载 BaoDan WebApp
- ChatFilters险种下拉7 种)+ 保司下拉(动态加载)
- 选择险种后切换 iframe src 到对应 BaoDan App
**2.7 App.vue 侧边栏**
```
左侧导航:
智能问答 → /chat
产品推荐 → /recommend
历史方案 → /recommend/history
────────(管理员以下才显示)
管理后台 → /admin
────────
```
- 根据用户 role 控制显示哪些菜单
- 顶部栏:页面标题 + 用户名 + 退出
#### 验证标准
```bash
# 浏览器
1. 登录 → 自动跳 /chat
2. iframe 加载出 BaoDan 对话界面
3. 筛选器下拉选择"重疾险"
4. 在 iframe 中提问 → BaoDan 回答
5. 侧边栏点击"产品推荐" → 跳转正常
```
---
### Phase 3产品推荐2 天)— 最大自研模块
**目标**:填写客户信息 → 生成方案 → 查看结果 → 导出。
#### 后端1 天)
**3.1 数据模型扩展 — recommendation_records**
确认表结构满足需求,补充 `share_token``share_expire_at` 字段。
**3.2 POST /api/recommend/generate**
```
输入:{ customer, insurance_types, coverage_amount, coverage_period, existing_policies }
处理:
1. JWT 鉴权
2. 参数校验(年龄 1-150、性别 male/female、险种至少 1 项等,见需求文档 13.3 节)
3. INSERT recommendation_recordsstatus=pending
4. 异步调 BaoDan Workflow API
POST http://localhost:5001/v1/workflows/run
Headers: Authorization: Bearer app-yyyyyyyyyyyy
Body: { inputs: { age, gender, insurance_types, ... }, response_mode: "blocking", user }
5. 解析 Workflow 输出 → JSON 格式化为 3 套方案(基础/均衡/全面)
6. UPDATE recommendation_recordsstatus=done, generated_plan=...
7. 记录操作日志
输出:{ task_id, status }
```
> 关键Workflow 返回的是 Markdown 格式的方案文本,需要在 workflow_helper.py 中解析为结构化 JSON。
**3.3 workflow_helper.py — BaoDan Workflow 调用封装**
```python
def call_recommendation_workflow(customer_info, insurance_types, coverage_amount, coverage_period):
"""
调 BaoDan Workflow API 生成推荐方案
返回: { plans: [{ name, total_premium, items: [...], summary }] }
"""
# 1. 构造 inputs
# 2. POST /v1/workflows/run
# 3. 解析 Markdown 输出 → 结构化 JSON
# 4. 失败处理(超时 120s、Workflow 错误)
```
**3.4 GET /api/recommend/generate/{task_id}**
```
处理:查 recommendation_records 表,返回 status + proposal
```
**3.5 POST /api/recommend/{id}/export**
```
输入:{ format: "pdf" | "pptx" | "docx" }
处理:
1. 查推荐记录
2. 根据 format 调对应生成器:
- docx: python-docx先做最简单
- pdf: reportlab 或 weasyprint
- pptx: python-pptx
3. 生成文件 → 保存到 /tmp/exports/ 或 OSS
4. 返回下载链接(带临时 token30 分钟有效)
输出:{ download_url, expire_at }
```
**3.6 POST /api/recommend/{id}/share**
```
处理:
1. 生成随机 token
2. 存 share_tokens 表(含过期时间)
输出:{ share_url, expire_at }
```
#### 前端1 天)
**3.7 RecommendForm.vue — 表单组件**
```vue
结构
基础信息姓名 / 年龄 / 性别radio/ 健康状况select/ 职业
收入预算年收入/ 月预算
关注险种checkbox group寿险/重疾险/医疗险/意外险/年金险/储蓄险
保障需求保额slider + input/ 保障期限select
已有保单可选折叠区域动态添加
[生成方案] 按钮
验证规则见需求文档 13.3 + 14
- 年龄必填整数 1-150
- 性别必选
- 职业必填1-64 字符
- 月预算必填>0
- 险种至少选 1
- 保额>=1
- 保障期限必选
草稿保存
- useDraft composable 自动存 localStorage
- 提交成功后清除草稿
```
**3.8 RecommendPage.vue — 主页面**
```
结构:
RecommendForm表单
↓ 提交后
loading 状态(轮询中...
↓ 完成后
RecommendResult结果展示
```
轮询逻辑:
```javascript
// 提交表单 → 拿到 task_id
// setInterval(2s) 轮询 GET /api/recommend/generate/{task_id}
// status=processing → 继续
// status=done → 展示方案
// status=failed → 显示错误
// 超过 60 次轮询 → 超时提示
```
**3.9 RecommendResult.vue — 结果展示**
```
结构:
三套方案Collapse 折叠面板):
基础方案 | 均衡方案 | 全面方案
每套方案内:
产品表格(产品名/保额/年保费/推荐理由)
总保费
方案总结
操作栏:[浏览器打印] [重新生成] [导出 PDF] [分享]
免责声明
```
**3.10 RecommendHistory.vue — 历史方案列表**
```
功能:
分页表格:方案名称 / 客户姓名 / 险种 / 状态 / 创建时间 / 操作
筛选:客户姓名 / 险种 / 时间范围
操作:查看详情 / 导出 / 删除
```
**3.11 RecommendDetail.vue — 方案详情**
```
功能:
展示完整方案内容
导出按钮PDF / Word / PPT
分享按钮 → 生成链接 → 复制
```
#### 验证标准
```bash
# 端到端
1. /recommend → 填写客户信息 → 点击"生成方案"
2. 页面显示 loading → 轮询中...
3. ~15 秒后显示 3 套方案
4. 点击"导出 PDF" → 浏览器下载 PDF
5. /recommend/history → 看到刚才生成的方案
```
---
### Phase 4企微集成1.5 天)
**目标**:企微私聊/群聊能问 BaoDan 问题。
#### 后端
**4.1 wecom_api.py — 企微 API 封装**
```python
class WeComAPI:
def get_access_token(self):
"""获取企微 access_token缓存到 Redis提前 5 分钟刷新)"""
def send_text_message(self, user_id, content):
"""单聊发消息"""
def send_group_message(self, chat_id, content):
"""群聊发消息"""
def send_long_message(self, user_id, content):
"""长消息自动分段发送(>2048字节时分段间隔500ms"""
```
**4.2 wecom_bot.py — 消息回调处理**
```
GET /api/wecom/callbackURL 验证)
1. SHA1(sort([Token, timestamp, nonce, echostr]))
2. 与 msg_signature 比对
3. 验证通过 → 返回解密后的 echostr
POST /api/wecom/callback消息接收
1. 立即返回 "success"5 秒内)
2. 异步处理:
a. 验证签名 + 解密 XML
b. 判断 MsgType
- text → 继续处理
- image/voice/video/file → 回复"暂不支持该消息类型"
- event → 忽略
c. 群聊消息 → 去掉"@机器人"前缀
d. 查 wecom_user_mapping 获取用户
e. 调 BaoDan Chat APIblocking
f. 通过企微 API 回复
```
> 关键5 秒内必须返回 success。所以用 Flask 的异步机制threading/queue先返回再处理。
**4.3 wecom_oauth.py — OAuth 登录**
```
GET /api/wecom/oauth
构造企微授权 URL → 302 重定向
GET /api/wecom/oauth/callback
1. 用 code 调企微 API → 获取用户信息
2. 查/建 wecom_user_mapping
3. 签发 JWT
4. 302 重定向到前端URL 带 token
```
**4.4 XML 加解密**
```python
# 企微消息加解密AES-CBC
# 参考企微官方 SDK 或自行实现
# 核心decrypt_msg(encrypted_msg) → 明文 XML
```
#### 验证标准
```bash
# 模拟企微回调
curl "http://localhost:5001/api/wecom/callback?msg_signature=xxx&timestamp=xxx&nonce=xxx&echostr=encrypted_echostr"
# → 返回解密后的 echostr
# 模拟消息推送(用企微调试工具)
# → 机器人回复 AI 回答
```
---
### Phase 5用户权限管理1 天)
**目标**:管理员能管用户、配角色、数据隔离。
#### 后端
**5.1 用户 CRUD — /api/admin/users**
```
GET /api/admin/users → 分页列表(支持 role/department/keyword 筛选)
POST /api/admin/users → 新增用户(插入 wecom_user_mapping
PUT /api/admin/users/{id} → 编辑(改角色/部门/状态)
DELETE /api/admin/users/{id} → 停用status=disabled + 吊销所有 Token
```
**5.2 批量导入 — /api/admin/users/batch-import**
```
POSTmultipart/form-data → 解析 Excel → 批量插入
```
**5.3 角色管理 — /api/admin/roles**
```
GET /api/admin/roles → 5 个内置角色 + 自定义角色
POST /api/admin/roles → 新建角色name + permissions 数组)
PUT /api/admin/roles/{id} → 更新权限
DELETE /api/admin/roles/{id} → 删除(内置角色不可删)
```
**5.4 权限校验中间件 — middleware.py**
```python
# 在需要权限的路由上加装饰器:
@require_permission('kb_manage')
def upload_document():
...
# 权限列表:
# chat, proposal, kb_manage, kb_view, log_view, user_manage,
# config_manage, team_stats, system_log
```
**5.5 数据权限5.2.3**
这是 BaoDan 不具备的核心能力:
```
- 销售(sales) → 只能看自己的数据(推荐记录、对话)
- 主管(manager) → 看本组数据(同 department
- 管理员(admin) / 超级管理员(super_admin) → 看全部
```
实现方式:在查询接口中自动注入 `WHERE user_id = ?``WHERE department = ?` 条件。
#### 前端
**5.6 UsersPage.vue**
```
功能:
用户表格(分页):用户名/企微ID/角色/部门/状态/操作
新增用户弹窗
编辑用户弹窗
停用确认弹窗
批量导入按钮(上传 Excel
```
**5.7 PermissionsPage.vue**
```
功能:
左侧:角色列表(可选中)
右侧权限配置面板checkbox 分组)
对话:智能问答 / 产品推荐
管理:知识库管理 / 对话日志 / 用户管理 / 数据统计 / 系统日志
[保存权限] 按钮
```
#### 验证标准
```bash
# 管理员操作
1. 登录 admin → /admin/users → 看到用户列表
2. 新增一个 sales 角色用户
3. 用新用户登录 → 只能看到 /chat、/recommend
4. 访问 /admin/users → 返回 403
```
---
### Phase 6数据统计1 天)
**目标**:管理员能看到系统使用数据。
#### 后端
**6.1 GET /api/stats/overview**
```
处理:
今日问答数 → 查 BaoDan 对话记录(或自建统计表)
活跃用户数 → 查 wecom_user_mapping 中 last_active_at 在今天内的
知识库命中率 → 查 BaoDan 日志中有 retriever_resources 的比例
文档总数 → 查 BaoDan datasets/documents 数量
输出:{ today_chats, active_users, kb_hit_rate, total_documents }
```
**6.2 GET /api/stats/trend**
```
输入:?metric=chat_count&start_date=2026-05-01&end_date=2026-05-31&granularity=day
处理:按天聚合数据
输出:{ metric, granularity, data: [{ date, value }] }
```
**6.3 GET /api/stats/kb-health**
```
输入:?days=30
处理:
热门问题 TOP20 → 按 query 文本分组计数
未命中问题 → 检索结果 score < 0.5 的问题
文档覆盖率 → 各险种文档数量统计
输出:{ hot_questions, missed_questions, coverage }
```
> 注:热门问题和未命中问题需要在问答接口中记录日志到自研表(或从 BaoDan 日志中聚合)。
#### 前端
**6.4 StatsPage.vue**
```
结构:
顶栏4 个指标卡片(今日问答/活跃用户/命中率/文档数)
中间趋势折线图ECharts7天/30天切换
底部左:险种分布饼图
底部右:热门问题 TOP20 表格
```
#### 验证标准
```bash
浏览器打开 /admin/stats → 看到指标卡片 + 图表
(数据量少时可能为空,但页面不报错)
```
---
### Phase 7知识库增强1 天)
**目标**BaoDan 后台不支持的 KB 功能(编号/标签/数据源)。
#### 后端
**7.1 数据库追加表**
```sql
-- 文档元数据(编号、标签、险种、保司)
CREATE TABLE document_metadata (
id SERIAL PRIMARY KEY,
baodan_document_id VARCHAR(64) NOT NULL,
dataset_id VARCHAR(64) NOT NULL,
doc_number VARCHAR(32), -- 编号,如 CJ-XX-001
insurance_type VARCHAR(32), -- 险种
company VARCHAR(64), -- 保司
tags JSONB DEFAULT '[]', -- 自定义标签
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 保司 API 数据源配置
CREATE TABLE datasource_configs (
id SERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL,
api_url VARCHAR(512) NOT NULL,
api_key_encrypted TEXT, -- AES 加密存储
sync_frequency VARCHAR(32), -- CRON 表达式
险种 VARCHAR(32),
last_sync_at TIMESTAMP,
last_sync_status VARCHAR(16),
last_sync_count INTEGER,
status VARCHAR(16) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW()
);
-- 同步日志
CREATE TABLE datasource_sync_logs (
id SERIAL PRIMARY KEY,
datasource_id INTEGER REFERENCES datasource_configs(id),
status VARCHAR(16), -- success/failed
items_added INTEGER DEFAULT 0,
items_updated INTEGER DEFAULT 0,
items_deleted INTEGER DEFAULT 0,
error_message TEXT,
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP
);
```
**7.2 文档管理增强接口**
```
POST /api/kb/documents/upload → 上传文档(同时写 document_metadata
GET /api/kb/documents → 文档列表JOIN document_metadata 拿编号/标签)
PATCH /api/kb/documents/{id} → 更新编号/标签
GET /api/kb/documents/{id}/status → 处理状态
DELETE /api/kb/documents/{id} → 删除文档
POST /api/kb/documents/{id}/retry → 重试
```
**7.3 保司数据源接口**
```
GET /api/kb/datasources → 数据源列表
POST /api/kb/datasources → 新建数据源
POST /api/kb/datasources/{id}/sync → 手动同步(异步)
GET /api/kb/datasources/{id}/logs → 同步日志
```
**7.4 编号自动生成规则**
```python
def generate_doc_number(insurance_type, company):
"""
规则:险种代码-保司代码-序号
CJ-XX-001重疾险-XX人寿-第1个文档
险种代码CJ=重疾, RS=寿险, YL=医疗, YW=意外, CX=车险, NJ=年金, CX=储蓄
"""
```
#### 前端
**7.5 KBManagePage.vue**
```
功能:
文档列表表格(分页):编号/文件名/险种/保司/标签/状态/操作
上传文档弹窗(多文件 + 选择险种 + 填写保司)
编辑元数据弹窗(修改编号、标签)
删除确认
```
**7.6 DataSourcePage.vue**
```
功能:
数据源列表表格:名称/URL/同步频率/上次同步/状态/操作
新建数据源弹窗
[立即同步] 按钮 → 显示同步进度
同步日志查看
```
#### 验证标准
```bash
1. /admin/kb → 上传一个 MD 文件 → 选择险种 → 看到文档在列表中
2. 点编辑 → 修改编号为 CJ-XX-001 → 保存成功
3. /admin/datasource → 新建一个数据源 → 点击"立即同步"
```
---
### Phase 8导出 + 日志 + 收尾0.5 天)
#### 导出功能
**8.1 PDF 导出**
```python
# 使用 reportlab 或 weasyprint
# 按模板渲染推荐方案 → 生成 PDF
# 需要客户提供 PDF 模板(或先用简单模板)
```
**8.2 Word 导出**
```python
# 使用 python-docx
# 最简单的导出方式,优先实现
```
**8.3 PPT 导出**
```python
# 使用 python-pptx
# 按模板生成 PPT需要客户提供模板
```
#### 系统日志
**8.4 system_operation_logs 记录**
在以下操作时自动写入:
- 登录/登出
- 文档上传/删除
- 角色权限变更
- 推荐方案生成
#### 通知告警(可选)
**8.5 企微 Webhook 通知**
```python
def send_alert(message):
"""通过企微机器人 Webhook 发送告警"""
webhook_url = config.WECOM_WEBHOOK_URL
requests.post(webhook_url, json={"msgtype": "text", "text": {"content": message}})
```
告警场景:
- 数据源同步失败
- Token 月消耗超预算
---
## 五、依赖安装清单
### 后端Python
```
PyJWT==2.* # JWT 签发
bcrypt==4.* # 密码哈希
python-docx==1.* # Word 导出
reportlab==4.* # PDF 导出(可选)
python-pptx==1.* # PPT 导出(可选)
openpyxl==3.* # Excel 批量导入
lxml==5.* # XML 解析(企微消息)
cryptography==43.* # AES 加解密(企微消息 + API Key
celery==5.* # 异步任务(企微消息处理、文档同步)
```
> 大部分依赖 BaoDan 已有Flask、SQLAlchemy、Redis不需要重复安装。
### 前端Node
```
vue@3
vue-router@4
element-plus
axios
marked # Markdown 渲染
echarts # 统计图表
```
---
## 六、风险与应对
| 风险 | 影响 | 应对 |
|------|------|------|
| BaoDan Workflow 输出格式不稳定 | 推荐方案解析失败 | 在 workflow_helper.py 中加容错解析 + fallback |
| 企微消息加解密实现复杂 | 回调不通 | 参考企微官方 Python SDKwxwork-sdk |
| 9GB 知识库上传慢 | 首次导入耗时长 | 分批上传 + 异步处理 + 进度反馈 |
| BaoDan 版本升级破坏接口 | 自研代码不可用 | 只改 main.py + app_factory.pyinsurance/ 独立 |
| 一人开发工期紧 | 10 天可能不够 | Phase 7-8 可推后Phase 0-5 是 MVP |
---
## 七、MVP 交付标准
Phase 0-5 完成后即可进入测试:
```
✅ 能登录(企微 + 账密)
✅ 能在 iframe 中跟 BaoDan 对话
✅ 能筛选险种
✅ 能填写客户信息生成推荐方案
✅ 能查看历史方案
✅ 企微私聊能问答
✅ 管理员能管用户和角色
```
Phase 6-8 为增强功能,可并行测试:
```
⏳ 数据统计仪表盘
⏳ 知识库编号/标签管理
⏳ 保司数据源同步
⏳ PDF/Word 导出
⏳ 系统操作日志
```