baodan/docs/开发计划.md

1111 lines
32 KiB
Markdown
Raw Normal View History

# 保险智能客服系统 — 完整开发计划
> **基于**:需求文档 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 导出
⏳ 系统操作日志
```