baodan/docs/保险智能客服系统_需求文档.md

6162 lines
185 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
> **日期**2026-05-31
> **技术基础**BaoDanAI 引擎)+ 自研企微后端
> **开发人数**1 人
> **功能项总计**137 项(用户前端 35 + 管理前端 56 + 企微机器人 5 + 后端接口 41
### 实现方式说明(必读)
每个功能点后标注了实现方式,含义如下:
| 标注 | 含义 | 说明 |
|------|------|------|
| **BaoDan 原生** | BaoDan 自带,无需写代码 | 在 BaoDan 后台配置即可使用 |
| **BaoDan + 增强** | BaoDan 有基础功能,需少量代码增强 | BaoDan 提供 80%,你补 20% |
| **BaoDan Workflow** | 在 BaoDan 可视化编辑器中拖拽编排 | 不写代码,但需要设计 Workflow 逻辑 |
| **自研** | 需要完全自己写代码 | 前端页面或后端接口 |
| **自研(调 BaoDan** | 自研后端接口,内部调 BaoDan API | Flask Blueprint -> 直接调用 BaoDan Python 模块 |
| **自研 + 企微 API** | 自研后端,对接企微开放平台 API | 需要企微应用权限 |
---
## 一、项目概述
### 1.1 项目目标
为保险代理团队构建一个基于知识库的智能客服系统,核心能力:
1. **智能问答**:代理人/客户通过网页或企微提问,系统从知识库中检索准确答案
2. **产品推荐**:根据客户需求自动生成保险产品推荐方案,支持导出 PDF/Word/PPT
3. **知识库管理**:支持批量导入 9GB 的 MD 格式保险文档,按险种/保司分类管理
4. **企微集成**:通过企微机器人实现单聊/群聊问答,通过企微 OAuth 实现免密登录
5. **管理后台**:知识库管理、日志审计、用户权限、系统配置、数据统计
### 1.2 用户角色
|角色|说明|访问方式|核心操作|
|------|------|---------|---------|
|超级管理员|系统管理者|网页后台|全部功能,含系统配置和用户管理|
|管理员|运营/合规人员|网页后台|知识库管理、日志查看、数据统计|
|销售主管|团队负责人|网页/企微 H5|查看团队问答数据、产品推荐|
|销售人员|保险代理人|网页/企微 H5|智能问答、产品推荐|
|客户|投保人/被保人|网页/企微 H5|智能问答(受限)、产品推荐|
|客户|投保人/被保人|网页/企微 H5|智能问答(受限)、产品推荐|
### 1.3 技术架构与实现方式
|功能模块|实现方式|说明|
|---------|---------|------|
|智能问答M1|BaoDan 原生|直接用 BaoDan 的对话功能|
|产品推荐M2|自研|BaoDan Workflow + 自研前端表单,代码放 api/insurance/recommend/|
|知识库管理M3|BaoDan 原生 + 自研增强|BaoDan 后台为主,标签/编号等功能需自研|
|留痕与日志M4|BaoDan 原生 + 自研增强|BaoDan 有基础日志,导出/操作日志需自研|
|用户与权限M5|自研|企微 OAuth + 角色权限体系,代码放 api/insurance/permissions/|
|系统配置M6|BaoDan 原生 + 自研增强|BaoDan 管理模型/Prompt模板和告警需自研|
|数据统计M7|自研|成本监控、健康度统计,代码放 api/insurance/stats/|
|企微机器人|自研|Flask Blueprint 对接企微 API + BaoDan代码放 api/insurance/wecom/|
|后端接口A1-A8|自研|Flask Blueprint复用 BaoDan 基础设施|
|企微 OAuth 登录|自研 + 企微 API|OAuth 免密登录,代码放 api/insurance/wecom/|
---
## 二、用户前端功能M1 + M2
### M1智能问答
#### 1.1 对话交互
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|1.1.1|文字输入框|支持回车发送Shift+Enter 换行;字数上限提示|高|BaoDan 原生|
|1.1.2|流式输出Streaming|回答逐字打印,降低等待焦虑感;显示「生成中...」状态|高|BaoDan 原生|
|1.1.3|多轮对话上下文保持|追问时携带历史上下文,支持「刚才你说的 XX 是什么意思」等追问|高|BaoDan 原生|
|1.1.4|会话管理|新建会话、切换历史会话列表、删除会话(软删除)|中|BaoDan 原生|
|1.1.5|会话标题自动命名|首条问题提取关键词作为会话标题,可手动重命名|低|BaoDan 原生|
#### 1.2 回复展示
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|1.2.1|Markdown 渲染|加粗、表格、有序/无序列表、代码块等格式正确渲染|高|BaoDan 原生|
|1.2.2|图片内联展示|回复中引用产品条款截图、对比图时可直接预览;支持点击放大|中|BaoDan + 增强|
|1.2.3|来源引用标注|回复末尾附来源文档名或 API 来源标签,可点击查看原始片段|高|BaoDan 原生|
|1.2.4|相关推荐问题|回答下方展示 2-3 条系统推荐的延伸问题,点击直接提问|低|BaoDan + 增强|
|1.2.5|一键复制回答|复制按钮,将完整回答文本复制到剪贴板|中|BaoDan + 增强|
#### 1.3 检索范围控制
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|1.3.1|险种筛选器|提问前可选定特定险种(重疾/寿险/医疗/储蓄等)缩小检索范围|中|BaoDan + 增强|
|1.3.2|保司筛选器|可指定一家或多家保司范围,避免跨保司混淆|中|BaoDan + 增强|
|1.3.3|全库检索(默认)|未选筛选器时跨险种、跨保司综合检索|高|BaoDan 原生|
#### 1.4 反馈与纠错
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|1.4.1|点赞/点踩按钮|回答下方快捷反馈,「有用」/「无用」一键投票|高|BaoDan 原生|
|1.4.2|纠错反馈入口|点击「回答有误」后弹窗,用户可输入正确信息提交|中|自研|
### M2产品推荐方案
#### 2.1 基础信息表单
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|2.1.1|基础信息表单|姓名、年龄、性别、健康状况(体况选项)、职业类别|高|自研|
|2.1.2|险种选择|多选险种;选择后动态渲染对应必填字段(不同险种字段不同)|高|自研|
|2.1.3|保障需求设置|保额目标(滑块/输入框)、月供预算上限|高|自研|
|2.1.4|保障期限选择|定期 N 年 / 保至某岁 / 终身,与险种联动可选项|高|自研|
|2.1.5|已有保单录入|录入客户现有保障AI 可避免重复建议;非必填|中|自研|
|2.1.6|表单草稿自动保存|每次输入变更后自动保存草稿,刷新或意外关闭后可恢复|中|自研|
**表单字段详细定义**
|字段|类型|必填|选项/规则|说明|
|------|------|:---:|-----------|------|--------
|姓名|文本|否|最大 20 字符|客户姓名|
|年龄|数字|是|0-150 整数|影响保费计算|
|性别|单选|是|男 / 女|部分险种性别差异定价|
|健康状况|单选|是|健康 / 有既往病史 / 慢性病 / 重大疾病史|影响核保结果|
|职业|文本|是|最大 50 字符|高危职业部分险种不可投|
|关注险种|多选|是|寿险 / 重疾险 / 医疗险 / 意外险 / 年金险 / 储蓄险|决定推荐范围|
|保额目标|数字|是|1-1000 万元|每个险种独立设置|
|月预算上限|数字|是|单位:元|所有险种总预算|
|保障期限|下拉|是|定期 10/20/30 年 / 保至 60/70/80 岁 / 终身|随险种联动|
#### 2.2 AI 自动匹配产品组合
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|2.2.1|AI 自动匹配产品组合|基于客户信息 + 知识库LLM 输出推荐产品列表及配置方案|高|BaoDan Workflow|
|2.2.2|多方案输出|生成 2-3 套保障方案(基础/均衡/全面),供销售选择|中|BaoDan Workflow|
|2.2.3|推荐理由说明|每款产品附加 AI 生成的推荐原因1-2 句话)|中|BaoDan Workflow|
**BaoDan Workflow 设计**
```
输入参数JSON年龄、性别、职业、收入、预算、关注险种等
[节点1] 参数校验 - 检查必填项
[节点2] 检索策略 - 根据险种确定搜索范围
[节点3] 知识库检索 - 从对应知识库搜索匹配的产品条款
[节点4] LLM 方案生成 - 基于检索结果,生成基础/均衡/全面三套方案
[节点5] 格式化输出 - Markdown 格式的方案报告
```
#### 2.3 方案预览编辑
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|2.3.1|方案预览页|实时渲染推荐方案内容,支持编辑、导出、分享操作|高|自研|
|2.3.2|关键字段手动修改|保额、保费、受益人、保障期限等可直接在预览页编辑|高|自研|
|2.3.3|产品替换|从候选产品列表中替换某款产品,实时刷新预览|中|自研|
#### 2.4 方案导出分享
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|2.4.1|导出 PDF/PPT 格式|按客户方提供模板渲染,输出标准 PDF/PPT 文件|高|自研|
|2.4.2|导出 Word (.docx)|可编辑的 Word 格式,方便进一步调整|中|自研|
|2.4.3|分享链接|生成有时效的在线预览链接,无需登录即可查看|低|自研|
|2.4.4|重新生成|保留原始输入参数,一键重新调用 AI 生成新版本|中|自研|
#### 2.5 历史方案管理
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|2.5.1|历史方案列表页|展示所有已生成的推荐方案,支持分页浏览|高|自研|
|2.5.2|多维筛选与搜索|按客户姓名、险种、时间段、创建人筛选|高|自研|
|2.5.3|方案详情查看|查看单个方案的完整内容,包括各方案对比|高|自研|
|2.5.4|历史版本重新下载|任意历史版本均可再次导出 PDF/Word|高|自研|
---
## 三、管理前端功能M3 - M7
### M3知识库管理
#### 3.1 文档上传管理
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|3.1.1|批量上传文档|支持 MD、Word、TXT 格式,可多文件同时上传,显示上传进度条|高|BaoDan 原生|
|3.1.2|文档列表|展示文件名、险种、保司、上传时间、处理状态;支持排序和搜索|高|BaoDan 原生|
|3.1.3|文档编号自动生成|上传时按「险种代码-保司代码-序号」规则自动分配编号,可手动修改|高|BaoDan + 增强|
|3.1.4|分类标签管理|险种标签、保司标签、自定义标签;支持新建/编辑/删除标签|高|BaoDan + 增强|
|3.1.5|文档删除与归档|软删除:归档后不再参与检索,但保留文件与记录;可恢复|中|BaoDan 原生|
|3.1.6|文档版本管理|上传同名新版本时保留旧版本;可切换使用哪个版本参与检索|中|BaoDan + 增强|
**知识库分类结构**
|知识库名称|分类维度|预估规模|||
|-----------|---------|---------|--------|--------|
|寿险-产品条款|险种|数百到数千 MD 文件|||
|寿险-核保规则|险种|数十到数百|||
|重疾险-产品条款|险种|数百|||
|重疾险-核保规则|险种|数十|||
|医疗险-产品条款|险种|数百|||
|意外险-产品条款|险种|数百|||
|车险-产品条款|险种|数百|||
|年金险-产品条款|险种|数百|||
|通用-理赔流程|通用|数十|||
|通用-监管法规|通用|数十|||
**文档分段规则Chunk 策略)**
|设置项|推荐值|说明|
|--------|--------|------|
|分段标识符|按 Markdown 标题层级|用 `\n##``\n###` 作为分隔符|
|最大分段长度|800 tokens|保险条款句子长,太小会切断语义|
|分段重叠|150 tokens|避免边界处丢信息|
|索引模式|高质量|使用 DeepSeek Embedding|
#### 3.2 处理状态监控
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|3.2.1|处理流水线状态|每份文档展示状态:待处理/格式转换中/向量化中/完成/失败|高|BaoDan 原生|
|3.2.2|失败原因与重试|失败文档显示错误原因OCR 失败/格式错误等),支持手动重试|高|BaoDan 原生|
|3.2.3|向量化进度|大文档分批处理时展示百分比进度条|低|BaoDan 原生|
#### 3.3 保司 API 数据源
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|3.3.1|数据源配置|新增/编辑保司 API接口地址、密钥加密存储、同步频率定时 CRON|高|自研|
|3.3.2|手动立即同步|一键触发指定数据源立即同步,显示实时进度|高|自研|
|3.3.3|同步状态监控|上次同步时间、同步条目数、耗时、成功/失败状态|中|自研|
|3.3.4|数据变更日志|每次同步记录新增/更新/删除的产品条目明细|低|自研|
|3.3.5|同步异常告警|失败时通过配置渠道(邮件/企微)通知管理员|中|自研|
#### 3.4 检索效果测试
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|3.4.1|知识库测试入口|输入测试问题,预览检索命中的文档片段与相关度分数|中|BaoDan 原生|
|3.4.2|未命中问题列表|近 7/30 天用户提问中知识库无法回答的问题汇总,辅助补充文档|高|自研|
|3.4.3|FAQ 手动条目|高频问题可手动添加固定问答对,优先级高于向量检索结果|中|自研|
---
### M4留痕与日志
#### 4.1 问答记录
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|4.1.1|全量对话记录查看|完整存储:问题/回答/引用来源/用户/时间戳;支持单条详情查看|高|BaoDan 原生|
|4.1.2|多维筛选|按用户、时间范围、险种、关键词、评分筛选|高|BaoDan + 增强|
|4.1.3|对话记录导出|BaoDan 对话 API 支持分页查询,后端封装导出为 CSV|中|自研(调 BaoDan|
|4.1.4|负反馈管理|BaoDan 内置标注系统可查看反馈;处理流程需自研|中|BaoDan + 增强|
#### 4.2 导出日志
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|4.2.2|导出下载操作日志|记录每次导出操作(操作人 + 时间 + 格式)|高|自研|
#### 4.3 系统操作日志
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|4.3.1|用户登录日志|登录/登出记录:用户/IP/设备/时间|中|自研|
|4.3.2|知识库操作日志|文档上传/删除/归档/标签变更等操作记录|中|自研|
|4.3.3|配置变更日志|系统配置(模型/Prompt/模板)的变更记录,含变更前后值|低|自研|
---
### M5用户与权限
#### 5.1 账号管理
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|5.1.1|新增/编辑/停用账号|基础信息维护;停用后立即踢出登录态|高|BaoDan 原生|
|5.1.2|绑定企微账号|通过企微 UserId 关联,实现企微 OAuth 免密登录|高|自研|
|5.1.3|分组管理|按城市/团队/部门建组,用于数据权限隔离|中|自研|
|5.1.4|批量导入用户|提供 Excel 模板,批量创建账号|低|自研|
#### 5.2 角色与权限
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|5.2.1|内置角色|超级管理员/管理员/销售主管/销售人员/客户 五档内置角色|高|BaoDan + 增强|
|5.2.2|功能权限配置|各角色可访问的功能模块开关,可视化勾选配置|高|自研|
|5.2.3|数据权限分层|销售见自己;主管见本组;管理员全量可见|高|自研|
|5.2.4|自定义角色|可新建角色并自由组合权限项|低|自研|
---
### M6系统配置
#### 6.1 LLM 模型配置
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|6.1.1|多模型配置|支持 GPT-4o / DeepSeek 等,每个模型单独配置 API Key|高|BaoDan 原生|
|6.1.2|API Key 加密存储|Key 入库前 AES 加密,页面显示脱敏;不可明文查看|高|BaoDan + 增强|
|6.1.3|模型删除/切换默认|删除未使用的模型配置;切换默认模型|低|BaoDan 原生|
|6.1.4|模型参数调整|Temperature / Max Tokens / Top-P 等参数调节|低|BaoDan 原生|
|6.1.5|连通性测试|一键 Ping 检测模型 API 是否可达,显示延迟|中|BaoDan 原生|
#### 6.2 Prompt 提示词管理
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|6.2.1|问答 Prompt 编辑|富文本编辑器,支持变量占位符(如 `{{险种}}`|高|BaoDan 原生|
|6.2.2|Prompt 变量配置|配置 Prompt 中使用的变量(如险种、保司),绑定数据源|中|BaoDan 原生|
|6.2.3|版本管理|每次保存自动生成版本快照,支持查看历史版本与一键回滚|中|BaoDan 原生|
|6.2.4|即时测试|编辑页内置测试入口,输入测试问题直接预览 Prompt 效果|中|BaoDan 原生|
**系统 Prompt 设计**
```
你是一名专业的保险顾问助手,服务于保险代理团队。
## 回答规则
1. 只基于知识库内容回答。如果没有相关信息,明确告知"当前知识库中未找到相关内容",不要编造答案
2. 标注信息来源:回答时引用具体的文档名称或条款编号,方便用户查证
3. 回答格式:
- 先用 1-2 句话给出核心结论
- 再用条目列出详细说明
- 最后附上注意事项或免责声明
4. 专业术语处理:首次出现的专业术语用括号做简要解释
5. 涉及金额/比例:必须精确引用知识库中的数字,不可四舍五入或估算
6. 涉及免责/拒赔条款:必须完整列出,不可省略
## 特殊场景
- 如果用户问"推荐什么产品",引导用户提供年龄、职业、预算等信息
- 如果用户的问题模糊,先追问澄清再回答
- 如果知识库中有多个产品/条款适用,列出所有适用项并说明区别
```
#### 6.3 导出模板管理
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|6.3.1|模板文件上传|上传 Word/PDF 底版模板文件|高|自研|
|6.3.2|险种与模板映射|不同险种指定不同模板支持一险种对应多模板A/B 选择)|高|自研|
|6.3.3|占位符字段定义|配置模板中哪些字段由 AI 填充,字段名与数据字段映射|高|自研|
#### 6.4 通知告警配置
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|6.4.1|通知渠道设置|邮件 SMTP 配置 / 企微机器人 Webhook URL|中|自研|
|6.4.2|告警规则配置|API 同步失败 N 次告警 / Token 月消耗超 X 元告警|中|自研|
---
### M7数据统计
#### 7.1 使用概览
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|7.1.1|核心指标卡片|今日问答数、活跃用户数、知识库命中率等关键指标|高|自研|
|7.1.2|趋势折线图|近 7/30 天问答量趋势、用户活跃度趋势|高|自研|
|7.1.3|险种/保司分布饼图|当期问答按险种、按保司维度分布|低|自研|
#### 7.2 知识库健康度
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|7.2.1|热门问题 TOP20|按提问频次排序,辅助补充/优化知识库|中|自研|
|7.2.2|未命中问题汇总|近 7/30 天知识库无法回答的问题列表,标记已处理状态|高|自研|
|7.2.3|文档覆盖率概览|各险种/保司文档数量、向量化状态、最后更新时间|低|自研|
#### 7.3 成本监控
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|7.3.1|Token 用量统计|按模型、按天统计 Token 消耗量和费用|中|自研|
|7.3.2|费用估算|基于 Token 单价估算当月累计费用,与预算阈值对比|中|自研|
|7.3.3|费用超限预警|当月费用超过设定阈值时页面内显示警告横幅|中|自研|
---
### 企微机器人(新增模块)
|编号|功能点|功能说明|优先级|实现方式|
|------|--------|---------|:---:|--------|
|WB-01|单聊对话|用户在企微私聊机器人,发送文字消息后收到 AI 回复|高|自研 + 企微 API|
|WB-02|群聊 @触发|在企微群内 @机器人 提问AI 回复到群内|高|自研 + 企微 API|
|WB-03|非文本消息处理|用户发图片/表情/文件时,回复「暂不支持该消息类型」|中|自研 + 企微 API|
|WB-04|消息异步处理|多人同时提问不阻塞,企微 5 秒内返回响应|高|自研|
|WB-05|回复超长消息处理|AI 回复超过企微限制时自动分段发送|低|自研|
**单聊交互流程**
```
用户私聊机器人发送"重疾险等待期多少天?"
-> 企微服务器推送消息到后端
-> 后端调 BaoDan API
-> BaoDan 检索知识库并生成回答
-> 后端通过企微 API 回复用户
```
**群聊交互流程**
```
群内用户 "@机器人 重疾险等待期多少天?"
-> 企微服务器推送消息到后端
-> 后端去掉"@机器人"前缀,调 BaoDan API
-> 企微 API 回复到群内(所有人可见)
```
---
### 企微 OAuth 登录
**交互流程**
```
1. 用户访问系统 -> 点击"企微登录"
2. 跳转到企微授权页面 -> 用户点击"同意授权"
3. 企微回调后端,携带 code 参数
4. 后端用 code 换取企微用户信息userid、name、department
5. 后端在系统中查找/创建对应用户
6. 生成 JWT Token -> 重定向到系统首页
```
---
## 四、后端接口详细设计A1-A8
### A1认证鉴权
#### A1.1 身份认证接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A1.1.1|POST|/auth/wework-login|接收企微用户信息,签发 JWT Token|高|自研(调 BaoDan||
|A1.1.2|POST|/auth/password-login|账号 + 密码登录,返回 JWT密码 bcrypt 哈希校验|中|自研(调 BaoDan||
|A1.1.3|POST|/auth/refresh-token|刷新过期 Token返回新 JWT|中|自研(调 BaoDan|自研(调 BaoDan|
|A1.1.4|POST|/auth/logout|吊销 Token加入黑名单或清除 Redis Session|中|自研|自研(调 BaoDan|
**A1.1.1 企微登录接口详细设计**
请求:
```json
POST /auth/wework-login
{
"code": "企微授权回调code",
"state": "随机状态值"
}
```
响应:
```json
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200,
"user": {
"id": "user-001",
"username": "张三",
"wecom_userid": "zhangsan",
"role": "sales",
"department": "上海团队"
}
}
}
```
**A1.1.2 账密登录接口详细设计**
请求:
```json
POST /auth/password-login
{
"username": "admin",
"password": "hashed_password"
}
```
响应:
```json
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200
}
}
```
**A1.1.3 刷新 Token 接口详细设计**
请求:
```json
POST /auth/refresh-token
Headers: { "Authorization": "Bearer <old_token>" }
```
响应:
```json
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200
}
}
```
**A1.1.4 退出登录接口详细设计**
请求:
```json
POST /auth/logout
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"message": "success"
}
```
---
### A2智能问答接口
#### A2.1 对话接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A2.1.1|POST|/chat/messageSSE 流式)|接收问题 + 会话 ID调用 RAG 检索 + LLMSSE 流式返回回答与引用来源|高|自研(调 BaoDan||
|A2.1.2|GET|/chat/sessions|获取当前用户的会话列表(分页)|高|自研(调 BaoDan||
|A2.1.3|POST|/chat/sessions|创建新会话,返回 session_id|高|自研(调 BaoDan|自研(调 BaoDan|
|A2.1.4|DELETE|/chat/sessions/{id}|软删除指定会话|中|自研(调 BaoDan|自研(调 BaoDan|
|A2.1.5|GET|/chat/sessions/{id}/messages|获取指定会话完整消息记录(含来源引用)|高|自研(调 BaoDan|自研(调 BaoDan|
|A2.1.6|POST|/chat/messages/{id}/feedback|提交回答评分(有用/无用)或纠错内容|中|自研(调 BaoDan|自研(调 BaoDan|
**A2.1.1 发送消息接口详细设计**
请求:
```json
POST /chat/message
Headers: { "Authorization": "Bearer <token>" }
{
"session_id": "sess-001",
"message": "重疾险的等待期是多少天?",
"filters": {
"险种": "重疾险",
"保司": null
}
}
```
响应SSE 流式):
```
data: {"type": "source", "data": {"doc_name": "XX重疾险条款.pdf", "chunk": "等待期为合同生效之日起90天..."}}
data: {"type": "delta", "data": "根据"}
data: {"type": "delta", "data": "XX重疾险"}
data: {"type": "delta", "data": "条款"}
data: {"type": "delta", "data": "规定"}
data: {"type": "delta", "data": ""}
data: {"type": "delta", "data": "等待期为合同生效之日起90天。"}
data: {"type": "done", "data": {"message_id": "msg-001", "conversation_id": "conv-001"}}
```
**A2.1.2 获取会话列表接口详细设计**
请求:
```json
GET /chat/sessions?page=1&page_size=20
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"data": {
"total": 35,
"items": [
{
"id": "sess-001",
"title": "重疾险等待期问题",
"created_at": "2026-05-31T10:00:00Z",
"updated_at": "2026-05-31T10:05:00Z",
"message_count": 6
}
]
}
}
```
**A2.1.5 获取消息记录接口详细设计**
请求:
```json
GET /chat/sessions/{id}/messages
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"data": {
"messages": [
{
"id": "msg-001",
"role": "user",
"content": "重疾险的等待期是多少天?",
"created_at": "2026-05-31T10:00:00Z"
},
{
"id": "msg-002",
"role": "assistant",
"content": "根据 XX 重疾险条款规定等待期为合同生效之日起90天。",
"sources": [
{"doc_name": "XX重疾险条款.pdf", "chunk": "等待期为合同生效之日起90天..."}
],
"created_at": "2026-05-31T10:00:05Z"
}
]
}
}
```
**A2.1.6 反馈接口详细设计**
请求:
```json
POST /chat/messages/{id}/feedback
Headers: { "Authorization": "Bearer <token>" }
{
"rating": "helpful",
"comment": null
}
```
或纠错:
```json
POST /chat/messages/{id}/feedback
Headers: { "Authorization": "Bearer <token>" }
{
"rating": "not_helpful",
"comment": "等待期应该是180天而不是90天"
}
```
#### A2.2 知识库检索接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A2.2.1|POST|/retrieval/search|向量检索:输入 query + 筛选条件(险种/保司),返回 Top-K 文档片段与相关度|高|自研(调 BaoDan||
|A2.2.2|GET|/retrieval/suggest|基于当前问题生成推荐追问列表2-3 条)|低|自研||
**A2.2.1 检索接口详细设计**
请求:
```json
POST /retrieval/search
Headers: { "Authorization": "Bearer <token>" }
{
"query": "重疾险等待期",
"filters": {
"险种": "重疾险",
"保司": null
},
"top_k": 5
}
```
响应:
```json
{
"code": 0,
"data": {
"results": [
{
"doc_name": "XX重疾险条款.md",
"chunk": "等待期为合同生效之日起90天...",
"score": 0.92,
"metadata": {
"险种": "重疾险",
"保司": "XX人寿"
}
}
]
}
}
```
---
### A3方案生成接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A3.1.1|POST|/proposals/generate|接收客户信息 + 险种,调用 LLM 生成产品推荐方案,返回方案 JSON|高|自研(调 BaoDan||
|A3.1.2|GET|/proposals/generate/{task_id}|轮询生成任务状态pending / processing / done / failed|高|自研||
|A3.1.3|POST|/proposals/{id}/export|导出推荐方案为 PDF/PPT/Word 格式|高|自研||
|A3.1.4|POST|/proposals/{id}/share|生成有时效的分享链接Token 鉴权N 小时有效)|低|自研|自研(调 BaoDan|
**A3.1.1 生成方案接口详细设计**
请求:
```json
POST /proposals/generate
Headers: { "Authorization": "Bearer <token>" }
{
"customer": {
"name": "李四",
"age": 35,
"gender": "male",
"health_status": "健康",
"occupation": "软件工程师",
"annual_income": 300000,
"monthly_budget": 2000
},
"insurance_types": ["重疾险", "医疗险", "意外险"],
"coverage_amount": 500000,
"coverage_period": "终身",
"existing_policies": []
}
```
响应:
```json
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "processing"
}
}
```
**A3.1.2 轮询任务状态接口详细设计**
请求:
```json
GET /proposals/generate/{task_id}
Headers: { "Authorization": "Bearer <token>" }
```
响应(生成中):
```json
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "processing"
}
}
```
响应(完成):
```json
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "done",
"proposal": {
"id": "prop-001",
"plans": [
{
"name": "基础方案",
"total_premium": 4800,
"items": [
{
"product_name": "XX重疾险",
"coverage": 300000,
"premium": 2400,
"reason": "35岁男性投保性价比高覆盖120种重疾"
}
],
"summary": "基础保障方案,覆盖重疾、医疗、意外三大类..."
}
]
}
}
}
```
**A3.1.3 方案导出接口详细设计**
请求:
```json
POST /proposals/{id}/export
Headers: { "Authorization": "Bearer <token>" }
{
"format": "pdf"
}
```
响应:
```json
{
"code": 0,
"data": {
"download_url": "https://your-domain.com/api/proposals/{id}/download?token=xxx",
"expire_at": "2026-06-03T10:00:00Z"
}
}
```
**A3.1.4 分享链接接口详细设计**
请求:
```json
POST /proposals/{id}/share
Headers: { "Authorization": "Bearer <token>" }
{
"expire_hours": 72
}
```
响应:
```json
{
"code": 0,
"data": {
"share_url": "https://your-domain.com/shared/prop-001?token=xxx",
"expire_at": "2026-06-03T10:00:00Z"
}
}
```
---
### A4知识库接口
#### A4.1 文档管理接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A4.1.1|POST|/kb/documents/upload|多文件上传,返回 upload_id触发异步处理流水线格式转换 -> 向量化)|高|自研(调 BaoDan||
|A4.1.2|GET|/kb/documents|文档列表,支持按险种/保司/状态/关键词筛选|高|自研(调 BaoDan||
|A4.1.3|GET|/kb/documents/{id}/status|查询单个文档处理流水线状态|高|自研(调 BaoDan|自研(调 BaoDan|
|A4.1.4|POST|/kb/documents/{id}/retry|手动重试处理失败的文档|高|自研(调 BaoDan|自研(调 BaoDan|
|A4.1.5|PATCH|/kb/documents/{id}|更新文档元数据(编号/分类/标签)|中|自研(调 BaoDan|自研(调 BaoDan|
|A4.1.6|DELETE|/kb/documents/{id}|归档/软删除文档,下线向量索引|中|自研(调 BaoDan|自研(调 BaoDan|
**A4.1.1 上传文档接口详细设计**
请求:
```json
POST /kb/documents/upload
Content-Type: multipart/form-data
Headers: { "Authorization": "Bearer <token>" }
Form Fields:
files: [file1.md, file2.md, file3.md]
险种: "重疾险"
保司: "XX人寿"
```
响应:
```json
{
"code": 0,
"data": {
"upload_id": "upload-001",
"documents": [
{"doc_id": "doc-001", "filename": "file1.md", "status": "processing"},
{"doc_id": "doc-002", "filename": "file2.md", "status": "processing"},
{"doc_id": "doc-003", "filename": "file3.md", "status": "processing"}
]
}
}
```
**A4.1.2 文档列表接口详细设计**
请求:
```json
GET /kb/documents?page=1&page_size=20&险种=重疾险&status=completed&keyword=等待期
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"data": {
"total": 150,
"items": [
{
"doc_id": "doc-001",
"filename": "XX重疾险条款.md",
"编号": "CJ-XX-001",
"险种": "重疾险",
"保司": "XX人寿",
"tags": ["产品条款", "等待期"],
"status": "completed",
"uploaded_at": "2026-05-15T10:00:00Z",
"processed_at": "2026-05-15T10:05:00Z"
}
]
}
}
```
**A4.1.3 文档状态接口详细设计**
请求:
```json
GET /kb/documents/{id}/status
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"data": {
"doc_id": "doc-001",
"filename": "XX重疾险条款.md",
"pipeline": [
{"stage": "upload", "status": "completed", "time": "2026-05-15T10:00:01Z"},
{"stage": "format_convert", "status": "completed", "time": "2026-05-15T10:00:02Z"},
{"stage": "chunking", "status": "completed", "time": "2026-05-15T10:00:03Z", "chunk_count": 45},
{"stage": "embedding", "status": "completed", "time": "2026-05-15T10:05:00Z"}
]
}
}
```
#### A4.2 数据源同步接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A4.2.1|GET|/kb/datasources|获取保司 API 数据源列表及同步状态|高|自研||
|A4.2.2|POST|/kb/datasources|新建数据源配置|高|自研||
|A4.2.3|POST|/kb/datasources/{id}/sync|手动触发指定数据源立即同步|高|自研|自研(调 BaoDan|
|A4.2.4|GET|/kb/datasources/{id}/logs|获取同步历史日志(变更明细)|低|自研|自研(调 BaoDan|
**A4.2.1 数据源列表接口详细设计**
请求:
```json
GET /kb/datasources
Headers: { "Authorization": "Bearer <token>" }
```
响应:
```json
{
"code": 0,
"data": [
{
"id": "ds-001",
"name": "XX人寿产品接口",
"api_url": "https://api.xxlife.com/products",
"sync_frequency": "0 2 * * *",
"last_sync": "2026-05-31T02:00:00Z",
"last_sync_status": "success",
"last_sync_count": 156
}
]
}
```
---
### A5用户权限接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A5.1.1|GET/POST/PUT/DELETE|/admin/users|用户 CRUD停用时吊销所有 Token|高|自研(调 BaoDan||
|A5.1.2|POST|/admin/users/batch-import|解析 Excel 模板,批量创建用户|低|自研||
|A5.1.3|GET/POST/PUT/DELETE|/admin/roles|角色 CRUD 及权限项绑定|高|自研|自研(调 BaoDan|
**A5.1.1 用户管理接口详细设计**
列表:
```json
GET /admin/users?page=1&page_size=20&role=sales&department=上海团队
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"total": 50,
"items": [
{
"id": "user-001",
"username": "张三",
"wecom_userid": "zhangsan",
"role": "sales",
"department": "上海团队",
"status": "active",
"created_at": "2026-05-01T10:00:00Z",
"last_active_at": "2026-05-31T09:30:00Z"
}
]
}
}
```
新增:
```json
POST /admin/users
{
"username": "李四",
"wecom_userid": "lisi",
"role": "sales",
"department": "上海团队"
}
```
**A5.1.3 角色管理接口详细设计**
列表:
```json
GET /admin/roles
```
响应:
```json
{
"code": 0,
"data": [
{
"id": "role-001",
"name": "超级管理员",
"permissions": ["*"],
"builtin": true
},
{
"id": "role-002",
"name": "管理员",
"permissions": ["kb_manage", "log_view", "user_manage", "config_manage"],
"builtin": true
},
{
"id": "role-003",
"name": "销售主管",
"permissions": ["chat", "proposal", "kb_view", "team_stats"],
"builtin": true
},
{
"id": "role-004",
"name": "销售人员",
"permissions": ["chat", "proposal"],
"builtin": true
},
{
"id": "role-005",
"name": "客户",
"permissions": ["chat"],
"builtin": true
}
]
}
```
---
### A6系统配置接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A6.1.1|GET/POST/PUT|/admin/llm-configs|LLM 配置 CRUDKey 写入时服务端加密|高|自研(调 BaoDan||
|A6.1.2|POST|/admin/llm-configs/{id}/ping|测试模型 API 连通性,返回延迟 ms|中|自研(调 BaoDan||
|A6.1.3|GET/POST|/admin/prompts|Prompt 模板 CRUD保存时自动生成版本快照|高|自研(调 BaoDan|自研(调 BaoDan|
|A6.1.4|POST|/admin/prompts/test|用测试问题即时验证 Prompt 效果|中|自研(调 BaoDan|自研(调 BaoDan|
**A6.1.1 LLM 配置接口详细设计**
请求:
```json
POST /admin/llm-configs
Headers: { "Authorization": "Bearer <admin_token>" }
{
"provider": "deepseek",
"model": "deepseek-chat",
"api_key": "sk-xxxxxxxxxxxx",
"base_url": "https://api.deepseek.com/v1",
"is_default": true,
"params": {
"temperature": 0.7,
"max_tokens": 4096,
"top_p": 0.9
}
}
```
响应:
```json
{
"code": 0,
"data": {
"id": "llm-001",
"provider": "deepseek",
"model": "deepseek-chat",
"api_key_masked": "sk-xxxx****xxxx",
"is_default": true,
"status": "active"
}
}
```
**A6.1.2 连通性测试接口详细设计**
请求:
```json
POST /admin/llm-configs/{id}/ping
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"reachable": true,
"latency_ms": 320,
"model": "deepseek-chat"
}
}
```
**A6.1.3 Prompt 管理接口详细设计**
列表:
```json
GET /admin/prompts
```
新增/更新:
```json
POST /admin/prompts
{
"name": "保险顾问系统提示词",
"type": "system",
"content": "你是一名专业的保险顾问助手...",
"variables": ["险种", "保司"]
}
```
响应(保存时自动生成版本快照):
```json
{
"code": 0,
"data": {
"id": "prompt-001",
"name": "保险顾问系统提示词",
"version": 3,
"versions": [
{"version": 1, "created_at": "2026-05-01T10:00:00Z"},
{"version": 2, "created_at": "2026-05-15T10:00:00Z"},
{"version": 3, "created_at": "2026-05-31T10:00:00Z"}
]
}
}
```
**A6.1.4 Prompt 测试接口详细设计**
请求:
```json
POST /admin/prompts/test
{
"prompt_id": "prompt-001",
"test_query": "重疾险等待期多少天?"
}
```
响应:
```json
{
"code": 0,
"data": {
"response": "根据条款规定重疾险等待期为90天...",
"latency_ms": 1200,
"token_usage": {"prompt_tokens": 520, "completion_tokens": 180}
}
}
```
---
### A7留痕日志接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A7.1.1|GET|/admin/logs/chat|问答记录查询(分页 + 多维筛选 + 关键词),支持导出 CSV|高|自研(调 BaoDan||
|A7.1.2|GET|/admin/logs/chat/export|导出对话记录为 CSV 文件流|高|自研(调 BaoDan||
|A7.1.3|GET|/admin/logs/system|系统操作日志查询|中|自研||
**A7.1.1 问答日志接口详细设计**
请求:
```json
GET /admin/logs/chat?page=1&page_size=50&user_id=user-001&start_date=2026-05-01&end_date=2026-05-31&keyword=重疾险&rating=not_helpful&export=csv
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应JSON 模式):
```json
{
"code": 0,
"data": {
"total": 1200,
"items": [
{
"id": "msg-001",
"user_id": "user-001",
"username": "张三",
"session_id": "sess-001",
"question": "重疾险等待期多少天?",
"answer": "根据 XX 重疾险条款规定等待期为合同生效之日起90天。",
"sources": ["XX重疾险条款.md"],
"rating": "helpful",
"created_at": "2026-05-31T10:00:00Z"
}
]
}
}
```
导出CSV 模式):
```
当 export=csv 时,返回 CSV 文件流
Content-Type: text/csv
Content-Disposition: attachment; filename="chat_logs_202605.csv"
```
**A7.1.2 导出对话记录接口详细设计**
请求:同 A7.1.1export=csv 时返回 CSV 文件流
**A7.1.3 系统日志接口详细设计**
请求:
```json
GET /admin/logs/system?page=1&page_size=50&action=login&user_id=user-001
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"total": 200,
"items": [
{
"id": "log-001",
"action": "login",
"user_id": "user-001",
"username": "张三",
"ip": "192.168.1.100",
"device": "Chrome/120 Windows",
"detail": "企微 OAuth 登录",
"created_at": "2026-05-31T09:00:00Z"
}
]
}
}
```
---
### A8统计报表接口
|接口编号|方法|URL|说明|优先级||实现方式|
|---------|------|-----|------|:---:|--------|--------|
|A8.1.1|GET|/stats/overview|使用概览:今日问答数、活跃用户数、知识库命中率|高|自研||
|A8.1.2|GET|/stats/trend|指定指标的日趋势数据(折线图数据源)|中|自研||
|A8.1.3|GET|/stats/kb-health|知识库健康度:热门问题/未命中列表/文档覆盖率|中|自研||
|A8.1.4|GET|/stats/token-cost|Token 消耗统计(按模型/用途/时间段),含费用估算|中|自研|自研(调 BaoDan|
**A8.1.1 使用概览接口详细设计**
请求:
```json
GET /stats/overview
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"today_chats": 45,
"active_users": 12,
"kb_hit_rate": 0.87,
"total_documents": 1500
}
}
```
**A8.1.2 趋势数据接口详细设计**
请求:
```json
GET /stats/trend?metric=chat_count&start_date=2026-05-01&end_date=2026-05-31&granularity=day
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"metric": "chat_count",
"granularity": "day",
"data": [
{"date": "2026-05-01", "value": 45},
{"date": "2026-05-02", "value": 62},
{"date": "2026-05-03", "value": 38}
]
}
}
```
**A8.1.3 知识库健康度接口详细设计**
请求:
```json
GET /stats/kb-health?days=30
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"hot_questions": [
{"question": "重疾险等待期多少天", "count": 230},
{"question": "车险理赔流程", "count": 180}
],
"missed_questions": [
{"question": "XX新产品费率", "count": 45, "handled": false},
{"question": "跨境保险政策", "count": 30, "handled": true}
],
"coverage": {
"寿险": {"doc_count": 320, "status": "completed", "last_update": "2026-05-20"},
"重疾险": {"doc_count": 280, "status": "completed", "last_update": "2026-05-18"},
"医疗险": {"doc_count": 150, "status": "completed", "last_update": "2026-05-15"}
}
}
}
```
**A8.1.4 Token 消耗接口详细设计**
请求:
```json
GET /stats/token-cost?start_date=2026-05-01&end_date=2026-05-31&group_by=model
Headers: { "Authorization": "Bearer <admin_token>" }
```
响应:
```json
{
"code": 0,
"data": {
"total_tokens": 2500000,
"total_cost_usd": 12.5,
"by_model": [
{
"model": "deepseek-chat",
"input_tokens": 1500000,
"output_tokens": 500000,
"cost_usd": 10.0
},
{
"model": "deepseek-embedding",
"input_tokens": 500000,
"output_tokens": 0,
"cost_usd": 2.5
}
],
"daily_trend": [
{"date": "2026-05-01", "tokens": 80000, "cost_usd": 0.4},
{"date": "2026-05-02", "tokens": 95000, "cost_usd": 0.475}
]
}
}
```
---
## 五、非功能性需求
### 5.1 性能要求
|指标|要求|说明|
|------|------|------|
|AI 问答响应时间|< 15 |从发送到开始看到回复Streaming 首字|
|产品推荐生成时间|< 60 |从提交表单到方案展示|
|页面首屏加载|< 3 |首次访问|
|同时在线用户|>= 20 人|单服务器承载|
|知识库容量|9GB / 50 万 chunk|当前规模|
### 5.2 可用性要求
|指标|要求|说明|
|------|------|------|
|系统可用率|>= 99%|工作时间 9:00-21:00|
|数据备份|每日自动|PostgreSQL + 知识库数据|
|故障恢复|< 30 分钟|重启服务后自动恢复|
|企微消息不丢失||5 秒内返回"处理中"异步生成回答|
### 5.3 安全要求
|项目|要求|说明|
|------|------|------|
|传输加密|HTTPS|全站 HTTPS企微要求|
|API Key 存储|AES 加密|DeepSeek Key 等不得明文存储|
|企微消息验证|签名校验|回调 URL 必须验证企微签名|
|XSS 防护|输入过滤 + 输出编码|所有用户输入经过转义|
|SQL 注入防护|参数化查询|使用 ORM 或参数化查询|
|密码策略|bcrypt 哈希|密码不可明文存储|
|Token 有效期|JWT + 刷新机制|Access Token 2 小时过期|
### 5.4 兼容性要求
|平台|浏览器|最低版本|
|------|--------|---------|
|PC|Chrome / Edge / Firefox|90+|
|手机|微信/企微内置浏览器|iOS 14+ / Android 10+|
|手机|手机 Chrome|90+|
### 5.5 部署环境
|项目|要求||
|------|------|--------|
|服务器|自有服务器Linux推荐 Ubuntu 22.04||
|CPU|4 +||
|内存|8 GB+||
|磁盘|100 GB+ SSD||
|网络|公网 IP + 域名 + HTTPS 证书||
|运行时|Python 3.12 + Node.js 22 + PostgreSQL 15 + Redis 6||
---
## 六、数据库设计
### 6.1 数据存储说明
|数据类型|存储位置|说明|
|---------|---------|------|
|BaoDan 核心数据用户对话知识库|BaoDan PostgreSQL|BaoDan 自己管理不直接操作|
|企微用户映射|你的 PostgreSQL|企微 userid BaoDan 用户的对应关系|
|推荐方案记录|你的 PostgreSQL|产品推荐的历史记录|
|BaoDan 服务配置|BaoDan PostgreSQL|模型配置Prompt工作流等|
### 6.2 自研表结构
#### 表 1wecom_user_mapping企微用户映射表
**用途**存储企微用户 ID BaoDan 用户 ID 的对应关系企微 OAuth 登录时查找或创建用户
|字段|类型|约束|说明|
|------|------|------|------|
|id|SERIAL|PRIMARY KEY|自增主键|
|wecom_userid|VARCHAR(64)|NOT NULL, UNIQUE|企微用户 ID|
|baodan_user_id|VARCHAR(64)|NOT NULL|BaoDan 内部用户 ID|
|username|VARCHAR(128)||用户姓名|
|department|VARCHAR(128)||所属部门|
|role|VARCHAR(32)|DEFAULT 'sales'|角色super_admin/admin/manager/sales|
|status|VARCHAR(16)|DEFAULT 'active'|状态active/disabled|
|created_at|TIMESTAMP|DEFAULT NOW()|创建时间|
|last_active_at|TIMESTAMP||最后活跃时间|
**索引**
- UNIQUE INDEX ON wecom_userid
- INDEX ON baodan_user_id
#### 表 2recommendation_records推荐方案记录表
**用途**记录每次产品推荐的输入参数和生成结果用于历史查询和方案对比
|字段|类型|约束|说明|
|------|------|------|------|
|id|SERIAL|PRIMARY KEY|自增主键|
|user_id|VARCHAR(64)|NOT NULL, FOREIGN KEY|创建人关联 wecom_user_mapping|
|customer_name|VARCHAR(64)||客户姓名|
|customer_age|SMALLINT||客户年龄|
|customer_gender|VARCHAR(8)||性别male/female|
|health_status|VARCHAR(32)||健康状况|
|occupation|VARCHAR(64)||职业|
|annual_income|INTEGER||年收入|
|monthly_budget|INTEGER||月预算|
|insurance_types|TEXT||关注险种JSON 数组 ["重疾险","医疗险"]|
|coverage_amount|INTEGER||保额目标|
|coverage_period|VARCHAR(32)||保障期限|
|existing_policies|TEXT||已有保单JSON可为空|
|generated_plan|TEXT||生成的方案内容Markdown 文本|
|plan_variants|TEXT||多套方案JSON基础/均衡/全面|
|baodan_task_id|VARCHAR(64)||BaoDan Workflow 任务 ID|
|status|VARCHAR(16)|DEFAULT 'pending'|状态pending/processing/done/failed|
|error_message|TEXT||失败原因可为空|
|created_at|TIMESTAMP|DEFAULT NOW()|创建时间|
|completed_at|TIMESTAMP||生成完成时间|
**索引**
- INDEX ON user_id
- INDEX ON status
- INDEX ON created_at
#### 表 3system_operation_logs系统操作日志表
**用途**记录关键系统操作登录知识库变更配置修改等用于审计
|字段|类型|约束|说明|
|------|------|------|------|
|id|SERIAL|PRIMARY KEY|自增主键|
|user_id|VARCHAR(64)|NOT NULL|操作人|
|action|VARCHAR(32)|NOT NULL|操作类型login/logout/upload/delete/config_change|
|target_type|VARCHAR(32)||操作对象类型document/user/prompt/llm_config|
|target_id|VARCHAR(64)||操作对象 ID|
|detail|JSONB||操作详情 {"old_value": "...", "new_value": "..."}|
|ip|VARCHAR(45)||操作 IP|
|user_agent|VARCHAR(256)||设备信息|
|created_at|TIMESTAMP|DEFAULT NOW()|操作时间|
**索引**
- INDEX ON user_id
- INDEX ON action
- INDEX ON created_at
---
## 七、页面结构与导航
### 7.1 页面清单
|页面|路径|说明|权限|
|------|------|------|------|
|登录页|/login|企微 OAuth + 账密登录|所有人|
|对话页|/chat|智能问答主界面BaoDan WebApp|销售/主管/管理员|
|产品推荐页|/recommend|填写客户需求 + 查看生成结果|销售/主管/管理员|
|方案历史页|/recommend/history|历史推荐方案列表|销售/主管/管理员|
|管理后台首页|/admin|管理后台入口|管理员/超级管理员|
|知识库管理|/admin/knowledge-base|文档上传/列表/状态监控|管理员|
|对话日志|/admin/logs/chat|问答记录查询/导出|管理员|
|系统日志|/admin/logs/system|操作日志查询|超级管理员|
|用户管理|/admin/users|用户 CRUD|超级管理员|
|角色管理|/admin/roles|角色权限配置|超级管理员|
|LLM 配置|/admin/llm-configs|模型 API Key 管理|超级管理员|
|Prompt 管理|/admin/prompts|提示词编辑/版本管理|管理员|
|数据统计|/admin/stats|趋势图/健康度/成本|管理员|
### 7.2 导航结构
```
[登录页]
+-- [用户端]
| |-- 左侧导航栏
| | |-- 智能问答 -> /chat
| | |-- 产品推荐 -> /recommend
| | |-- 历史方案 -> /recommend/history
| | +-- (销售人员只能看到这三项)
||
| +-- 右上角:用户信息 + 退出登录
+-- [管理端](仅管理员可见)
|-- 左侧导航栏
| |-- 知识库管理 -> /admin/knowledge-base
| |-- 对话日志 -> /admin/logs/chat
| |-- 系统日志 -> /admin/logs/system仅超级管理员
| |-- 用户管理 -> /admin/users仅超级管理员
| |-- 角色管理 -> /admin/roles仅超级管理员
| |-- LLM 配置 -> /admin/llm-configs仅超级管理员
| |-- Prompt 管理 -> /admin/prompts
| +-- 数据统计 -> /admin/stats
+-- 顶部:管理后台标题 + 切换到用户端
```
### 7.3 页面交互细节
#### 对话页(/chat
```
+---------------------------------------------------+
|[新建对话] [会话列表侧边栏]|
||
|+-----------------------------------------------+|
||用户: 重疾险等待期多少天?||
|+-----------------------------------------------+|||
||
|+-----------------------------------------------+|
||AI: 根据 XX 重疾险条款规定...||
||[来源: XX重疾险条款.md]||
||[点赞] [点踩] [复制]||
|+-----------------------------------------------+|||
||
|+-----------------------------------------------+|
||[险种筛选: 全部 v] [保司筛选: 全部 v]||
||||
||输入框 [发送]||
|+-----------------------------------------------+|||
+---------------------------------------------------+
```
#### 产品推荐页(/recommend
```
+---------------------------------------------------+
|产品推荐 - 新建方案|
||
|[客户信息]|
|姓名: [____] 年龄: [____] 性别: (o)男 ( )女|
|健康状况: [健康 v] 职业: [__________]|
|年收入: [____]万 月预算: [____]元|
||
|[关注险种] [x]重疾险 [x]医疗险 [ ]意外险 ...|
||
|[保障需求]|
|保额目标: [====o========] 50万|
|保障期限: [终身 v]|
||
|[生成方案]|
||
|+-----------------------------------------------+|
||方案预览区||
||=== 基础方案 (年保费: 4,800元) ===||
||1. XX重疾险 - 保额30万 - 年保费2,400元||
||推荐理由: ...||
||2. XX医疗险 - 年保费1,200元||
||...||
||||
||[浏览器打印] [重新生成]||
|+-----------------------------------------------+|||
+---------------------------------------------------+
```
---
## 八、企微机器人详细规格
### 8.1 接入配置
|配置项|说明|在哪里获取|
|--------|------|-----------|
|CorpID|企业 ID|企微管理后台 -> 我的企业|
|AgentID|应用 ID|企微管理后台 -> 应用管理 -> 创建应用|
|AgentSecret|应用密钥|应用详情页|
|Token|回调消息验证 Token|应用 -> 接收消息 -> 设置 API 接收(自行生成)|
|EncodingAESKey|消息加密密钥|同上(随机生成 43 位字符串)|
### 8.2 回调 URL 验证GET
企微在配置回调 URL 时会发送 GET 请求验证:
```
GET /api/wecom/callback?msg_signature=xxx&timestamp=xxx&nonce=xxx&echostr=xxx
```
验证流程:
1. 将 Token、timestamp、nonce、echostr 按字典序排列拼接
2. 对拼接字符串做 SHA1 哈希
3. 比较哈希结果与 msg_signature
4. 验证通过则返回解密后的 echostr
### 8.3 消息接收POST
企微用户发消息时推送的加密 XML
```xml
<xml>
<ToUserName><![CDATA[你的企业ID]]></ToUserName>
<Encrypt><![CDATA[加密后的消息]]></Encrypt>
</xml>
```
解密后的消息体text 类型):
```xml
<xml>
<ToUserName><![CDATA[你的企业ID]]></ToUserName>
<FromUserName><![CDATA[用户UserID]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[重疾险等待期多少天]]></Content>
<MsgId>1234567890123456</MsgId>
<AgentID>1000002</AgentID>
</xml>
```
群聊消息额外字段:
```xml
<xml>
...
<ChatId><![CDATA[群聊ID]]></ChatId>
<Content><![CDATA[@机器人 重疾险等待期多少天]]></Content>
...
</xml>
```
### 8.4 消息回复
通过企微 API 发送文本消息:
```
POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
```
请求体:
```json
{
"touser": "用户UserID",
"msgtype": "text",
"agentid": 1000002,
"text": {
"content": "根据 XX 重疾险条款规定等待期为90天..."
}
}
```
群聊回复到群:
```
POST https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN
```
请求体:
```json
{
"chatid": "群聊ID",
"msgtype": "text",
"text": {
"content": "根据 XX 重疾险条款规定等待期为90天..."
}
}
```
### 8.5 消息长度限制
|场景|限制|处理方式|
|------|------|---------|
|单条文本消息|最大 2048 字节|超过时分段发送,每段间隔 500ms|
|Access Token|有效期 2 小时|代码内缓存,提前 5 分钟刷新|
|回调响应时间|5 秒内|先返回"success",异步处理消息和回复|
### 8.6 非文本消息处理
|消息类型|处理方式||
|---------|---------|--------|
|图片 (image)|回复"暂不支持图片识别,请用文字描述您的问题"||
|语音 (voice)|回复"暂不支持语音识别,请用文字描述您的问题"||
|视频 (video)|回复"暂不支持视频消息"||
|文件 (file)|回复"暂不支持文件消息"||
|位置 (location)|忽略不回复||
|事件 (event)|忽略不回复(如成员加入/退出事件)||
---
## 九、BaoDan 集成规范
> **架构变更**:自研代码直接运行在 BaoDan 进程内Flask Blueprint调用 BaoDan 的 RAG 引擎和 LLM 服务时**直接 import Python 函数**,不走 HTTP API。企微回调 URL 直接指向 BaoDan 服务端口。
### 9.1 后端调用方式
```
你的 insurance 模块Flask Blueprint
|
|-- 直接 import BaoDan 的 RAG 引擎
| from core.rag.retrieval.dataset_retrieval import DatasetRetrieval
| from core.rag.embedding.model import ModelEmbedding
|
|-- 直接 import BaoDan 的 LLM 服务
| from core.model_runtime.model_manager import ModelManager
|
|-- 直接调用 BaoDan 的 Workflow
| from core.workflow.workflow_engine import WorkflowEngine
|
|-- 复用 BaoDan 的数据库连接
| from extensions.ext_database import db
| from models.dataset import Dataset, Document
|
|-- 复用 BaoDan 的认证
| from extensions.ext_login import login
| from models.account import Account
```
| Headers: Authorization: Bearer <BAODAN_APP_API_KEY>
|-- 调用 BaoDan Workflow API产品推荐
| POST http://baodan-api:5001/v1/workflows/run
| Headers: Authorization: Bearer <BAODAN_WORKFLOW_API_KEY>
|-- 调用 BaoDan Knowledge API文档管理可选
| POST http://baodan-api:5001/v1/datasets/{id}/documents
| Headers: Authorization: Bearer <BAODAN_DATASET_API_KEY>
```
### 9.2 BaoDan Chat API 调用规范
**请求**
```json
POST /v1/chat-messages
Headers:
Authorization: Bearer app-xxxxxxxxxxxx
Content-Type: application/json
{
"inputs": {},
"query": "重疾险等待期多少天?",
"response_mode": "blocking",
"user": "wecom_zhangsan",
"conversation_id": "",
"files": []
}
```
|参数|类型|说明|
|------|------|------|
|inputs|object|自定义变量,与 Prompt 中的占位符对应|
|query|string|用户的问题文本|
|response_mode|string|blocking等完整回答或 streaming流式返回|
|user|string|用户标识,企微场景用 "wecom_{userid}"|
|conversation_id|string|空字符串 = 新对话;非空 = 继续已有对话|
|files|array|附件列表(暂不使用)|
**blocking 模式响应**
```json
{
"event": "message",
"message_id": "msg-xxx",
"conversation_id": "conv-xxx",
"mode": "blocking",
"answer": "根据 XX 重疾险条款规定等待期为合同生效之日起90天。",
"metadata": {
"usage": {
"prompt_tokens": 520,
"completion_tokens": 180,
"total_tokens": 700
},
"retriever_resources": [
{
"dataset_name": "重疾险-产品条款",
"document_name": "XX重疾险条款.md",
"segment_content": "等待期为合同生效之日起90天...",
"score": 0.92
}
]
},
"created_at": 1700000000
}
```
### 9.3 BaoDan Workflow API 调用规范
**请求**
```json
POST /v1/workflows/run
Headers:
Authorization: Bearer app-yyyyyyyyyyyy
Content-Type: application/json
{
"inputs": {
"age": "35",
"gender": "male",
"occupation": "软件工程师",
"annual_income": "300000",
"monthly_budget": "2000",
"insurance_types": "重疾险,医疗险,意外险",
"coverage_amount": "500000",
"coverage_period": "终身"
},
"response_mode": "blocking",
"user": "wecom_zhangsan"
}
```
**响应**
```json
{
"workflow_run_id": "wf-xxx",
"task_id": "task-xxx",
"data": {
"outputs": {
"recommendation": "## 基础方案\n\n..."
},
"status": "succeeded",
"elapsed_time": 15.2,
"total_tokens": 2000
}
}
```
### 9.4 用户标识映射
|场景|BaoDan user 格式|说明|
|------|---------------|------|
|企微单聊|`wecom_{userid}`|如 `wecom_zhangsan`|
|企微群聊|`wecom_{userid}`|同上,按发送人区分|
|网页登录|`web_{baodan_user_id}`|BaoDan 内部用户 ID|
### 9.5 对话上下文管理
|场景|conversation_id|说明|
|------|----------------|------|
|新对话|空字符串 `""`|BaoDan 返回新的 conversation_id|
|继续对话|传入上次返回的 conversation_id|BaoDan 自动携带历史上下文|
|切换会话|传入目标会话的 conversation_id|BaoDan 加载该会话的上下文|
|新建会话|传入空字符串|开始全新的对话|
### 9.6 BaoDan WebApp 嵌入方式
对话页面直接嵌入 BaoDan 的 WebApp通过 iframe 方式:
```html
<!-- 方式一:直接嵌入 BaoDan WebApp -->
<iframe
src="http://你的BAODAN地址/chat/{app_id}?user=用户ID"
style="width:100%; height:100%; border:none;"
allow="microphone">
</iframe>
```
**BaoDan WebApp URL 格式**
- 对话应用:`{baodan_base_url}/chat/{app_id}`
- 工作流应用:`{baodan_base_url}/chat/{app_id}`
**iframe 通信**BaoDan WebApp 通过 `postMessage` 与父页面通信(如需要定制交互,需要监听 message 事件)。
---
## 十、异常与边界处理
### 10.1 智能问答异常处理
|异常场景|系统行为|用户看到什么|
|---------|---------|-------------|
|BaoDan API 不可达|后端返回 500|"服务暂时不可用,请稍后重试"|
|BaoDan API 超时(>60秒|中断请求|"回答生成超时,请重试"|
|DeepSeek API Key 过期|BaoDan 返回错误|"AI 服务配置异常,请联系管理员"|
|知识库无匹配结果|BaoDan 正常返回|AI 回复"当前知识库中未找到相关内容"|
|用户输入为空|前端拦截|发送按钮禁用|
|用户输入超长(>10000字|前端拦截|提示"输入内容过长,请缩短后重试"|
|BaoDan 返回空回答|后端兜底|"抱歉,暂时无法回答您的问题,请换个方式提问"|
|流式输出中断|前端显示已收到部分|"回答中断,请重新提问"|
### 10.2 产品推荐异常处理
|异常场景|系统行为|用户看到什么|
|---------|---------|-------------|
|必填项未填|前端拦截|对应字段下方红色提示"请填写XX"|
|无效输入(年龄=-1|前端校验|"请输入有效的年龄1-150"|
|Workflow 执行失败|记录错误,更新状态|"方案生成失败,请重试"|
|Workflow 执行超时(>120秒|任务标记为 failed|"方案生成超时,请重试"|
|生成结果为空|BaoDan Workflow 内处理|"未找到匹配的保险产品,请调整筛选条件"|
|网络中断|任务可能仍在后端执行|"网络异常,方案正在生成中,请稍后查看"|
### 10.3 企微机器人异常处理
|异常场景|系统行为|用户看到什么|
|---------|---------|-------------|
|消息解密失败|记录日志,返回 success|不回复(避免企微重试)|
|用户不在白名单|根据配置决定|"您暂无使用权限"或忽略|
|BaoDan 处理中超过 5 秒|已返回 success|先不回复,异步处理完后回复|
|企微 API 回复失败|记录日志|无(用户看不到)|
|Access Token 获取失败|重试 3 次|机器人不回复|
|消息内容包含敏感词|正常处理|不做额外过滤(合规由管理员负责)|
### 10.4 企微 OAuth 异常处理
|异常场景|系统行为|用户看到什么|
|---------|---------|-------------|
|用户拒绝授权|跳转回登录页|"授权已取消,请重新登录"|
|code 已过期5分钟有效期|企微返回错误|"授权已过期,请重新登录"|
|企微用户未在系统中注册|根据配置决定|"您的账号暂未开通,请联系管理员"|
|JWT Token 过期|前端检测 401|跳转到登录页|
|刷新 Token 失败|清除本地 Token|跳转到登录页|
### 10.5 网络与基础设施异常
|异常场景|系统行为|恢复方式|
|---------|---------|---------|
|PostgreSQL 连接断开|后端报错日志|数据库恢复后自动重连|
|Redis 连接断开|Celery 暂停|Redis 恢复后自动恢复|
|磁盘空间满|知识库上传失败,其他功能正常|清理磁盘后手动重试|
|服务器重启|所有服务停止|Docker 自动重启 / 手动启动|
|BaoDan 服务重启|所有对话断开|BaoDan 自动恢复,用户刷新页面即可|
---
## 十一、术语表
### 11.1 保险领域术语
|术语|英文|说明|
|------|------|------|
|重疾险|Critical Illness Insurance|确诊约定重大疾病后一次性赔付的保险|
|寿险|Life Insurance|身故后向受益人赔付的保险|
|医疗险|Medical Insurance|报销医疗费用的保险|
|意外险|Accident Insurance|因意外事故导致伤害/身故时赔付的保险|
|年金险|Annuity Insurance|按约定周期领取保险金的保险|
|储蓄险|Savings Insurance|兼具保障和储蓄功能的保险|
|等待期|Waiting Period|保单生效后到保障开始的等待天数(通常 90-180 天)|
|免赔额|Deductible|保险公司不赔付的部分(如医疗险的 1 万免赔额)|
|保额|Sum Insured|保险赔付的最高金额|
|保费|Premium|被保险人需缴纳的保险费用|
|核保|Underwriting|保险公司评估是否承保及承保条件的过程|
|体况|Health Status|投保人的身体健康状况|
|条款|Policy Terms|保险合同的详细约定内容|
|理赔|Claim|向保险公司申请赔付的过程|
### 11.2 技术术语
|术语|说明||
|------|------|--------|
|BaoDan|开源 AI 应用开发平台,提供 RAG、对话管理、工作流等功能||
|RAG|检索增强生成Retrieval-Augmented Generation先从知识库检索相关文档再让 LLM 基于检索结果生成回答||
|LLM|大语言模型Large Language Model如 DeepSeek、GPT-4o||
|Embedding|向量嵌入,将文本转换为数学向量以支持语义搜索||
|Chunk|文档被切分成的小段落,是知识库检索的最小单位||
|Workflow|BaoDan 的可视化工作流编辑器,可拖拽编排多步骤 AI 流程||
|SSE|Server-Sent Events流式传输协议用于逐字显示 AI 回答||
|JWT|JSON Web Token用于用户认证的令牌机制||
|PTY|伪终端(本项目不涉及,仅 Dinotty 相关)||
|WebApp|BaoDan 提供的内嵌对话界面,可通过 iframe 嵌入你的系统||
---
## 十二、数据流转图
> 以下用文本/伪代码形式展示系统核心业务的完整数据流转链路,每条链路标注了每一步的 API 调用、数据库读写、外部服务调用、返回数据和错误处理。
### 12.1 用户提问 → AI 回答(完整链路)
```
用户(浏览器/企微)
[步骤1] 前端发起请求
│ POST /api/chat/message
│ Headers: Authorization: Bearer <JWT>
│ Body: {"session_id":"sess-001", "message":"重疾险等待期多少天?", "filters":{"险种":"重疾险"}}
[步骤2] 后端 - JWT 鉴权
│ ├─ 读取 Redis: GET token:blacklist:<token> → 检查是否被吊销
│ ├─ 解析 JWT → 提取 user_id、role
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE baodan_user_id = ?
│ │
│ ├─ [成功] → 继续步骤3
│ └─ [失败] → 返回 {"code":1002, "message":"未授权,请重新登录"}
[步骤3] 后端 - 调用 BaoDan Chat APIblocking 模式)
│ POST http://baodan-api:5001/v1/chat-messages
│ Headers: Authorization: Bearer app-xxxxxxxxxxxx
│ Body: {
│ "inputs": {"险种": "重疾险", "保司": ""},
│ "query": "重疾险等待期多少天?",
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan",
│ "conversation_id": "sess-001",
│ "files": []
│ }
│ ├─ [成功] → 继续步骤4
│ ├─ [超时 >60s] → 返回 {"code":2002, "message":"AI 回答生成超时,请重试"}
│ └─ [BaoDan 返回错误] → 返回 {"code":2001, "message":"AI 服务暂时不可用,请稍后重试"}
[步骤4] BaoDan 内部处理(无需后端干预)
│ ├─ BaoDan 接收 query
│ ├─ Embedding: 将 query 向量化
│ ├─ 知识库检索: Weaviate 向量相似度搜索 → 返回 Top-K 文档片段
│ ├─ Prompt 组装: System Prompt + 检索结果 + 用户 query
│ ├─ LLM 调用: DeepSeek API → 流式生成回答
│ └─ 返回完整回答 + metadata来源引用
[步骤5] 后端 - 处理 BaoDan 响应并存储
│ ├─ 提取 answer、conversation_id、retriever_resources
│ ├─ 写入 PostgreSQL可选: 记录问答日志
│ └─ 组装返回数据
[步骤6] 后端 → 前端 响应
│ 返回 SSE 流式数据:
│ data: {"type":"source", "data":{"doc_name":"XX重疾险条款.md", "chunk":"等待期为90天..."}}
│ data: {"type":"delta", "data":"根据"}
│ data: {"type":"delta", "data":"XX重疾险条款规定..."}
│ ...
│ data: {"type":"done", "data":{"message_id":"msg-001", "conversation_id":"conv-001"}}
[步骤7] 前端渲染
├─ 逐字显示 AI 回答
├─ 展示来源引用(可展开查看原文)
├─ 显示 [点赞] [点踩] [复制] 按钮
└─ 完成
```
### 12.2 产品推荐(完整链路)
```
用户(浏览器)
[步骤1] 前端表单提交
│ POST /api/proposals/generate
│ Headers: Authorization: Bearer <JWT>
│ Body: {
│ "customer": {"name":"李四", "age":35, "gender":"male", "health_status":"健康",
│ "occupation":"软件工程师", "annual_income":300000, "monthly_budget":2000},
│ "insurance_types": ["重疾险","医疗险","意外险"],
│ "coverage_amount": 500000,
│ "coverage_period": "终身",
│ "existing_policies": []
│ }
[步骤2] 后端 - JWT 鉴权
│ 同 12.1 步骤2
[步骤3] 后端 - 参数校验
│ ├─ 必填字段检查age/gender/occupation/insurance_types/monthly_budget/coverage_amount
│ ├─ 类型检查age 为整数 1-150gender 为 "male"/"female"
│ ├─ 业务规则insurance_types 至少选1项monthly_budget > 0
│ │
│ ├─ [校验通过] → 继续步骤4
│ └─ [校验失败] → 返回 {"code":1001, "message":"参数错误年龄必须为1-150之间的整数"}
[步骤4] 后端 - 创建推荐记录
│ ├─ 写入 PostgreSQL: INSERT INTO recommendation_records (user_id, customer_name, ..., status='pending')
│ └─ 返回 task_id
[步骤5] 后端 - 异步调用 BaoDan Workflow API
│ POST http://baodan-api:5001/v1/workflows/run
│ Headers: Authorization: Bearer app-yyyyyyyyyyyy
│ Body: {
│ "inputs": {
│ "age": "35", "gender": "male", "occupation": "软件工程师",
│ "annual_income": "300000", "monthly_budget": "2000",
│ "insurance_types": "重疾险,医疗险,意外险",
│ "coverage_amount": "500000", "coverage_period": "终身"
│ },
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan"
│ }
│ ├─ [成功] → 继续步骤6
│ ├─ [超时 >120s] → 更新 PostgreSQL: UPDATE recommendation_records SET status='failed' → 返回 {"code":2004}
│ └─ [Workflow 失败] → 更新 PostgreSQL: SET status='failed', error_message=... → 返回 {"code":2003}
[步骤6] BaoDan Workflow 内部处理
│ ├─ 节点1: 参数校验Code Node
│ ├─ 节点2: 检索策略生成Code Node→ 为每个险种生成检索 query
│ ├─ 节点3: 知识库检索Knowledge Retrieval Node→ Weaviate 检索 Top-5
│ ├─ 节点4: LLM 方案生成LLM Node→ DeepSeek 生成三套方案
│ ├─ 节点5: 格式化输出Code Node→ Markdown 清理
│ └─ 返回 outputs.recommendationMarkdown 文本)
[步骤7] 后端 - 保存结果
│ ├─ 写入 PostgreSQL: UPDATE recommendation_records SET
│ │ generated_plan=?, status='done', completed_at=NOW()
│ └─ 返回完整方案数据
[步骤8] 后端 → 前端 响应
│ 返回 {"code":0, "data":{"task_id":"task-001", "status":"done",
│ "proposal":{"id":"prop-001", "plans":[...]}}}
[步骤9] 前端轮询如步骤5返回 processing
│ GET /api/proposals/generate/{task_id}
│ ├─ [status=processing] → 继续轮询(间隔 2 秒)
│ ├─ [status=done] → 展示方案
│ └─ [status=failed] → 显示错误提示
[步骤10] 前端展示方案
├─ 渲染三套方案(基础/均衡/全面)
├─ 每套方案包含:产品表格 + 总保费 + 推荐理由
├─ 显示 [浏览器打印] [重新生成] 按钮
└─ 完成
```
### 12.3 企微机器人问答(完整链路)
```
企微用户(手机/电脑)
[步骤1] 用户发送消息
│ 用户在企微中 @机器人 或私聊发送:"重疾险等待期多少天?"
[步骤2] 企微服务器 → 你的后端 推送加密消息
│ POST https://your-domain.com/api/wecom/callback
│ Headers: msg_signature=xxx&timestamp=xxx&nonce=xxx
│ Body: <xml><ToUserName>...</ToUserName><Encrypt>加密消息</Encrypt></xml>
[步骤3] 后端 - 立即返回 "success"5秒内必须响应
│ 返回空字符串 "success"
企微要求5秒内响应否则会重试推送
[步骤4] 后端 - 异步处理消息
│ ├─ 验证签名: SHA1(sort([Token, timestamp, nonce])) == msg_signature
│ │ ├─ [验证失败] → 记录日志,流程结束(不回复)
│ │ └─ [验证通过] → 继续
│ ├─ 解密消息: AES-CBC 解密 Encrypt 字段 → 得到明文 XML
│ ├─ 解析 XML: 提取 FromUserName(用户ID)、Content(消息内容)、MsgType
│ │
│ ├─ [MsgType != text] → 回复"暂不支持该消息类型,请用文字描述" → 流程结束
│ ├─ [用户不在白名单] → 回复"您暂无使用权限" → 流程结束
│ └─ [正常文本消息] → 继续步骤5
[步骤5] 后端 - 查询/创建用户映射
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE wecom_userid = ?
│ │
│ ├─ [用户不存在 + 系统允许注册] → INSERT 新记录 → 继续步骤6
│ ├─ [用户不存在 + 系统不允许] → 回复"您的账号暂未开通" → 流程结束
│ └─ [用户存在] → 继续步骤6
[步骤6] 后端 - 调用 BaoDan Chat API
│ POST http://baodan-api:5001/v1/chat-messages
│ Headers: Authorization: Bearer app-xxxxxxxxxxxx
│ Body: {
│ "inputs": {},
│ "query": "重疾险等待期多少天?",
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan",
│ "conversation_id": "",
│ "files": []
│ }
│ ├─ [成功] → 继续步骤7
│ ├─ [超时 >55s] → 回复"处理时间较长,请稍后再试" → 流程结束
│ └─ [BaoDan 错误] → 回复"AI 服务暂时不可用,请稍后重试" → 流程结束
[步骤7] 后端 - 回复企微消息
│ ├─ 获取企微 Access Token:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=CORPID&corpsecret=SECRET
│ │ 返回 {"access_token":"xxx", "expires_in":7200}
│ │ Token 缓存到 Redis提前5分钟刷新
│ │
│ ├─ 判断消息长度:
│ │ ├─ [<=2048字节] → 单条回复
│ │ └─ [>2048字节] → 分段回复每段间隔500ms
│ │
│ ├─ 单聊回复:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=TOKEN
│ │ Body: {"touser":"zhangsan", "msgtype":"text", "agentid":1000002,
│ │ "text":{"content":"根据XX重疾险条款规定等待期为90天..."}}
│ │
│ ├─ 群聊回复:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=TOKEN
│ │ Body: {"chatid":"群聊ID", "msgtype":"text",
│ │ "text":{"content":"根据XX重疾险条款规定等待期为90天..."}}
│ │
│ ├─ [发送成功] → 记录日志 → 流程结束
│ └─ [发送失败] → 记录错误日志 → 流程结束(用户无感知)
[步骤8] 企微用户收到回复
└─ 完成
```
### 12.4 企微 OAuth 登录(完整链路)
```
用户(企微内置浏览器)
[步骤1] 用户点击"企微登录"按钮
│ 前端构造 OAuth 授权 URL 并跳转:
│ https://open.weixin.qq.com/connect/oauth2/authorize?
│ corp_id=CORPID&
│ redirect_uri=https://your-domain.com/api/auth/wework-callback&
│ response_type=code&
│ scope=snsapi_base&
│ state=RANDOM_STATE_STRING
│ #wechat_redirect
[步骤2] 企微授权页面
│ ├─ [用户点击"同意"] → 企微携带 code + state 回调 redirect_uri
│ └─ [用户点击"取消"] → 回调 URL 带 error=access_denied
│ 前端捕获 → 显示"授权已取消,请重新登录"
[步骤3] 企微服务器 → 你的后端 回调
│ GET https://your-domain.com/api/auth/wework-callback?
│ code=XXXXX&
│ state=RANDOM_STATE_STRING
[步骤4] 后端 - 验证 state 防 CSRF
│ ├─ 读取 Redis: GET oauth:state:<session_id>
│ ├─ 比对 state 值
│ │
│ ├─ [state 不匹配] → 返回 403 "登录验证失败,请重试"
│ └─ [state 匹配] → 继续步骤5
[步骤5] 后端 - 用 code 换取企微用户信息
│ ├─ 获取 Access Token:
│ │ GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?
│ │ corpid=CORPID&corpsecret=SECRET
│ │ 返回 {"access_token":"xxx", "expires_in":7200}
│ │
│ ├─ 获取用户信息:
│ │ GET https://qyapi.weixin.qq.com/cgi-bin/user/get?
│ │ access_token=TOKEN&userid=USERID
│ │ 返回 {"userid":"zhangsan", "name":"张三", "department":[1]}
│ │
│ ├─ [企微 API 失败] → 返回 500 "企微服务异常,请稍后重试"
│ └─ [企微 API 成功] → 继续步骤6
[步骤6] 后端 - 查找或创建系统用户
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE wecom_userid = 'zhangsan'
│ │
│ ├─ [用户存在 + status=active] → 继续步骤7
│ ├─ [用户存在 + status=disabled] → 返回 403 "您的账号已被禁用,请联系管理员"
│ ├─ [用户不存在 + 允许自动注册] → INSERT 新记录 → 继续步骤7
│ └─ [用户不存在 + 不允许自动注册] → 返回 403 "您的账号暂未开通,请联系管理员"
[步骤7] 后端 - 签发 JWT Token
│ ├─ 生成 JWT: payload = {user_id, username, role, department, exp=now+2h}
│ ├─ 用 SECRET_KEY 签名
│ ├─ 写入 Redis: SET token:user:<user_id> = <token> EX 7200
│ └─ 更新 PostgreSQL: UPDATE wecom_user_mapping SET last_active_at = NOW()
[步骤8] 后端 → 前端 重定向
│ 302 重定向到前端页面URL 携带 Token
│ https://your-domain.com/app/login/callback?
│ token=eyJhbGciOiJIUzI1NiIs...&
│ expires_in=7200&
│ user={"id":"user-001","username":"张三","role":"sales","department":"上海团队"}
[步骤9] 前端 - 处理回调
│ ├─ 解析 URL 参数,提取 token 和 user 信息
│ ├─ 存储 Token: localStorage.setItem('token', token)
│ ├─ 存储用户信息: localStorage.setItem('user', userJSON)
│ ├─ 设置 Axios 拦截器: 每次请求自动携带 Authorization: Bearer <token>
│ ├─ 设置 Token 自动刷新: 在 token 过期前 5 分钟调用 /auth/refresh-token
│ └─ 跳转到主页: router.push('/chat')
[步骤10] 登录完成
└─ 用户进入系统主界面
```
---
## 十三、接口字段约束表
> 每个接口的所有请求参数和响应字段的完整约束定义。字段验证分为前端验证(即时反馈)和后端验证(安全兜底),两层都必须实现。
### 13.1 A1 认证鉴权接口
#### A1.1.1 POST /auth/wework-login
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|code|string|是|1-128字符|-|-|非空,去除首尾空格后长度>=1|企微授权码不能为空|
|string|state|是|1-64字符|-|-|必须与 Redis 中存储的 state 匹配|登录验证失败,请重试|
#### A1.1.2 POST /auth/password-login
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|username|是|3-64字符|-|-|仅允许字母、数字、下划线|用户名不能为空|
|string|password|是|8-128字符|-|-|非空字符串|密码不能为空|
#### A1.1.3 POST /auth/refresh-token
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|Authorization (Header)|是|-|-|-|Bearer <token> 格式token 为有效 JWT|登录已过期,请重新登录|
#### A1.1.4 POST /auth/logout
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|Authorization (Header)|是|-|-|-|Bearer <token> 格式|登录已过期,请重新登录|
### 13.2 A2 智能问答接口
#### A2.1.1 POST /chat/message
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|session_id|是|1-64字符|-|-|非空,必须是当前用户拥有的会话 ID|会话不存在或已删除|
|string|message|是|1-10000字符|-|-|去除首尾空格后长度>=1|请输入您的问题|
|object|filters|否|-|{}|-|对象类型,包含险种和保司字段|-|
|string|filters.险种|否|0-32字符|null|重疾险/寿险/医疗险/意外险/年金险/储蓄险|如果提供,必须是有效险种名|无效的险种筛选条件|
|string|filters.保司|否|0-64字符|null|-|字符串类型|无效的保司筛选条件|
#### A2.1.2 GET /chat/sessions
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|page|否|1-10000|1|-|正整数|页码必须为正整数|
|int|page_size|否|1-100|20|-|正整数最大100|每页条数不能超过100|
#### A2.1.3 POST /chat/sessions
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|title|否|0-128字符|自动根据首条消息生成|-|如果提供则为非空字符串|-|
#### A2.1.4 DELETE /chat/sessions/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,必须是当前用户拥有的会话 ID|会话不存在或已删除|
#### A2.1.5 GET /chat/sessions/{id}/messages
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,必须是当前用户拥有的会话 ID|会话不存在或已删除|
#### A2.1.6 POST /chat/messages/{id}/feedback
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,必须是当前用户的消息 ID|消息不存在|
|string|rating|是|-|-|helpful/not_helpful|必须是 helpful 或 not_helpful|请选择评分(有用或无用)|
|string|comment|否|0-2000字符|null|-|如果 rating 为 not_helpful 且提供 comment长度>=1|-|
#### A2.2.1 POST /retrieval/search
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|query|是|1-1000字符|-|-|非空搜索词|请输入搜索关键词|
|object|filters|否|-|{}|-|对象类型|-|
|string|filters.险种|否|0-32字符|null|重疾险/寿险/医疗险/意外险/年金险/储蓄险|有效险种名|-|
|string|filters.保司|否|0-64字符|null|-|字符串|-|
|int|top_k|否|1-20|5|-|正整数|返回数量不能超过20|
#### A2.2.2 GET /retrieval/suggest
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|query|是|1-500字符|-|-|非空,当前对话上下文|请输入上下文|
### 13.3 A3 方案生成接口
#### A3.1.1 POST /proposals/generate
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|object|customer|是|-|-|-|非空对象|客户信息不能为空|
|string|customer.name|是|1-64字符|-|-|非空字符串|请输入客户姓名|
|int|customer.age|是|1-150|-|-|正整数|请输入有效的年龄1-150|
|string|customer.gender|是|-|-|male/female|必须是 male 或 female|请选择客户性别|
|string|customer.health_status|否|0-32字符|健康|健康/有既往病史/拒保史|-|
|string|customer.occupation|是|1-64字符|-|-|非空字符串|请输入客户职业|
|int|customer.annual_income|否|0-100000000|0|-|非负整数|年收入不能为负数|
|int|customer.monthly_budget|是|1-1000000|-|-|正整数|月预算必须大于0|
|array|insurance_types|是|1-10项|-|重疾险/寿险/医疗险/意外险/年金险/储蓄险|非空数组,每项为有效险种名|请至少选择一个关注险种|
|int|coverage_amount|是|10000-10000000|-|-|正整数,>=10000|保额不能低于1万元|
|string|coverage_period|是|-|-|10年/20年/30年/至60岁/至70岁/至80岁/终身|必须是有效期限|请选择保障期限|
|array|existing_policies|否|0-20项|[]|-|数组,每项为对象|-|
#### A3.1.2 GET /proposals/generate/{task_id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|task_id (Path)|是|1-64字符|-|-|非空,必须是当前用户的任务 ID|任务不存在|
#### A3.1.4 POST /proposals/{id}/share
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,必须是当前用户拥有的方案 ID|方案不存在|
|int|expire_hours|否|1-720|72|-|正整数最大30天|分享有效期不能超过720小时|
### 13.4 A4 知识库接口
#### A4.1.1 POST /kb/documents/upload
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|file[]|files|是|1-50个文件|-|md/doc/docx/txt/pdf|仅支持 MD/Word/TXT/PDF 格式|仅支持 MD、Word、TXT、PDF 格式的文件|
|file|单个文件大小|-|最大 50MB|-|-|单文件不超过 50MB|文件大小不能超过50MB|
|string|险种|是|1-32字符|-|重疾险/寿险/医疗险/意外险/年金险/储蓄险|必须选择险种分类|请选择险种分类|
|string|保司|是|1-64字符|-|-|非空字符串|请选择保险公司|
#### A4.1.2 GET /kb/documents
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|page|否|1-10000|1|-|正整数|-|
|int|page_size|否|1-100|20|-|正整数|-|
|string|险种|否|0-32字符|null|重疾险/寿险/医疗险/意外险/年金险/储蓄险|有效险种名|-|
|string|保司|否|0-64字符|null|-|字符串|-|
|string|status|否|-|null|processing/completed/failed|有效状态值|-|
|string|keyword|否|0-128字符|null|-|搜索关键词(文件名/编号匹配)|-|
#### A4.1.3 GET /kb/documents/{id}/status
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空文档 ID|文档不存在|
#### A4.1.4 POST /kb/documents/{id}/retry
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,文档必须是 failed 状态|文档不存在或状态不允许重试|
#### A4.1.5 PATCH /kb/documents/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空文档 ID|文档不存在|
|string|编号|否|0-32字符|当前值|-|字母数字和连字符|-|
|string|险种|否|1-32字符|当前值|重疾险/寿险/医疗险/意外险/年金险/储蓄险|有效险种名|-|
|string|保司|否|1-64字符|当前值|-|非空字符串|-|
|array|tags|否|0-20项|当前值|-|字符串数组,每项 1-32 字符|-|
#### A4.1.6 DELETE /kb/documents/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空文档 ID|文档不存在|
#### A4.2.1 GET /kb/datasources
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|(无请求参数)| | | | | | | |
#### A4.2.2 POST /kb/datasources
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|name|是|1-128字符|-|-|非空字符串|数据源名称不能为空|
|string|api_url|是|1-512字符|-|-|合法 URL 格式|请输入有效的 API 地址|
|string|sync_frequency|是|-|-|-|Cron 表达式格式5字段|请输入有效的定时表达式|
|string|险种|是|1-32字符|-|重疾险/寿险/医疗险/意外险/年金险/储蓄险|有效险种名|请选择险种分类|
#### A4.2.3 POST /kb/datasources/{id}/sync
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空数据源 ID|数据源不存在|
#### A4.2.4 GET /kb/datasources/{id}/logs
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空数据源 ID|数据源不存在|
|int|page|否|1-10000|1|-|正整数|-|
|int|page_size|否|1-100|20|-|正整数|-|
### 13.5 A5 用户权限接口
#### A5.1.1 GET /admin/users
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|page|否|1-10000|1|-|正整数|-|
|int|page_size|否|1-100|20|-|正整数|-|
|string|role|否|-|null|super_admin/admin/manager/sales|有效角色值|-|
|string|department|否|0-128字符|null|-|字符串|-|
|string|keyword|否|0-128字符|null|-|用户名搜索关键词|-|
#### A5.1.1 POST /admin/users
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|username|是|2-64字符|-|-|非空,不能与已有用户名重复|用户名不能为空|
|string|wecom_userid|是|1-64字符|-|-|非空,不能与已有映射重复|企微用户ID不能为空|
|string|role|是|-|-|super_admin/admin/manager/sales|必须是有效角色|请选择有效角色|
|string|department|否|0-128字符|-|-|字符串|-|
#### A5.1.1 PUT /admin/users/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空用户 ID|用户不存在|
|string|role|否|-|当前值|super_admin/admin/manager/sales|有效角色|-|
|string|department|否|0-128字符|当前值|-|字符串|-|
|string|status|否|-|当前值|active/disabled|有效状态|-|
#### A5.1.1 DELETE /admin/users/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空用户 ID|用户不存在|
|(注意:禁用用户时吊销其所有 Token| | | | | | | |
#### A5.1.2 POST /admin/users/batch-import
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|file|file|是|1个文件|-|xls/xlsx|必须是 Excel 文件且符合模板格式|请上传符合模板格式的 Excel 文件|
|模板列username, wecom_userid, role, department| | | | | | | |
#### A5.1.3 GET /admin/roles
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|(无请求参数)| | | | | | | |
#### A5.1.3 POST /admin/roles
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|name|是|2-32字符|-|-|非空,不能与已有角色名重复|角色名不能为空|
|array|permissions|是|1-50项|-|-|非空数组,每项为有效权限标识|请至少分配一个权限|
#### A5.1.3 PUT /admin/roles/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空角色 ID|角色不存在|
|string|name|否|2-32字符|当前值|-|非空字符串|-|
|array|permissions|否|0-50项|当前值|-|权限标识数组|-|
#### A5.1.3 DELETE /admin/roles/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空,内置角色不可删除|角色不存在或为内置角色不可删除|
### 13.6 A6 系统配置接口
#### A6.1.1 GET /admin/llm-configs
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|(无请求参数)| | | | | | | |
#### A6.1.1 POST /admin/llm-configs
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|provider|是|1-32字符|-|deepseek/openai/zhipu/-|非空,有效供应商名|请选择模型供应商|
|string|model|是|1-64字符|-|-|非空,有效模型名|请选择模型|
|string|api_key|是|1-512字符|-|-|非空字符串,服务端 AES 加密存储|API Key 不能为空|
|string|base_url|否|0-512字符|供应商默认值|-|合法 URL 格式|请输入有效的 API 地址|
|bool|is_default|否|-|false|-|布尔值|-|
|object|params|否|-|{}|-|模型参数对象|-|
|float|params.temperature|否|0.0-2.0|0.7|-|浮点数范围校验|Temperature 必须在 0-2 之间|
|int|params.max_tokens|否|1-32768|4096|-|正整数|Max Tokens 必须为正整数|
|float|params.top_p|否|0.0-1.0|0.9|-|浮点数范围校验|Top P 必须在 0-1 之间|
#### A6.1.1 PUT /admin/llm-configs/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空配置 ID|配置不存在|
|(字段同 POST均为可选只更新提供的字段| | | | | | | |
#### A6.1.2 POST /admin/llm-configs/{id}/ping
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空配置 ID|配置不存在|
#### A6.1.3 GET /admin/prompts
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|(无请求参数)| | | | | | | |
#### A6.1.3 POST /admin/prompts
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|name|是|2-128字符|-|-|非空字符串|Prompt 名称不能为空|
|string|type|是|-|-|system/user|有效类型|请选择 Prompt 类型|
|string|content|是|1-50000字符|-|-|非空字符串,保存时自动生成版本快照|Prompt 内容不能为空|
|array|variables|否|0-20项|[]|-|字符串数组,变量名格式|-|
#### A6.1.3 PUT /admin/prompts/{id}
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|id (Path)|是|1-64字符|-|-|非空 Prompt ID|Prompt 不存在|
|(字段同 POST均为可选| | | | | | | |
#### A6.1.4 POST /admin/prompts/test
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|prompt_id|是|1-64字符|-|-|非空 Prompt ID|Prompt 不存在|
|string|test_query|是|1-2000字符|-|-|非空测试问题|请输入测试问题|
### 13.7 A7 留痕日志接口
#### A7.1.1 GET /admin/logs/chat
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|page|否|1-10000|1|-|正整数|-|
|int|page_size|否|1-100|50|-|正整数|-|
|string|user_id|否|0-64字符|null|-|用户 ID|-|
|string|start_date|否|10字符|null|-|YYYY-MM-DD 格式日期|日期格式不正确|
|string|end_date|否|10字符|null|-|YYYY-MM-DD 格式日期,>= start_date|结束日期不能早于开始日期|
|string|keyword|否|0-128字符|null|-|搜索关键词|-|
|string|rating|否|-|null|helpful/not_helpful|有效评分值|-|
|string|export|否|-|null|csv|导出格式|-|
#### A7.1.3 GET /admin/logs/system
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|page|否|1-10000|1|-|正整数|-|
|int|page_size|否|1-100|50|-|正整数|-|
|string|action|否|-|null|login/logout/upload/delete/config_change|有效操作类型|-|
|string|user_id|否|0-64字符|null|-|用户 ID|-|
|string|start_date|否|10字符|null|-|YYYY-MM-DD 格式|-|
|string|end_date|否|10字符|null|-|YYYY-MM-DD 格式|-|
### 13.8 A8 统计报表接口
#### A8.1.2 GET /stats/trend
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|metric|是|-|-|chat_count/user_count/proposal_count|有效指标名|请选择统计指标|
|string|start_date|是|10字符|-|-|YYYY-MM-DD 格式|请选择开始日期|
|string|end_date|是|10字符|-|-|YYYY-MM-DD 格式|请选择结束日期|
|string|granularity|否|-|day|day/week/month|有效粒度值|-|
#### A8.1.3 GET /stats/kb-health
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|int|days|否|1-365|30|-|正整数|统计天数不能超过365|
#### A8.1.4 GET /stats/token-cost
|字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案|
|------|-----|:---:|------------|------|------|--------|------------|
|string|start_date|是|10字符|-|-|YYYY-MM-DD 格式|请选择开始日期|
|string|end_date|是|10字符|-|-|YYYY-MM-DD 格式|请选择结束日期|
|string|group_by|否|-|model|model/day|有效分组维度|-|
---
## 十四、产品推荐表单验证规则
> 产品推荐页面(/recommend所有表单字段的完整验证规则包含前端即时验证和后端兜底验证。
### 14.1 客户信息表单
|字段名|类型|必填|前端验证规则|后端验证规则|错误提示文案|边界值测试用例|
|------|-----|:---:|------------|------------|------------|------------|
|客户姓名|text|是|非空,去除首尾空格后长度 1-64仅允许中文/英文/空格/·|同前端|请输入客户姓名|空值→"请输入客户姓名";空格→"请输入客户姓名"65字符→"姓名不能超过64个字符"|
|年龄|number|是|整数,范围 1-150不支持小数|同前端|请输入有效的年龄1-150|空值→"请输入年龄"0→"请输入有效的年龄1-150"-1→同上151→同上3.5→"年龄必须为整数"1→合法150→合法|
|性别|radio|是|必须选择 male 或 female默认无选中|同前端|请选择客户性别|未选择→"请选择客户性别"male→合法female→合法|
|健康状况|select|否|枚举值:健康/有既往病史/拒保史,默认"健康"|同前端|-|空值→默认"健康""健康"→合法|
|职业|text|是|非空1-64字符去除首尾空格|同前端|请输入客户职业|空值→"请输入客户职业"65字符→"职业不能超过64个字符"|
|年收入|number|否|非负整数,范围 0-100000000默认 0|同前端|年收入不能为负数|空值→默认0-1→"年收入不能为负数"100000001→"年收入不能超过1亿元"|
|月预算|number|是|正整数,范围 1-1000000|同前端|月预算必须大于0|空值→"请输入月预算"0→"月预算必须大于0"-1→同上1000001→"月预算不能超过100万元"|
### 14.2 保障需求表单
|字段名|类型|必填|前端验证规则|后端验证规则|错误提示文案|边界值测试用例|
|------|-----|:---:|------------|------------|------------|------------|
|关注险种|checkbox-group|是|至少勾选1项最多6项可选项重疾险/寿险/医疗险/意外险/年金险/储蓄险|同前端|请至少选择一个关注险种|未勾选→"请至少选择一个关注险种"勾选1项→合法勾选6项→合法|
|保额目标|slider/number|是|正整数,范围 10000-10000000步进 10000|同前端|保额不能低于1万元|10000→合法9999→"保额不能低于1万元"10000001→"保额不能超过1000万元"|
|保障期限|select|是|枚举值10年/20年/30年/至60岁/至70岁/至80岁/终身|同前端|请选择保障期限|未选择→"请选择保障期限""终身"→合法|
### 14.3 已有保单信息(可选)
|字段名|类型|必填|前端验证规则|后端验证规则|错误提示文案|边界值测试用例|
|------|-----|:---:|------------|------------|------------|------------|
|已有保单|dynamic-form|否|每个保单条目包含:产品名称(必填)、保司(必填)、保额(可选)、生效日期(可选)|同前端|-|不填→合法跳过填1条→合法填20条→合法填21条→"已有保单不能超过20条"|
### 14.4 表单提交综合验证
|验证场景|触发条件|前端行为|后端行为|错误提示|
|--------|---------|--------|--------|--------|
|全部必填项未填 | 点击"生成方案" | 各必填字段下方显示红色行内错误 | 返回 1001 错误码 | 各字段对应错误提示 |
|部分必填项未填 | 点击"生成方案" | 未填字段标红,已填字段正常 | 返回 1001 错误码 | 缺失字段的错误提示 |
|所有字段填写正确 | 点击"生成方案" | 按钮变为 loading 状态,显示"方案生成中..." | 调用 BaoDan Workflow | 无 |
|网络断开 | 提交时 | 弹窗提示"网络异常,请检查网络连接" | 不到达后端 | 网络异常,请检查网络连接 |
|JWT 过期 | 提交时 | 自动跳转登录页 | 返回 1003 错误码 | 登录已过期,请重新登录 |
|重复提交 | 快速双击"生成方案" | 第一次点击后按钮立即禁用 | 正常处理第一个请求 | 无 |
|生成超时(>120s) | 后端响应超时 | 弹窗提示"方案生成超时,请重试" | 标记任务为 failed | 方案生成超时,请重试 |
|生成失败 | BaoDan Workflow 执行失败 | 弹窗提示"方案生成失败,请重试" | 标记任务为 failed | 方案生成失败,请重试 |
### 14.5 前端验证实现规范
- 所有字段在用户**离开输入框时**blur 事件)触发首次验证
- 在用户**修改输入时**input 事件)触发实时验证(仅在已触发过首次验证后)
- 点击"生成方案"按钮时执行**全量验证**
- 错误提示显示在字段下方,使用红色文字(#F56C6C字号 12px
- 验证不通过时输入框边框变为红色border-color: #F56C6C
- 验证通过时恢复默认边框颜色
- 提交按钮在所有必填项验证通过前保持 disabled 状态(灰色,不可点击)
---
## 十五、BaoDan 配置操作手册
> 从零开始配置 BaoDan 平台的完整操作步骤,面向从未接触过 BaoDan 的开发者。每一步都标注了具体的按钮名称、菜单位置和配置值。
### 15.1 BaoDan 初始化(首次访问)
**前提**BaoDan Docker 部署已完成(参见第二十章部署清单)。
1. 浏览器打开 `http://你的服务器IP:3000`
2. 首次访问会看到"BaoDan"欢迎页面,点击 **"设置管理员账号"** 按钮
3. 填写管理员信息:
- 邮箱:`admin@your-domain.com`
- 密码设置一个强密码至少8位含大小写+数字)
- 名称:`系统管理员`
4. 点击 **"创建管理员"** 按钮
5. 自动跳转到 BaoDan 后台首页,初始化完成
### 15.2 添加 DeepSeek 模型供应商
1. 登录 BaoDan 后台(`http://你的服务器IP:3000`
2. 点击左下角 **齿轮图标**(设置)→ 进入设置页面
3. 点击顶部 **"模型供应商"** 标签页
4. 点击 **"添加供应商"** 按钮
5. 在供应商列表中找到 **"DeepSeek"**,点击 **"设置"** 按钮
6. 填写配置:
- API Key`sk-xxxxxxxxxxxxxxxxxxxxxxxx`(从 platform.deepseek.com 获取)
- API Base URL`https://api.deepseek.com/v1`(默认已填,无需修改)
7. 点击 **"保存"** 按钮
8. 添加 Embedding 模型:
- 在同一页面找到 **"文本嵌入模型"** 区域
- 点击 **"设置默认模型"**
- 选择供应商 `DeepSeek`,模型 `deepseek-embedding`
- 点击 **"保存"**
9. 验证:点击 **"测试"** 按钮,显示"连接成功"即可
### 15.3 创建知识库(按险种分类)
**每个险种创建一个独立知识库**,便于管理和检索筛选。
1. 点击左侧导航 **"知识库"** 菜单
2. 点击 **"创建知识库"** 按钮
3. 填写信息:
- 名称:`重疾险-产品条款`(格式:`险种-分类`
- 描述:`重疾险相关产品条款、费率表、理赔规则等文档`
4. 点击 **"创建"** 按钮
5. 进入知识库设置页面配置分段规则见15.4
6. 重复以上步骤,依次创建以下知识库:
- `寿险-产品条款`
- `医疗险-产品条款`
- `意外险-产品条款`
- `年金险-产品条款`
- `储蓄险-产品条款`
- `保险法规-政策文件`(可选,存放监管政策类文档)
### 15.4 上传文档到知识库
1. 进入目标知识库页面(如"重疾险-产品条款"
2. 点击 **"添加文件"** 按钮
3. 选择 **"上传文件"** 选项
4. 拖拽或选择 `.md` 文件(支持批量上传)
5. 配置分段设置:
- **分段标识符**:选择 **"Markdown 标题"**(按 `#` / `##` / `###` 分段)
- **分段最大长度**`1000` tokens
- **分段重叠**`100` tokens确保上下文连续性
- **文本预处理规则**
- 勾选 **"替换连续空格/换行/制表符"**
- 勾选 **"删除所有 URL 和邮箱地址"**(可选)
6. **索引方式**选择:
- **高质量模式**(推荐):使用 Embedding 向量 + 全文检索混合模式
- Embedding 模型:`deepseek-embedding`
7. 点击 **"保存并处理"** 按钮
8. 等待处理完成(状态从 "处理中" 变为 "可用"
9. 重复以上步骤上传所有文档
### 15.5 创建聊天应用(智能问答)
1. 点击左侧导航 **"工作室"** 菜单
2. 点击 **"创建应用"** 按钮
3. 选择 **"聊天助手"** 类型
4. 填写应用信息:
- 名称:`保险智能客服`
- 描述:`基于保险知识库的智能问答助手`
5. 点击 **"创建"** 按钮
6. 进入应用编辑页面,配置以下内容:
**配置 Prompt**
1. 点击 **"编排"** 标签页
2.**"提示词"** 区域编辑系统提示词:
```
你是一名专业的保险顾问助手,专门为保险代理人提供产品咨询和方案建议服务。
## 核心规则
1. 所有回答必须基于检索到的知识库内容,不可编造产品信息
2. 如果知识库中没有相关内容,明确告知用户"当前知识库中未找到相关信息,建议咨询相关保险公司"
3. 回答要专业、准确、简洁,适合保险代理人向客户转述
4. 涉及具体条款时,必须注明产品名称和出处
## 回答格式
- 使用清晰的分段和编号
- 重要数据(保额、保费、等待期等)用粗体标注
- 如果涉及多个产品,使用表格对比展示
```
**关联知识库**
1. 点击 **"上下文"** 区域
2. 点击 **"添加"** 按钮
3. 选择所有已创建的知识库(全选或按需选择)
4. 配置检索参数:
- **Top-K**`5`返回最相关的5个文档片段
- **Score 阈值**`0.5`(低于此分数的片段不返回)
**配置模型参数**
1. 点击 **"模型"** 区域
2. 选择模型:`deepseek-chat`
3. 设置参数:
- Temperature`0.7`
- Top P`0.9`
- Max Tokens`4096`
### 15.6 设置应用公开访问
1. 在应用编辑页面,点击右上角 **"发布"** 按钮
2. 弹窗中确认发布
3. 发布后,点击 **"访问 API"** 获取 API 地址
4. 记录 **API 密钥**(格式:`app-xxxxxxxxxxxx`),后续后端配置使用
5. 获取 WebApp URL`http://baodan:3000/chat/{app_id}`
### 15.7 创建 Workflow 应用(产品推荐)
1. 点击左侧导航 **"工作室"**
2. 点击 **"创建应用"** → 选择 **"工作流"** 类型
3. 名称:`产品推荐方案生成`
4. 点击 **"创建"** 后进入可视化编辑器
5. 按以下顺序添加节点(拖拽组件到画布):
**节点1 - 开始节点**(默认已存在):
- 添加输入变量:
- `age`:类型 string必填
- `gender`:类型 string必填
- `occupation`:类型 string必填
- `annual_income`:类型 string必填
- `monthly_budget`:类型 string必填
- `insurance_types`:类型 string必填
- `coverage_amount`:类型 string必填
- `coverage_period`:类型 string必填
**节点2 - 代码节点(参数校验)**
- 拖入 "代码执行" 组件
- 输入:所有开始节点变量
- 代码逻辑:校验 age 为 1-150 整数gender 为 male/female 等
**节点3 - 代码节点(检索策略)**
- 输入:校验后的参数
- 代码:根据 insurance_types 生成检索 query 列表
**节点4 - 知识库检索节点**
- 拖入 "知识库" 组件
- 关联所有险种知识库
- 输入:检索 query
- Top-K5
- Score 阈值0.6
**节点5 - LLM 节点**
- 拖入 "LLM" 组件
- 模型:`deepseek-chat`
- Temperature0.7
- Prompt参见第十九章完整 Prompt
**节点6 - 代码节点(格式化输出)**
- 输入LLM 输出的 Markdown
- 逻辑:清理格式,确保可读性
**节点7 - 结束节点**
- 输出变量:`recommendation`(格式化后的方案文本)
6. 点击 **"发布"** 按钮保存 Workflow
7. 记录 API 密钥Workflow 专用 Key
### 15.8 配置 iframe 嵌入参数
1. 进入聊天应用设置
2. 点击 **"API 访问"** 标签页
3. 勾选 **"启用 WebApp 访问"**
4. 配置 **"跨域设置"**
- 允许的来源Allowed Origins添加 `https://your-domain.com`
5. 获取 iframe 嵌入代码:
```
<iframe
src="http://baodan:3000/chat/{app_id}?user={user_id}"
style="width:100%; height:100%; border:none;"
allow="microphone">
</iframe>
```
### 15.9 测试对话功能
1. 在聊天应用页面,点击右上角 **"预览"** 按钮
2. 输入测试问题:`重疾险的等待期是多少天?`
3. 验证以下内容:
- [ ] 收到基于知识库的回答(非编造内容)
- [ ] 回答中包含来源引用
- [ ] 回答在 15 秒内开始输出
4. 测试边界情况:
- 输入空消息 → 应提示"请输入问题"
- 输入无关问题 → 应回答"当前知识库中未找到相关内容"
- 输入超长文本(>10000字→ 应正常处理或提示过长
### 15.10 测试 Workflow 功能
1. 进入 Workflow 应用页面,点击 **"预览"** 按钮
2. 填写测试输入:
- age`35`
- gender`male`
- occupation`软件工程师`
- annual_income`300000`
- monthly_budget`2000`
- insurance_types`重疾险,医疗险`
- coverage_amount`500000`
- coverage_period`终身`
3. 点击 **"运行"** 按钮
4. 验证以下内容:
- [ ] 每个节点正常执行(无红色错误标记)
- [ ] 知识库检索返回了相关产品信息
- [ ] LLM 生成了三套方案(基础/均衡/全面)
- [ ] 每套方案包含产品表格、保费、推荐理由
- [ ] 总保费不超过月预算 x 1224000元/年)
- [ ] 整体执行时间 < 120
---
## 十六、企微应用配置操作手册
> 企微管理后台的完整配置步骤,从创建应用到机器人可用的全流程。
### 16.1 登录企微管理后台
1. 打开浏览器访问 `https://work.weixin.qq.com/`
2. 使用管理员账号扫码或账密登录
3. 进入管理后台首页
### 16.2 创建自建应用
1. 点击左侧导航 **"应用管理"**
2. 点击 **"自建"** 区域的 **"创建应用"** 按钮
3. 填写应用信息
- **应用名称**`保险智能客服`
- **应用 Logo**上传一个图标建议 200x200px PNG
- **应用介绍**`基于 AI 的保险知识问答和产品推荐助手`
- **可见范围**选择需要使用此应用的部门全部部门或指定销售部门
4. 点击 **"创建应用"** 按钮
5. 创建成功后记录以下信息
- **AgentId**在应用详情页顶部显示 `1000002`
- **Secret**点击 **"Secret"** 旁边的 **"查看"** 按钮输入管理员密码后获取
### 16.3 配置应用可见范围
1. 在应用详情页点击 **"可见范围"** 区域的 **"编辑"** 按钮
2. 勾选需要使用此应用的部门
3. 点击 **"保存"**
4. **重要**可见范围决定了哪些用户能在企微中看到此应用
### 16.4 配置接收消息(回调 URL
1. 在应用详情页找到 **"接收消息"** 区域
2. 点击 **"设置API接收"** 按钮
3. 填写以下信息
- **URL**`https://your-domain.com/api/wecom/callback`
注意必须是 HTTPS企微要求
- **Token**点击 **"随机获取"** 按钮自动生成
- **EncodingAESKey**点击 **"随机获取"** 按钮自动生成43位字符串
4. 点击 **"保存"** 按钮
5. 企微会向你填写的 URL 发送验证请求
6. **此时后端服务必须已部署并运行**否则保存会失败
7. 验证通过后点击 **"接收消息"** 下的 **"设置接收消息"**选择 **"使用 API 接收消息"**
8. Token EncodingAESKey 记录下来配置到后端环境变量
```
WECOM_TOKEN=你复制的Token
WECOM_ENCODING_AES_KEY=你复制的EncodingAESKey
```
### 16.5 配置企业可信 IP
1. 在应用详情页找到 **"企业可信IP"** 区域
2. 点击 **"配置"** 按钮
3. 添加你服务器的**公网 IP 地址** `123.45.67.89`
4. 如果有多个出口 IP全部添加
5. 点击 **"保存"**
6. **注意**如果不配置企微消息回调会被拒绝
### 16.6 获取 CorpID / AgentID / Secret
|信息|获取位置|格式示例|
|------|---------|---------|
|CorpID|管理后台 我的企业 企业信息 企业ID|`ww1234567890abcdef`|
|AgentID|应用管理 应用详情页顶部|`1000002`|
|AgentSecret|应用详情 Secret 查看|`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`|
配置到后端环境变量
```
WECOM_CORP_ID=ww1234567890abcdef
WECOM_AGENT_ID=1000002
WECOM_AGENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
### 16.7 配置 OAuth 回调域名
1. 管理后台 **"我的企业"** **"企业微信授权登录"**
2. 点击 **"设置授权回调域"**
3. 填写你的域名`your-domain.com`
4. 点击 **"保存"**
5. **注意**这里填的是根域名不是具体路径回调 URL `https://your-domain.com/api/auth/wework-callback`
### 16.8 测试机器人消息收发
1. 在企微手机端或电脑端搜索应用名称 **"保险智能客服"**
2. 打开应用发送一条消息`你好`
3. 验证以下内容
- [ ] 收到 AI 回复非超时错误
- [ ] 回复内容与知识库相关
- [ ] 回复延迟 < 10
4. 如果没有收到回复
- 检查后端日志是否有企微回调记录
- 检查可信 IP 配置是否正确
- 检查 Token/Secret 配置是否正确
### 16.9 测试 OAuth 登录
1. 在企微内置浏览器中访问`https://your-domain.com/app/login`
2. 点击 **"企微登录"** 按钮
3. 应自动跳转到企微授权页面
4. 点击 **"同意"** 授权
5. 验证以下内容
- [ ] 成功跳转回系统主页
- [ ] 右上角显示用户姓名
- [ ] 可以正常使用各项功能
6. 如果登录失败
- 检查 OAuth 回调域名是否配置正确
- 检查后端 WECOM_CORP_ID WECOM_AGENT_SECRET 是否正确
- 查看浏览器控制台网络请求的错误信息
### 16.10 配置应用主页 URL企微 H5
1. 在应用详情页找到 **"应用主页"** 区域
2. 点击 **"设置"** 按钮
3. 选择 **"自定义主页"**
4. 填写主页 URL`https://your-domain.com/app`
5. 设置 **"工作台"** 展示
- 勾选 **"在企业微信工作台展示"**
- 设置展示名称`保险智能客服`
6. 点击 **"保存"**
7. 验证在企微工作台中应能看到"保险智能客服"入口
---
## 十七、前端页面交互设计
> 每个页面的完整交互设计,包括所有状态、用户操作流程、按钮行为和状态变化。
### 17.1 登录页(/login
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|初始状态|登录方式选择界面|页面首次加载|
|企微授权中|全屏 loading + "正在跳转企微授权..."|点击"企微登录"按钮后|
|授权回调中|全屏 loading + "正在登录..."|企微回调到达后端时|
|登录失败|登录界面 + 顶部红色提示条|登录接口返回错误|
|已登录|自动跳转到 /chat|已存在有效 Token|
**用户操作流程**
1. 用户访问 /login 显示登录界面
2. 选择登录方式
- **方式A - 企微登录**点击"企微登录"按钮 跳转企微授权 同意授权 回调到后端 签发 JWT 跳转 /chat
- **方式B - 账密登录**输入用户名 + 密码 点击"登录" 后端校验 签发 JWT 跳转 /chat
3. 登录成功 存储 Token localStorage 跳转 /chat
**按钮行为**
- "企微登录"按钮点击后变为 loading 状态禁止重复点击跳转企微授权页
- "登录"按钮表单验证通过后可点击点击后显示 loading + "登录中..."禁止重复点击
- Enter 在密码输入框中按 Enter 等同于点击"登录"按钮
### 17.2 对话页(/chat
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|加载中|骨架屏Skeleton|页面首次加载|
|空状态|居中图标 + "开始新的对话吧" + 快捷提问按钮|无任何会话|
|正常|左侧会话列表 + 右侧对话区域|有会话数据|
|发送中|用户消息气泡 + AI 回复区域显示"思考中..."动画|发送消息后等待响应|
|流式输出|AI 回复区域逐字显示文字|SSE 流式响应中|
|错误|对话区域顶部红色提示条|接口返回错误|
|会话删除确认|弹窗"确定要删除这个会话吗"|点击删除按钮|
**用户操作流程**
1. 进入页面 加载会话列表 显示最近会话或空状态
2. 点击"新建对话" 清空对话区域 等待用户输入
3. 在输入框输入问题 点击发送或按 Enter)→ 显示"思考中..."
4. 收到流式响应 逐字显示 AI 回答
5. 回答完成 显示来源引用 + 操作按钮复制/点赞/点踩
6. 可继续在同一会话中提问
**按钮行为**
- "新建对话"按钮清空对话区域重置 session_id 为空输入框获得焦点
- "发送"按钮输入框为空时禁用灰色有内容时启用蓝色点击后立即禁用直到回复完成
- "复制"按钮复制 AI 回答的纯文本到剪贴板点击后变为"已复制"状态2秒后恢复
- "点赞/点踩"按钮点击后调用反馈接口已评的按钮高亮可切换
- "删除会话"按钮显示确认弹窗确认后调用 DELETE 接口从列表移除
- 筛选下拉框险种/保司选择后影响后续对话的知识库检索范围
### 17.3 产品推荐页(/recommend
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|初始状态|空白表单所有字段为默认值|页面首次加载或重置|
|表单填写中|表单各字段可编辑|用户交互中|
|表单验证失败|未通过字段标红 + 红色行内错误提示|点击"生成方案"|
|提交中|按钮 loading + "方案生成中..." + 禁止重复提交|提交成功后|
|生成中轮询|进度提示 + "正在生成方案请稍候..."|后端返回 processing|
|生成完成|方案预览区显示三套方案|轮询返回 done|
|生成失败|红色错误提示 + "重新生成"按钮|轮询返回 failed|
|网络错误|弹窗"网络异常请检查网络连接"|请求超时或网络断开|
**用户操作流程**
1. 进入页面 显示空白表单
2. 填写客户信息姓名/年龄/性别/职业/收入/预算
3. 选择关注险种勾选复选框
4. 设置保额目标和保障期限
5. 可选添加已有保单信息
6. 点击"生成方案" 前端全量校验 校验通过则提交
7. 显示 loading 轮询任务状态 显示生成的方案
8. 可点击"重新生成"回到步骤2
**按钮行为**
- "生成方案"按钮全量验证通过后可点击点击后显示 loading 状态disabled 直到生成完成或失败
- "重新生成"按钮重置表单为上次提交值允许修改后重新提交
- "浏览器打印"按钮调用 window.print() 打印方案预览区
- "添加保单"按钮在已有保单区域动态添加一行表单
- "删除保单"行按钮移除对应保单行至少保留0行
### 17.4 推荐结果页
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|加载中|骨架屏|页面加载时|
|正常|三套方案基础/均衡/全面每套含产品表格|数据加载完成|
|空状态|暂无方案 + "去生成"按钮|无历史方案|
|错误|错误提示 + "重试"按钮|接口返回错误|
**用户操作流程**
1. 生成完成后直接在推荐页下方显示方案
2. 可切换查看三套方案Tab 切换基础方案/均衡方案/全面方案
3. 每套方案显示方案名称总年保费产品明细表格推荐理由
4. 可点击"浏览器打印"导出
5. 可点击"重新生成"回到表单页
### 17.5 历史方案页(/recommend/history
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|加载中|表格骨架屏|页面加载时|
|正常|方案列表表格 + 分页器|有历史数据|
|空状态|居中图标 + "暂无推荐方案"|无历史数据|
|筛选结果为空|表格显示"暂无匹配数据"|筛选条件无匹配|
|删除确认|弹窗"确定要删除此方案吗"|点击删除按钮|
**用户操作流程**
1. 进入页面 加载方案列表默认按时间倒序
2. 可使用筛选条件客户姓名险种日期范围状态
3. 点击某条记录 查看方案详情
4. 可对方案进行操作查看删除分享
**按钮行为**
- "查看"按钮弹窗展示方案详情Markdown 渲染
- "删除"按钮显示确认弹窗确认后删除
- "分享"按钮调用分享接口生成有时效的链接显示在弹窗中可复制
- 分页器翻页加载对应页数据
- "导出CSV"按钮调用日志接口 export=csv触发浏览器下载
### 17.6 管理后台页面
#### 17.6.1 管理后台框架(/admin
**布局**左侧导航栏 + 右侧内容区 + 顶部栏
**左侧导航菜单**
- 知识库管理 /admin/knowledge-base
- 对话日志 /admin/logs/chat
- 系统日志 /admin/logs/system仅超级管理员可见
- 用户管理 /admin/users仅超级管理员可见
- 角色管理 /admin/roles仅超级管理员可见
- LLM 配置 /admin/llm-configs仅超级管理员可见
- Prompt 管理 /admin/prompts
- 数据统计 /admin/stats
**顶部栏**管理后台标题 + "切换到用户端"链接 + 用户信息 + 退出登录
#### 17.6.2 知识库管理页(/admin/knowledge-base
**页面状态**
|状态|显示内容|触发条件|
|------|--------|---------|
|加载中|表格骨架屏|页面加载|
|正常|文档列表表格 + 上传按钮 + 筛选栏|有文档数据|
|上传中|上传进度条 + "正在处理..."|上传文件后|
|处理中|文档状态列显示 spinner + "处理中"|文档正在向量化|
**按钮行为**
- "上传文档"按钮打开文件选择器选择文件后弹出分类配置险种/保司确认后上传
- "重试"按钮 failed 状态文档可见重新触发处理流水线
- "查看状态"按钮弹窗显示处理流水线各阶段状态和耗时
- "删除"按钮确认后软删除文档并下线索引
- "编辑"按钮弹窗编辑文档元数据编号/分类/标签
#### 17.6.3 对话日志页(/admin/logs/chat
**按钮行为**
- "查询"按钮根据筛选条件重新加载日志
- "重置"按钮清空所有筛选条件恢复默认
- "导出CSV"按钮下载日志数据为 CSV 文件
#### 17.6.4 用户管理页(/admin/users
**按钮行为**
- "新增用户"按钮弹窗表单用户名/企微ID/角色/部门填写后保存
- "批量导入"按钮下载 Excel 模板 填写后上传 预览导入结果 确认导入
- "编辑"按钮弹窗修改角色/部门/状态
- "禁用"按钮确认后禁用用户同时吊销其所有 Token
- "启用"按钮恢复已禁用的用户
#### 17.6.5 角色管理页(/admin/roles
**按钮行为**
- "新增角色"按钮弹窗表单角色名 + 权限树勾选保存后生效
- "编辑"按钮修改角色名和权限内置角色仅可编辑权限
- "删除"按钮内置角色不可删除按钮灰色禁用自定义角色可删除
#### 17.6.6 LLM 配置页(/admin/llm-configs
**按钮行为**
- "添加模型"按钮弹窗表单供应商/模型/Key/Base URL/参数保存后自动加密存储 Key
- "测试连通性"按钮调用 ping 接口显示延迟结果绿色=成功/红色=失败)
- "设为默认"按钮将该模型设为默认其他模型取消默认
- "删除"按钮确认后删除配置已被应用引用的不可删除
---
## 十八、错误提示文案清单
> 所有面向用户的错误消息完整清单,按错误码组织。每条错误消息都是完整的中文句子,前端直接展示给用户。
### 18.1 统一响应格式
```json
{
"code": 0,
"message": "success",
"data": { ... }
}
```
- `code = 0` 表示成功
- `code != 0` 表示失败`message` 字段为用户可见的错误文案
- 前端根据 `code` 值决定展示方式弹窗/行内/Toast
### 18.2 认证相关错误1xxx
|错误码|触发场景|显示位置|具体文案|是否可重试|
|:---:|--------|--------|--------|:---:|
|1001|请求参数缺少必填项或类型错误|页面顶部 Toast 提示|参数错误{具体字段}不能为空|修改后重试|
|1001|请求参数类型不匹配|页面顶部 Toast 提示|参数错误{字段名}格式不正确|修改后重试|
|1002|未携带 Token Token 格式无效|自动跳转登录页|登录已过期请重新登录|需重新登录|
|1002|Token 不在有效用户中|自动跳转登录页|登录已过期请重新登录|需重新登录|
|1003|JWT Token 已超过有效期|自动跳转登录页|登录已过期请重新登录|需重新登录|
|1003|refresh-token 接口 Token 过期|自动跳转登录页|登录已过期请重新登录|需重新登录|
|1004|用户角色无权访问该接口|页面顶部 Toast 提示|权限不足您没有访问该功能的权限|需管理员授权|
|1004|非管理员访问管理接口|页面顶部 Toast 提示|权限不足您没有管理权限||
|1005|请求的资源 ID 不存在|行内提示或弹窗|请求的资源不存在或已删除||
|1005|访问不存在的会话|行内提示|会话不存在或已删除||
|1005|访问不存在的方案|行内提示|方案不存在或已删除||
|1005|访问不存在的文档|行内提示|文档不存在或已删除||
|1005|访问不存在的用户|行内提示|用户不存在||
|1005|访问不存在的角色|行内提示|角色不存在||
|1005|访问不存在的 Prompt|行内提示|Prompt 不存在||
|1005|访问不存在的数据源|行内提示|数据源不存在||
|1005|访问不存在的任务|行内提示|任务不存在||
### 18.3 BaoDan 相关错误2xxx
|错误码|触发场景|显示位置|具体文案|是否可重试|
|:---:|--------|--------|--------|:---:|
|2001|BaoDan API 不可达或返回错误|页面顶部 Toast 提示|AI 服务暂时不可用请稍后重试||
|2001|BaoDan API 返回 500 错误|页面顶部 Toast 提示|AI 服务暂时不可用请稍后重试||
|2001|BaoDan API Key 过期或无效|页面顶部 Toast 提示|AI 服务配置异常请联系管理员|需管理员修复|
|2002|BaoDan Chat API 响应超过 60 |页面顶部 Toast 提示|AI 回答生成超时请重试||
|2003|BaoDan Workflow 执行失败|弹窗提示|方案生成失败请重试||
|2003|Workflow 节点执行出错|弹窗提示|方案生成失败请稍后重试||
|2004|BaoDan Workflow 执行超过 120 |弹窗提示|方案生成超时请重试||
### 18.4 企微相关错误3xxx
|错误码|触发场景|显示位置|具体文案|是否可重试|
|:---:|--------|--------|--------|:---:|
|3001|企微 API 调用失败|服务端日志用户不可见|不展示给用户记录日志|自动重试|
|3002|企微 Access Token 获取失败|服务端日志用户不可见|不展示给用户3次重试后放弃|自动重试3次|
|3003|企微消息回复失败|服务端日志用户不可见|不展示给用户记录日志||
|3004|企微 OAuth 授权码已过期5分钟有效|登录页顶部 Toast 提示|授权已过期请重新登录|重新发起授权|
|3005|企微用户不在系统白名单中|登录页顶部 Toast 提示|您的账号暂未开通请联系管理员||
|3005|企微用户已被禁用|登录页顶部 Toast 提示|您的账号已被禁用请联系管理员||
### 18.5 文件相关错误4xxx
|错误码|触发场景|显示位置|具体文案|是否可重试|
|:---:|--------|--------|--------|:---:|
|4001|文件上传失败网络中断等原因|页面顶部 Toast 提示|文件上传失败请检查网络后重试||
|4001|文件写入磁盘失败|页面顶部 Toast 提示|文件保存失败请稍后重试||
|4002|文件格式不支持|上传弹窗行内提示|仅支持 MDWordTXTPDF 格式的文件|需转换格式|
|4003|单个文件超过 50MB 限制|上传弹窗行内提示|文件大小不能超过 50MB|需压缩文件|
|4003|批量上传总大小超过限制|上传弹窗行内提示|上传文件总大小不能超过 200MB|需分批上传|
|4004|Excel 导入模板格式错误|弹窗提示|请下载模板后按模板格式填写|需使用模板|
### 18.6 系统相关错误5xxx-9xxx
|错误码|触发场景|显示位置|具体文案|是否可重试|
|:---:|--------|--------|--------|:---:|
|5001|PostgreSQL 数据库操作失败|页面顶部 Toast 提示|系统繁忙请稍后重试||
|5002|Redis 缓存操作失败|页面顶部 Toast 提示|系统繁忙请稍后重试||
|5003|文件操作失败读写异常|页面顶部 Toast 提示|文件处理异常请稍后重试||
|9999|服务器内部未知错误|页面顶部 Toast 提示|系统异常请稍后重试||
### 18.7 前端本地验证错误(非接口返回)
|触发场景|显示位置|具体文案|是否可重试|
|--------|--------|--------|:---:|
|登录用户名为空|行内错误|用户名不能为空|修改后重试|
|登录密码为空|行内错误|密码不能为空|修改后重试|
|登录用户名过短|行内错误|用户名至少3个字符|修改后重试|
|登录密码过短|行内错误|密码至少8个字符|修改后重试|
|客户姓名为空|行内错误|请输入客户姓名|修改后重试|
|年龄为空|行内错误|请输入年龄|修改后重试|
|年龄不在有效范围|行内错误|请输入有效的年龄1-150|修改后重试|
|年龄非整数|行内错误|年龄必须为整数|修改后重试|
|性别未选择|行内错误|请选择客户性别|修改后重试|
|职业为空|行内错误|请输入客户职业|修改后重试|
|年收入为负数|行内错误|年收入不能为负数|修改后重试|
|月预算为空|行内错误|请输入月预算|修改后重试|
|月预算为零或负数|行内错误|月预算必须大于0|修改后重试|
|未选择关注险种|行内错误|请至少选择一个关注险种|修改后重试|
|保额低于下限|行内错误|保额不能低于1万元|修改后重试|
|保额超过上限|行内错误|保额不能超过1000万元|修改后重试|
|保障期限未选择|行内错误|请选择保障期限|修改后重试|
|已有保单超过20条|行内错误|已有保单不能超过20条|修改后重试|
|网络断开时提交|页面弹窗|网络异常请检查网络连接|检查网络后重试|
|输入内容过长>10000字|行内提示|输入内容过长,请缩短后重试|修改后重试|
|搜索关键词为空|行内提示|请输入搜索关键词|修改后重试|
### 18.8 错误展示规范
|展示方式|适用场景|展示位置|持续时间|是否可关闭|
|--------|--------|--------|--------|:---:|
|Toast 提示(轻提示)|一般性错误、操作成功提示|页面顶部居中 |3秒后自动消失|否|
|行内错误|表单字段验证失败|对应字段下方|持续显示直到修正|否|
|弹窗Modal|严重错误、需用户确认的操作|页面居中遮罩层|用户点击关闭或确认|是|
|错误页面404/500|页面级错误|全屏替换内容|持续显示|点击按钮跳转|
---
## 十九、BaoDan Workflow 节点完整设计
> 产品推荐方案 BaoDan Workflow 的每个节点完整配置,包含输入变量、输出变量、完整 Prompt、条件分支逻辑、错误处理和测试数据。
### 19.1 Workflow 总览
```
[开始] → [节点1:参数校验] → [节点2:检索策略] → [节点3:知识库检索(循环)]
[节点6:格式化输出] ← [节点4:LLM方案生成] ← [节点5:异常处理]
[结束]
```
### 19.2 节点 1参数校验Code Node
**节点配置**
|属性|值|
|------|---|
|节点名称|参数校验|
|节点类型|Code代码执行|
|编程语言|Python3|
**输入变量**
|变量名|类型|来源|必填|
|------|-----|-----|:---:|
|age|string|开始节点|是|
|gender|string|开始节点|是|
|occupation|string|开始节点|是|
|annual_income|string|开始节点|是|
|monthly_budget|string|开始节点|是|
|insurance_types|string|开始节点|是|
|coverage_amount|string|开始节点|是|
|coverage_period|string|开始节点|是|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|validated_params|object|校验通过的参数对象|
|error_msg|string|校验失败时的错误信息(为空表示通过)|
|is_valid|bool|是否校验通过|
**代码逻辑**
```python
import json
def main(age: str, gender: str, occupation: str, annual_income: str,
monthly_budget: str, insurance_types: str, coverage_amount: str,
coverage_period: str) -> dict:
errors = []
# 年龄校验
try:
age_int = int(age)
if not (1 <= age_int <= 150):
errors.append("年龄必须在1-150之间")
except ValueError:
errors.append("年龄必须为整数")
# 性别校验
if gender not in ("male", "female"):
errors.append("性别必须为 male 或 female")
# 职业校验
if not occupation or not occupation.strip():
errors.append("职业不能为空")
# 月预算校验
try:
budget = int(monthly_budget)
if budget <= 0:
errors.append("月预算必须大于0")
except ValueError:
errors.append("月预算必须为正整数")
# 保额校验
try:
amount = int(coverage_amount)
if amount < 10000:
errors.append("保额不能低于1万元")
except ValueError:
errors.append("保额必须为正整数")
# 险种校验
valid_types = ["重疾险", "寿险", "医疗险", "意外险", "年金险", "储蓄险"]
selected = [t.strip() for t in insurance_types.split(",") if t.strip()]
if not selected:
errors.append("请至少选择一个险种")
for t in selected:
if t not in valid_types:
errors.append(f"无效的险种: {t}")
if errors:
return {
"validated_params": {},
"error_msg": "; ".join(errors),
"is_valid": False
}
return {
"validated_params": {
"age": age_int,
"gender": gender,
"occupation": occupation.strip(),
"annual_income": int(annual_income) if annual_income else 0,
"monthly_budget": budget,
"insurance_types": selected,
"coverage_amount": amount,
"coverage_period": coverage_period
},
"error_msg": "",
"is_valid": True
}
```
**错误处理**:当 `is_valid = False` 时,直接跳转到结束节点,输出 `error_msg` 作为错误提示。
### 19.3 节点 2检索策略生成Code Node
**输入变量**
|变量名|类型|来源|
|------|-----|-----|
|validated_params|object|节点1输出|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|search_queries|array|检索关键词列表每项含险种和query|
**代码逻辑**
```python
def main(validated_params: dict) -> dict:
queries = []
age = validated_params["age"]
for insurance_type in validated_params["insurance_types"]:
query = f"{insurance_type} 产品条款 保额 费率 {age}岁"
queries.append({
"险种": insurance_type,
"query": query
})
return {"search_queries": queries}
```
### 19.4 节点 3知识库检索Knowledge Retrieval Node
**节点配置**
|属性|值|
|------|---|
|节点名称|知识库检索|
|节点类型|Knowledge Retrieval知识库检索|
|关联知识库|所有险种知识库(重疾险/寿险/医疗险/意外险/年金险/储蓄险)|
|检索模式|混合检索(向量 + 全文)|
|Top-K|每个查询返回 5 个最相关片段|
|Score 阈值|0.6(低于此分数的片段被过滤)|
**输入变量**
|变量名|类型|来源|
|------|-----|-----|
|query|string|从 search_queries 数组中提取的检索词|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|retrieved_docs|array|检索到的文档片段列表,含文档名、内容、分数、来源|
**节点行为**
- 对每个险种分别执行一次检索
- 每次检索返回 Top-5 相关片段
- 所有检索结果合并后传递给下游节点
### 19.5 节点 4LLM 方案生成LLM Node
**节点配置**
|属性|值|
|------|---|
|节点名称|方案生成|
|节点类型|LLM|
|模型|deepseek-chat|
|Temperature|0.7|
|Top P|0.9|
|Max Tokens|8192|
**输入变量**
|变量名|类型|来源|
|------|-----|-----|
|validated_params|object|节点1输出|
|retrieved_docs|array|节点3输出|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|recommendation|string|生成的三套方案Markdown 格式)|
**完整 Prompt**
```
你是一名资深保险方案规划师,拥有 10 年保险行业经验。你的任务是根据客户的个人信息和检索到的保险产品条款,为客户量身定制保险产品推荐方案。
## 客户信息
- 年龄:{{validated_params.age}} 岁
- 性别:{{validated_params.gender}}
- 职业:{{validated_params.occupation}}
- 年收入:{{validated_params.annual_income}} 元
- 月预算:{{validated_params.monthly_budget}} 元
- 关注险种:{{validated_params.insurance_types}}
- 保额目标:{{validated_params.coverage_amount}} 元
- 保障期限:{{validated_params.coverage_period}}
## 检索到的产品信息
{{retrieved_docs}}
## 输出要求
请严格按照以下格式生成三套方案(基础方案 / 均衡方案 / 全面方案),每套方案需独立完整:
### 基础方案(年保费约 XXXX 元)
|产品名称|所属保险公司|险种|保额|年保费|推荐理由|
|---------|-----------|-----|-----|------|--------|
|XX重疾险|XX人寿|重疾险|30万|2400元|35岁男性投保性价比高覆盖120种重疾|
方案总结2-3句话说明基础方案的特点和适用人群
### 均衡方案(年保费约 XXXX 元)
(同上格式)
方案总结2-3句话
### 全面方案(年保费约 XXXX 元)
(同上格式)
方案总结2-3句话
## 重要规则
1. **保费数据必须来自检索到的产品信息,绝对不可编造**
2. 如果某险种在知识库中没有匹配到合适产品,在该险种下标注"暂无合适产品推荐,请咨询相关保险公司"
3. 三套方案的总年保费不得超过客户月预算 x 12
4. 基础方案侧重核心保障,保费最低;均衡方案保障适中;全面方案覆盖最广
5. 优先推荐性价比高的产品
6. 每款产品的推荐理由控制在 1-2 句话,突出核心卖点
7. 最后附上免责声明:"以上方案仅供参考,具体保障内容以保险合同条款为准。投保前请仔细阅读产品条款。"
```
### 19.6 节点 5异常处理Code Node
**输入变量**
|变量名|类型|来源|
|------|-----|-----|
|is_valid|bool|节点1输出|
|error_msg|string|节点1输出|
|recommendation|string|节点4输出可能为空|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|final_output|string|最终输出内容|
|is_success|bool|是否成功|
**代码逻辑**
```python
def main(is_valid: bool, error_msg: str, recommendation: str) -> dict:
if not is_valid:
return {
"final_output": f"参数校验失败:{error_msg}",
"is_success": False
}
if not recommendation or not recommendation.strip():
return {
"final_output": "未能生成推荐方案,请尝试调整筛选条件后重新提交。",
"is_success": False
}
return {
"final_output": recommendation,
"is_success": True
}
```
### 19.7 节点 6格式化输出Code Node
**输入变量**
|变量名|类型|来源|
|------|-----|-----|
|final_output|string|节点5输出|
**输出变量**
|变量名|类型|说明|
|------|-----|-----|
|recommendation|string|格式化后的方案文本|
**代码逻辑**
```python
import re
def main(final_output: str) -> dict:
# 去除多余空行保留最多2个连续换行
text = re.sub(r'\n{3,}', '\n\n', final_output)
# 去除首尾空白
text = text.strip()
return {"recommendation": text}
```
### 19.8 条件分支逻辑
```
节点1(参数校验)
├─ is_valid = True → 节点2(检索策略) → 节点3(知识库检索) → 节点4(LLM生成) → 节点5(异常处理) → 节点6(格式化) → 结束
└─ is_valid = False → 节点5(异常处理) → 节点6(格式化) → 结束
```
### 19.9 节点失败时的行为
|失败节点|错误处理|输出内容|
|---------|--------|--------|
|节点1参数校验|直接跳转节点5|返回 error_msg |
|节点2检索策略|BaoDan 自动重试1次失败则跳转节点5|返回"检索策略生成失败"|
|节点3知识库检索|BaoDan 自动重试1次失败则跳转节点5|返回"知识库检索失败,请稍后重试"|
|节点4LLM生成|BaoDan 自动重试1次失败则跳转节点5|返回"方案生成失败,请稍后重试"|
|节点5/6处理节点|BaoDan 自动重试1次|返回"系统处理异常"|
### 19.10 测试数据与预期输出
**测试用例 1标准场景**
输入:
```json
{
"age": "35",
"gender": "male",
"occupation": "软件工程师",
"annual_income": "300000",
"monthly_budget": "2000",
"insurance_types": "重疾险,医疗险",
"coverage_amount": "500000",
"coverage_period": "终身"
}
```
预期输出:
- 三套方案(基础/均衡/全面)
- 每套方案含产品表格(产品名/保司/险种/保额/保费/推荐理由)
- 基础方案总年保费 <= 24000 元2000 x 12
- 不得包含编造的产品信息
**测试用例 2边界 - 低预算**
输入:
```json
{
"age": "25",
"gender": "female",
"occupation": "教师",
"annual_income": "80000",
"monthly_budget": "500",
"insurance_types": "意外险",
"coverage_amount": "50000",
"coverage_period": "10年"
}
```
预期输出:基础方案总年保费 <= 6000 元
**测试用例 3异常 - 无效参数**
输入:
```json
{
"age": "-1",
"gender": "unknown",
"occupation": "",
"annual_income": "0",
"monthly_budget": "0",
"insurance_types": "",
"coverage_amount": "0",
"coverage_period": ""
}
```
预期输出:返回参数校验失败错误信息
---
## 二十、从零到运行部署清单
> 从一台全新的 Linux 服务器到系统完全可用的完整部署步骤,每一步包含具体命令和验证方法。适用于 Ubuntu 22.04 LTS。
### 20.1 服务器环境准备
**最低配置**4核CPU / 8GB内存 / 100GB SSD / 公网IP
**第一步:系统更新**
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git vim ufw
```
**第二步:设置时区**
```bash
sudo timedatectl set-timezone Asia/Shanghai
```
**第三步:配置防火墙**
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status
```
验证:`sudo ufw status` 应显示 22、80、443 端口已允许。
**第四步:安装 Docker**
```bash
# 安装 Docker
curl -fsSL https://get.docker.com | sudo bash
# 启动 Docker 并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker
# 验证
docker --version
# 预期输出Docker version 24.x.x 或更高
```
**第五步:安装 Docker Compose**
```bash
# 安装 Docker Compose 插件
sudo apt install -y docker-compose-plugin
# 验证
docker compose version
# 预期输出Docker Compose version v2.x.x
```
### 20.2 BaoDan 部署
**第一步:克隆 BaoDan 仓库**
```bash
cd /opt
sudo git clone https://github.com/langgenius/baodan.git
cd /opt/baodan/docker
sudo cp .env.example .env
```
**第二步:修改 BaoDan 环境变量**
```bash
sudo vim .env
```
修改以下关键配置:
```bash
# 数据库配置
DB_USERNAME=postgres
DB_PASSWORD=your_strong_db_password_here
DB_HOST=db
DB_PORT=5432
DB_DATABASE=baodan
# Redis 配置
REDIS_PASSWORD=your_strong_redis_password_here
# 密钥配置(随机生成,勿用默认值)
SECRET_KEY=随机生成的64位字符串
INIT_PASSWORD=your_admin_password_here
# 外部访问地址
CONSOLE_WEB_URL=https://your-domain.com
SERVICE_API_URL=https://your-domain.com
APP_WEB_URL=https://your-domain.com
```
生成随机密钥:
```bash
openssl rand -base64 42
```
**第三步:启动 BaoDan**
```bash
sudo docker compose up -d
```
等待所有容器启动完成(约 2-5 分钟):
```bash
sudo docker compose ps
```
验证:所有容器状态应为 `running`
**第四步:验证 BaoDan 访问**
浏览器打开 `http://你的服务器IP:3000`,应看到 BaoDan 欢迎页面。
### 20.3 PostgreSQL 配置
BaoDan 的 Docker Compose 已包含 PostgreSQL。如需在同一实例使用自研表
**第一步:进入 PostgreSQL**
```bash
sudo docker compose exec db psql -U postgres
```
**第二步:创建自研数据库**
```sql
CREATE DATABASE insurance_bot OWNER postgres;
\c insurance_bot
```
**第三步:创建自研表结构**
```sql
-- 企微用户映射表
CREATE TABLE wecom_user_mapping (
id SERIAL PRIMARY KEY,
wecom_userid VARCHAR(64) NOT NULL UNIQUE,
baodan_user_id VARCHAR(64) NOT NULL,
username VARCHAR(128),
department VARCHAR(128),
role VARCHAR(32) DEFAULT 'sales',
status VARCHAR(16) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
last_active_at TIMESTAMP
);
CREATE UNIQUE INDEX idx_wecom_userid ON wecom_user_mapping(wecom_userid);
CREATE INDEX idx_baodan_user_id ON wecom_user_mapping(baodan_user_id);
-- 推荐方案记录表
CREATE TABLE recommendation_records (
id SERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
customer_name VARCHAR(64),
customer_age SMALLINT,
customer_gender VARCHAR(8),
health_status VARCHAR(32),
occupation VARCHAR(64),
annual_income INTEGER,
monthly_budget INTEGER,
insurance_types TEXT,
coverage_amount INTEGER,
coverage_period VARCHAR(32),
existing_policies TEXT,
generated_plan TEXT,
plan_variants TEXT,
baodan_task_id VARCHAR(64),
status VARCHAR(16) DEFAULT 'pending',
error_message TEXT,
created_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP
);
CREATE INDEX idx_rec_user_id ON recommendation_records(user_id);
CREATE INDEX idx_rec_status ON recommendation_records(status);
CREATE INDEX idx_rec_created_at ON recommendation_records(created_at);
-- 系统操作日志表
CREATE TABLE system_operation_logs (
id SERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL,
target_type VARCHAR(32),
target_id VARCHAR(64),
detail JSONB,
ip VARCHAR(45),
user_agent VARCHAR(256),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_sol_user_id ON system_operation_logs(user_id);
CREATE INDEX idx_sol_action ON system_operation_logs(action);
CREATE INDEX idx_sol_created_at ON system_operation_logs(created_at);
```
验证:`\dt` 应显示 3 张表已创建。
### 20.4 Redis 配置
BaoDan 的 Docker Compose 已包含 Redis。自研后端共用同一个 Redis 实例即可。
验证:
```bash
sudo docker compose exec redis redis-cli -a your_redis_password ping
# 预期输出PONG
```
### 20.5 后端部署
**第一步:安装 Python 环境**
```bash
# 安装 Python 3.12
sudo apt install -y python3.12 python3.12-venv python3-pip
# 验证
python3.12 --version
# 预期输出Python 3.12.x
```
**第二步:创建项目目录并配置虚拟环境**
```bash
mkdir -p /opt/insurance-bot/backend
cd /opt/insurance-bot/backend
# 创建虚拟环境
python3.12 -m venv venv
source venv/bin/activate
```
**第三步:安装依赖**
```bash
pip install fastapi uvicorn[standard] psycopg2-binary redis python-jose[cryptography] passlib[bcrypt] python-multipart httpx pydantic
```
**第四步:配置环境变量**
```bash
cat > /opt/insurance-bot/backend/.env << 'EOF'
# 数据库
DATABASE_URL=postgresql://postgres:your_db_password@db:5432/insurance_bot
# Redis
REDIS_URL=redis://:your_redis_password@redis:6379/0
# JWT
JWT_SECRET_KEY=your_jwt_secret_key_here
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=120
# BaoDan
BAODAN_API_BASE_URL=http://api:5001
BAODAN_CHAT_API_KEY=app-xxxxxxxxxxxx
BAODAN_WORKFLOW_API_KEY=app-yyyyyyyyyyyy
# 企微
WECOM_CORP_ID=ww1234567890abcdef
WECOM_AGENT_ID=1000002
WECOM_AGENT_SECRET=your_agent_secret_here
WECOM_TOKEN=your_callback_token
WECOM_ENCODING_AES_KEY=your_encoding_aes_key
# 服务器
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
EOF
```
**第五步:编写 Dockerfile**
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
**第六步:启动后端服务**
```bash
# 如果使用 Docker
sudo docker build -t insurance-bot-backend .
sudo docker run -d \
--name insurance-backend \
--restart always \
--env-file .env \
--network docker_default \
-p 8000:8000 \
insurance-bot-backend
# 或者直接运行
cd /opt/insurance-bot/backend
source venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8000
```
验证:
```bash
curl http://localhost:8000/docs
# 预期输出Flask Swagger UI 页面
```
### 20.6 前端部署
**第一步:安装 Node.js**
```bash
# 安装 Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs
# 验证
node --version # v22.x.x
npm --version # 10.x.x
```
**第二步:构建前端**
```bash
cd /opt/insurance-bot/frontend
# 安装依赖
npm install
# 构建生产版本
npm run build
# 产物在 dist/ 目录
```
**第三步:配置 Nginx**
```bash
sudo apt install -y nginx
```
创建 Nginx 配置文件:
```bash
sudo vim /etc/nginx/sites-available/insurance-bot
```
```nginx
server {
listen 80;
server_name your-domain.com;
# 重定向到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 证书(参见 20.8
ssl_certificate /etc/ssl/your-domain.com.pem;
ssl_certificate_key /etc/ssl/your-domain.com.key;
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 你的前端Vue 3 产品推荐页面)
location /app {
alias /opt/insurance-bot/frontend/dist;
try_files $uri $uri/ /app/index.html;
}
# 你的后端 API
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
# BaoDan 后台 + 对话页面
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# BaoDan API
location /v1/ {
proxy_pass http://127.0.0.1:5001/v1/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
启用配置:
```bash
sudo ln -s /etc/nginx/sites-available/insurance-bot /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
```
验证:`sudo nginx -t` 应输出 `syntax is ok``test is successful`
### 20.7 企微后台配置
参见第十六章《企微应用配置操作手册》完整步骤。关键信息确认清单:
|配置项|获取方式|配置位置|
|------|---------|--------|
|CorpID|企微管理后台 → 我的企业 | 后端 .env |
|AgentID|应用详情页 | 后端 .env |
|AgentSecret|应用详情 → Secret | 后端 .env |
|Token|接收消息设置页 | 后端 .env |
|EncodingAESKey|接收消息设置页 | 后端 .env |
|回调URL|`https://your-domain.com/api/wecom/callback`|企微后台设置|
|可信IP|服务器公网IP|企微后台设置|
|OAuth域名|`your-domain.com`|企微管理后台 → 授权登录|
### 20.8 域名 + HTTPS 配置
**第一步:购买域名并解析**
在域名服务商处添加 DNS 记录:
|记录类型|主机记录|记录值|
|--------|--------|------|
|A|@|服务器公网IP|
|A|chat|服务器公网IP|
|A|api|服务器公网IP|
**第二步:申请 SSL 证书**
使用 Let's Encrypt 免费证书:
```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d chat.your-domain.com -d api.your-domain.com
```
按照提示输入邮箱并同意条款。Certbot 会自动修改 Nginx 配置并安装证书。
**第三步:设置自动续期**
```bash
sudo crontab -e
# 添加以下行每天凌晨2点检查续期
0 2 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
```
验证:浏览器访问 `https://your-domain.com`,应显示安全锁标志。
### 20.9 验证清单
|步骤|验证命令/操作|预期结果|通过标准|
|-----|-------------|--------|--------|
|1. Docker 运行 | `docker compose ps` | 所有容器 running | 无 exited 容器 |
|2. PostgreSQL 连接 | `docker compose exec db psql -U postgres -c '\l'` | 显示 baodan 和 insurance_bot 数据库 | 两个库都存在 |
|3. Redis 连接 | `docker compose exec redis redis-cli ping` | PONG | 返回 PONG |
|4. BaoDan 后台访问 | 浏览器打开 `http://IP:3000` | BaoDan 欢迎页面 | 页面正常加载 |
|5. BaoDan 管理员登录 | 使用 15.1 设置的账号登录 | 进入 BaoDan 后台 | 登录成功 |
|6. DeepSeek 模型配置 | BaoDan 设置 → 模型供应商 | 显示 DeepSeek 且状态正常 | 测试连通成功 |
|7. 知识库文档 | BaoDan 知识库页面 | 文档状态为"可用" | 无 processing/failed |
|8. 对话功能 | BaoDan 预览对话 | 回答基于知识库内容 | 回答准确 |
|9. 后端 API | `curl http://localhost:8000/docs` | Swagger UI 页面 | 页面正常加载 |
|10. 前端页面 | 浏览器打开 `https://your-domain.com/app` | Vue 应用页面 | 页面正常加载 |
|11. HTTPS | 浏览器访问 `https://your-domain.com` | 安全锁标志 | 证书有效 |
|12. 企微机器人 | 在企微中发送消息给机器人 | 收到 AI 回复 | 回复在 10 秒内 |
|13. 企微 OAuth | 在企微内置浏览器访问登录页 | 跳转授权后登录成功 | 进入系统主页 |
|14. 产品推荐 | 填写表单并提交 | 生成三套方案 | 方案内容合理 |
|15. 权限验证 | 用销售人员账号访问管理页面 | 被拒绝访问 | 显示权限不足提示 |
---
## 二十一、角色-功能权限矩阵
### 21.1 页面访问权限
| 页面 | 超级管理员 | 管理员 | 销售主管 | 销售人员 | 客户 |
|------|:---:|:---:|:---:|:---:|:---:|
| /login | Y | Y | Y | Y | Y |
| /chat | Y | Y | Y | Y | Y |
| /recommend | Y | Y | Y | Y | Y |
| /recommend/history | Y | Y | Y | Y | Y |
| /admin/knowledge-base | Y | Y | - | - | - |
| /admin/logs/chat | Y | Y | 仅本组 | 仅自己 | - |
| /admin/logs/system | Y | - | - | - | - |
| /admin/users | Y | - | - | - | - |
| /admin/roles | Y | - | - | - | - |
| /admin/llm-configs | Y | - | - | - | - |
| /admin/prompts | Y | Y | - | - | - |
| /admin/stats | Y | Y | 仅本组 | - | - |
### 21.2 数据权限规则
| 角色 | 可见数据范围 |
|------|------------|
| 超级管理员 | 全部数据 |
| 管理员 | 本部门数据 |
| 销售主管 | 本组数据 |
| 销售人员 | 仅自己的数据 |
| 客户 | 仅自己的对话 |
## 二十二、端到端业务流程
```
[开始]
v
[1. 用户登录]
| 企微 OAuth / 账密登录
| -> 生成 JWT Token
v
[2. 进入对话页]
| BaoDan WebApp 加载iframe 嵌入)
| 显示会话列表(历史对话)
v
[3. 选择检索范围](可选)
| 选择险种筛选器(重疾/寿险/医疗...
| 选择保司筛选器XX人寿/太平洋...
v
[4. 用户输入问题]
| 回车发送 / 点击发送按钮
| 前端校验:非空、长度限制
v
[5. 发送请求]
| -> BaoDan Chat APISSE 流式)
| -> BaoDan 执行 RAG 检索(知识库 -> 向量搜索 -> Top-K 段落)
| -> LLM 基于检索结果生成回答
v
[6. 流式展示回答]
| 逐字打印 AI 回复
| 显示引用来源文档
v
[7. 用户评价]
| 点赞/点踩 -> BaoDan 记录
| 或纠错 -> 弹窗输入 -> 提交到后端
v
[结束]
```
### 14.2 产品推荐流程(用户端)
```
[开始]
v
[1. 进入产品推荐页]
| 显示空白表单
v
[2. 填写客户信息]
| 基础信息(姓名/年龄/性别/健康/职业)
| 选择关注险种(多选 -> 动态显示对应字段)
| 设置保障需求(保额滑块/预算输入)
| 选择保障期限
| (可选)录入已有保单
v
[3. 前端校验]
| 必填项检查、数值范围检查
| 不通过 -> 红色提示
| 通过 -> 继续
v
[4. 点击"生成方案"]
| 前端显示 loading + 进度提示
| -> POST /api/recommend/generate
| -> 后端调 BaoDan Workflow API
v
[5. BaoDan Workflow 执行]
| 参数校验 -> 知识库检索 -> LLM 生成方案
| 返回 2-3 套方案(基础/均衡/全面)
v
[6. 方案展示]
| Markdown 渲染方案报告
| 产品对比表格 + 推荐理由
v
[7. 用户操作]
| a. 满意 -> 打印导出 / 分享链接
| b. 不满意 -> 重新生成
| c. 需修改 -> 进入编辑模式
v
[8. 保存/导出]
| 方案自动保存到数据库
| 用户可选择浏览器打印为 PDF
v
[结束]
```
### 14.3 知识库文档入库流程(管理员端)
```
[开始]
v
[1. 管理员进入知识库管理页]
| 查看当前文档列表和状态
v
[2. 上传文档]
| 选择文件MD/Word/TXT
| 选择分类标签(险种/保司)
| 可多文件批量上传
| -> POST /kb/documents/upload
v
[3. 异步处理流水线]
| a. 格式转换Word -> 纯文本)
| b. 文档分段(按 Markdown 标题切分800 tokens/段)
| c. 向量化DeepSeek Embedding 调用)
| d. 写入向量数据库
v
[4. 处理完成]
| 文档状态 -> completed
| 文档可被检索
v
[5. 异常处理](如失败)
| 文档状态 -> failed
| 显示错误原因
| 管理员可点击"重试"
v
[结束]
```
### 14.4 企微机器人问答流程
```
[用户在企微中操作]
v
[1. 发送消息]
| 单聊:直接发消息给机器人
| 群聊:@机器人 + 问题内容
v
[2. 企微服务器推送]
| -> POST /api/wecom/callback
| 加密 XML 消息体
v
[3. 后端接收]
| a. 验证签名
| b. 解密消息
| c. 立即返回 "success"<5秒
v
[4. 异步处理]Background Task
| a. 解析消息类型
| - 文本 -> 继续
| - 非文本 -> 回复"暂不支持"
| b. 提取问题内容
| - 群聊:去掉"@机器人"前缀
| c. 调用 BaoDan Chat API
| d. 等待 AI 回复
v
[5. 回复消息]
| 单聊 -> wecom send message API
| 群聊 -> wecom appchat send API
| 超长消息 -> 自动分段
v
[结束]
```
---
## 二十三、状态机定义
### 15.1 推荐方案状态机
```
+----------+
| pending | <-- 用户提交请求
+----+-----+
| Workflow 开始执行
v
+----------+
|processing| <-- BaoDan Workflow 运行中
+----+-----+
+--------+--------+
||
v v
+--------+ +--------+
| done | | failed | <-- 超时或错误
+--------+ +--------+
|||
| | 用户点击"重新生成"
+--------+--------+
v
+----------+
| pending | <-- 重新提交
+----------+
```
状态转换规则:
|当前状态|触发条件|目标状态|操作|
|---------|---------|---------|------|
|pending|Workflow 开始执行|processing|开始异步任务|
|processing|Workflow 返回成功|done|保存 generated_plan|
|processing|Workflow 返回失败|failed|记录 error_message|
|processing|超时(>120秒|failed|记录 "生成超时"|
|failed|用户点击重新生成|pending|重置状态,重新提交|
|done|用户点击重新生成|pending|重置状态,重新提交|
### 15.2 知识库文档处理状态机
```
+------------+
| uploaded | <-- 文件上传完成
+-----+------+
v
+------------+
| converting | <-- 格式转换中Word->文本)
+-----+------+
v
+------------+
| chunking | <-- 文档分段中
+-----+------+
v
+------------+
| embedding | <-- 向量化中(调 DeepSeek Embedding
+-----+------+
+--------+--------+
||
v v
+----------+ +--------+
|completed | | failed | <-- 任一步骤出错
+----------+ +--------+
|||
| | 管理员点击"重试"
+--------+--------+
v
+------------+
| uploaded | <-- 回到起点
+------------+
```
状态转换规则:
|当前状态|触发条件|目标状态|操作|
|---------|---------|---------|------|
|uploaded|开始处理|converting|调用格式转换|
|converting|转换成功|chunking|开始分段|
|chunking|分段完成|embedding|开始向量化|
|embedding|向量化完成|completed|文档可被检索|
|任一环节出错|异常|failed|记录错误原因|
|failed|点击"重试"|uploaded|重新开始处理|
### 15.3 企微消息处理状态机
```
+-----------+
| received | <-- 收到企微推送
+-----+-----+
v
+-----------+
| decrypting| <-- 解密消息
+-----+-----+
+-----+-----+
||
v v
+---------+ +----------+
| success | | error | <-- 解密失败
+---------+ +----------+
v
+-----------+
| processing| <-- 异步调用 BaoDan
+-----+-----+
+----+----+
||
v v
+------+ +--------+
|done||failed|
+------+ +--------+
|||
v v
+---------+ +----------+
| replied | | no_reply | <-- 回复失败时记录日志
+---------+ +----------+
```
---
## 二十四、数据安全与合规
### 16.1 敏感数据分类
|数据类型|敏感级别|包含字段|保护措施|
|---------|:---:|---------|---------|
|客户个人信息|高|姓名、年龄、健康状况、收入|不在日志中明文记录;数据库加密存储|
|企微用户信息|中|userid、姓名、部门|仅在后端使用,不暴露给前端|
|API Key|高|DeepSeek Key、企微 Secret|AES 加密存储,前端脱敏显示|
|对话内容|中|用户问题、AI 回答|存储在 BaoDan 数据库,有访问权限控制|
|JWT Token|高|用户认证令牌|HTTPS 传输2 小时过期|
### 16.2 数据保护规则
|规则|说明|实现方式||
|------|------|---------|--------|
|传输加密|所有数据传输走 HTTPS|Nginx 配置 SSL 证书||
|存储加密|API Key 等敏感配置加密|AES-256 加密后入库||
|日志脱敏|日志中不记录完整敏感信息|日志截断:问题/回答记录前 50 字符||
|前端脱敏|API Key 在页面上显示为 `sk-xxxx****xxxx`|后端返回时自动脱敏||
|访问控制|不同角色只能访问授权数据|接口层权限校验||
|数据隔离|销售人员只能看自己的数据|查询时自动注入 user_id 过滤条件||
### 16.3 合规要求
|要求|说明|
|------|------|
|个人信息保护法|收集客户个人信息需告知目的,最小必要原则|
|保险行业监管|AI 推荐方案需附免责声明,不可替代专业建议|
|对话记录留存|保险行业建议保留至少 10 年对话记录|
|知识库版本溯源|修改知识库需保留版本记录,可追溯||
---
## 二十五、备份与恢复策略
### 17.1 备份范围
|备份对象|存储位置|重要性|备份频率|
|---------|---------|:---:|---------|
|PostgreSQL 数据库|远程存储|高|每日凌晨 2 点|
|BaoDan 向量数据库|远程存储|高|每日凌晨 2 点|
|知识库原始文件9GB MD|远程存储|高|每周一次(数据变化少)|
|用户上传的文件|本地 + 远程|中|每日增量|
|系统配置文件|Git 仓库|中|代码提交即备份|
|日志文件|本地|低|保留 90 天后自动清理|
### 17.2 恢复指标
|指标|目标值|说明||
|------|--------|------|--------|
|RPO恢复点目标|< 24 小时|最多丢失一天的数据||
|RTO恢复时间目标|< 2 小时|从故障到服务恢复的时间||
|备份验证|每月一次|恢复到测试环境验证备份可用||
### 17.3 恢复流程
```
[数据库恢复]
1. 从备份存储下载最近的数据库备份
2. 停止 BaoDan 服务
3. 恢复 PostgreSQL 数据库
4. 启动 BaoDan 服务
5. 验证知识库检索正常
6. 通知管理员恢复完成
[全量恢复]
1. 重新部署 BaoDanDocker 或源码)
2. 恢复 PostgreSQL 数据库
3. 恢复知识库文件
4. 重新启动所有服务
5. 全面功能验证
```
---
## 二十六、部署架构
### 18.1 服务器拓扑
```
[互联网]
[域名 + HTTPS]
+------v------+
| Nginx | 端口: 80/443
|反向代理|
+------+------+
+--------------+--------------+
|||
+------v------+ +----v----+ +-------v-------+
|BaoDan Web||BaoDan||你的前端|
|(Next.js)||API||(Vue 3)|
|端口:3000||端口||端口:3000|
+-------------+ | :5001 | +---------------+
+---------+
||||
+------v--------------v--------------v------+
|PostgreSQL||
|端口: 5432|
|(BaoDan 数据 + 你的自研表)|
+---------------------------------------------+
|||
+------v------+ +----v----+ +-------v-------+
|Redis||Weaviate||Celery Worker|
|端口:6379||端口||(后台任务)|
+-------------+ | :8080 | +---------------+
+---------+
+-------------------+
|BaoDan 服务(含 insurance 模块)|||||
|端口: 8000|
|(企微回调/推荐接口)|
+-------------------+
```
### 18.2 端口规划
|服务|端口|对外暴露|说明|
|------|:---:|:---:|------|
|Nginx|80, 443||HTTPS 入口|
|BaoDan Web|3000|Nginx 转发|管理后台 + 对话 WebApp|
|BaoDan API|5001|内部|BaoDan 核心 API|
|BaoDan 服务 insurance 模块|5001|Nginx 转发|企微回调 + 推荐接口|
|PostgreSQL|5432|内部|数据库|
|Redis|6379|内部|缓存|
|Weaviate|8080|内部|向量数据库|
|你的 Vue 前端|5173|Nginx 转发|产品推荐页面|
### 18.3 Nginx 配置要点
```nginx
# HTTPS 证书
ssl_certificate /etc/ssl/your-domain.com.pem;
ssl_certificate_key /etc/ssl/your-domain.com.key;
# BaoDan 后台 + 对话页面
location / {
proxy_pass http://127.0.0.1:3000;
}
# BaoDan API
location /v1/ {
proxy_pass http://127.0.0.1:5001/v1/;
}
# 你的前端(产品推荐页面等)
location /app/ {
proxy_pass http://127.0.0.1:5173/;
}
# 你的后端 API企微回调 + 推荐接口)
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
}
# 企微回调路径(需要较长超时)
location /api/wecom/callback {
proxy_pass http://127.0.0.1:8000/api/wecom/callback;
proxy_read_timeout 120s; # 企微回调需要较长超时
}
```
### 18.4 域名规划
|域名/路径|指向|说明|
|----------|------|------|
|chat.your-domain.com|Nginx -> BaoDan Web|对话页面入口|
|chat.your-domain.com/admin|Nginx -> BaoDan Web|管理后台入口|
|chat.your-domain.com/app|Nginx -> Vue 前端|产品推荐页面|
|chat.your-domain.com/api/wecom/callback|Nginx -> BaoDan|企微机器人回调 URL|
|api.your-domain.com|Nginx -> BaoDan|后端 API 入口|
---
## 二十七、BaoDan 功能映射表
> 每项需求对应 BaoDan 的哪个功能,开发时可直接对照。
|需求编号|功能点|BaoDan 对应功能|配置位置|
|---------|--------|-------------|---------|
|1.1.1|文字输入框|WebApp 内置输入框|默认|
|1.1.2|流式输出|WebApp Streaming 模式|应用设置 -> 对话速度 -> 流式|
|1.1.3|多轮对话上下文|WebApp 会话管理|应用设置 -> 上下文窗口长度|
|1.1.4|会话管理|WebApp 侧边栏|默认|
|1.1.5|会话标题自动命名|WebApp 自动标题|应用设置 -> 自动话题转换|
|1.2.1|Markdown 渲染|WebApp 内置 Markdown 渲染|默认|
|1.2.3|来源引用标注|WebApp 引用显示|应用设置 -> 引用与归属|
|1.3.1-3|检索范围控制|知识库选择器|多知识库配置|
|1.4.1|点赞/点踩|WebApp 反馈功能|应用设置 -> 对话反馈|
|3.1.1|批量上传文档|知识库上传|知识库 -> 上传文档|
|3.1.2|文档列表|知识库文档管理|知识库 -> 文档列表|
|3.2.1|处理流水线状态|文档处理状态|知识库 -> 文档状态|
|3.2.2|失败原因与重试|文档错误详情|知识库 -> 文档详情|
|3.4.1|检索效果测试|召回测试|知识库 -> 召回测试|
|4.1.1|对话记录查看|对话日志|日志 -> 对话日志|
|6.1.1|多模型配置|模型供应商设置|设置 -> 模型供应商|
|6.2.1|Prompt 编辑|提示词编排|应用 -> 提示词编排|
|2.2.1|AI 匹配产品组合|Workflow 编排|应用 -> 工作室 -> 创建工作流|
---
## 二十八、全局错误码定义
### 19.1 统一响应格式
所有自研后端接口返回统一 JSON 格式:
```json
{
"code": 0,
"message": "success",
"data": { ... }
}
```
### 19.2 错误码表
|错误码|说明|HTTP 状态码|处理建议|
|:---:|------|:---:|---------|
|0|成功|200|-|
|1001|参数错误(缺少必填项/类型错误)|200|检查请求参数|
|1002|未授权(无 Token 或 Token 无效)|200|重新登录|
|1003|Token 已过期|200|调用 refresh-token 接口|
|1004|权限不足(角色无权访问)|200|联系管理员提升权限|
|1005|资源不存在(如用户/方案/文档 ID 不存在)|200|检查资源 ID|
|2001|BaoDan API 调用失败|200|检查 BaoDan 服务状态和 API Key|
|2002|BaoDan API 调用超时(>60 秒)|200|重试或检查 BaoDan 服务负载|
|2003|BaoDan Workflow 执行失败|200|检查 Workflow 配置|
|2004|BaoDan Workflow 执行超时(>120 秒)|200|简化输入参数或检查 Workflow|
|3001|企微 API 调用失败|200|检查企微 CorpID/Secret 配置|
|3002|企微 Access Token 获取失败|200|检查网络和企微配置|
|3003|企微消息回复失败|200|检查 AgentID 和用户权限|
|3004|企微 OAuth code 已过期|200|让用户重新授权|
|3005|企微用户未授权/不在白名单|200|联系管理员开通权限|
|4001|文件上传失败|200|检查文件格式和大小|
|4002|文件格式不支持|200|仅支持 MD/Word/TXT|
|4003|文件大小超限|200|单文件最大 50MB|
|5001|数据库操作失败|200|检查数据库连接|
|5002|Redis 操作失败|200|检查 Redis 连接|
|9999|服务器内部错误|500|检查服务器日志|
### 19.3 说明
- 业务异常1xxx-5xxx统一使用 HTTP 200通过 `code` 字段区分成功/失败
- 系统异常9xxx使用对应 HTTP 状态码
- `message` 字段为人类可读的错误描述,前端直接展示给用户
- 生产环境不返回详细的错误堆栈,只在服务端日志中记录
---
## 二十九、页面归属与构建方式
> 明确每个页面由谁提供,开发者知道哪些需要自己写代码,哪些直接用。
### 20.1 页面归属清单
|页面|路径|提供方|构建方式|说明|
|------|------|:---:|---------|------|--------
|登录页|/login|自研|Vue 3 组件|企微 OAuth 跳转 + 账密登录表单|
|对话页|/chat|BaoDan WebApp|iframe 嵌入|直接嵌入 BaoDan 的 Chat WebApp|
|产品推荐页|/recommend|自研|Vue 3 页面|表单 + 结果展示,调后端 API|
|方案历史页|/recommend/history|自研|Vue 3 页面|方案列表 + 筛选,调后端 API|
|知识库管理页|/admin/kb|BaoDan 后台|BaoDan 原生|直接用 BaoDan 知识库管理界面|
|对话日志页|/admin/logs/chat|BaoDan 后台|BaoDan 原生|直接用 BaoDan 日志界面|
|系统日志页|/admin/logs/system|自研|Vue 3 页面|BaoDan 没有系统操作日志|
|用户管理页|/admin/users|自研|Vue 3 页面|企微用户绑定 + 角色分配|
|角色管理页|/admin/roles|自研|Vue 3 页面|权限树勾选配置|
|LLM 配置页|/admin/llm-configs|BaoDan 后台|BaoDan 原生|直接用 BaoDan 模型配置|
|Prompt 管理页|/admin/prompts|BaoDan 后台|BaoDan 原生|直接用 BaoDan 提示词编辑|
|数据统计页|/admin/stats|自研|Vue 3 页面|图表展示ECharts/Chart.js|
|管理后台框架|/admin|自研|Vue 3 组件|左侧导航 + 顶部栏 + 路由|
### 20.2 统计
|类型|页面数|说明|||
|------|:---:|------|--------|--------|
|**BaoDan 直接提供**|4 个|对话页、知识库管理、对话日志、LLM 配置、Prompt 管理|||
|**自研前端**|9 个|登录页、推荐页、历史页、系统日志、用户管理、角色管理、统计页、管理框架|||
|**合计**|13 个|-|||
### 20.3 BaoDan 页面嵌入方式
|BaoDan 页面|嵌入方式|URL 格式|
|----------|---------|---------|
|对话 WebApp|iframe|`http://baodan:3000/chat/{app_id}?user={user_id}`|
|知识库管理|新窗口打开 BaoDan 后台|`http://baodan:3000/datasets`|
|对话日志|新窗口打开 BaoDan 后台|`http://baodan:3000/logs`|
|LLM 配置|新窗口打开 BaoDan 后台|`http://baodan:3000/settings/model`|
|Prompt 管理|新窗口打开 BaoDan 后台|`http://baodan:3000/apps/{app_id}/prompt-engine`|
> 说明知识库管理、日志、LLM 配置、Prompt 管理这 4 个页面目前通过新窗口跳转到 BaoDan 后台操作。如果后续需要深度定制(如加标签管理、编号规则),需要改为自研前端 + 调 BaoDan API。
---
## 三十、BaoDan Workflow 节点详细设计
> 产品推荐方案的 BaoDan Workflow 每个节点的输入、输出、Prompt 定义。
### 21.1 Workflow 总览
```
[开始] -> [节点1:参数校验] -> [节点2:检索策略] -> [节点3:知识库检索]
-> [节点4:LLM方案生成] -> [节点5:格式化输出] -> [结束]
```
### 21.2 各节点定义
#### 节点 1参数校验
|属性|值|
|------|---|
|节点类型|代码节点Code|
|输入|用户提交的 JSON 参数|
|输出|validated_params校验后的参数或 error_msg错误信息|
校验规则:
- age: 必填整数1-150
- gender: 必填,"male" 或 "female"
- occupation: 必填,非空字符串
- insurance_types: 必填,非空数组
- monthly_budget: 必填,正整数
- coverage_amount: 必填,正整数
#### 节点 2检索策略
|属性|值|
|------|---|
|节点类型|代码节点Code|
|输入|validated_params|
|输出|search_queries检索关键词列表|
逻辑:根据用户选择的险种,为每个险种生成一条检索 query
```python
def generate_search_queries(params):
queries = []
for insurance_type in params["insurance_types"]:
query = f"{insurance_type} 产品条款 保额 费率 {params['age']}岁"
queries.append({"险种": insurance_type, "query": query})
return queries
```
#### 节点 3知识库检索
|属性|值|
|------|---|
|节点类型|知识库检索节点Knowledge Retrieval|
|输入|search_queries|
|输出|retrieved_docs检索到的文档片段|
|知识库|关联所有险种相关知识库|
|Top-K|每个险种检索 Top 5|
|相似度阈值|0.6|
#### 节点 4LLM 方案生成
|属性|值|
|------|---|
|节点类型|LLM 节点|
|输入|retrieved_docs + validated_params|
|输出|recommendationMarkdown 格式方案)|
|模型|deepseek-chat|
|Temperature|0.7|
**节点 Prompt**
```
你是一名专业的保险方案规划师。根据以下客户信息和检索到的产品条款,生成保险产品推荐方案。
## 客户信息
- 年龄:{{age}} 岁
- 性别:{{gender}}
- 职业:{{occupation}}
- 年收入:{{annual_income}} 元
- 月预算:{{monthly_budget}} 元
- 关注险种:{{insurance_types}}
- 保额目标:{{coverage_amount}} 元
- 保障期限:{{coverage_period}}
## 检索到的产品信息
{{retrieved_docs}}
## 输出要求
请生成三套方案(基础方案 / 均衡方案 / 全面方案),每套方案包含:
1. 方案名称和总年保费
2. 每款推荐产品的表格:
|产品名称|所属保司|险种|保额|年保费|推荐理由1-2句话|
3. 方案总结2-3句话说明方案特点
4. 免责声明:"以上方案仅供参考,具体保障内容以保险合同条款为准。"
## 注意事项
- 保费数据必须来自检索到的产品信息,不可编造
- 如果某险种在知识库中没有匹配产品,标注"暂无合适产品推荐"
- 总保费不得超过客户月预算 * 12
- 优先推荐性价比高的产品
```
#### 节点 5格式化输出
|属性|值|||||
|------|---|--------|--------|--------|--------|
|节点类型|代码节点Code|||||
|输入|recommendationLLM 输出的 Markdown|||||
逻辑:对 LLM 输出做基本格式清理(去除多余空行、确保 Markdown 格式正确),直接输出给前端渲染。
|模块|总数|高优先级|中优先级|低优先级|
|------|:---:|:---:|:---:|:---:|
|M1 智能问答(用户前端)|16|9|5|2|
|M2 产品推荐(用户前端)|19|9|7|3|
|M3 知识库管理(管理前端)|17|9|5|3|
|M4 留痕与日志(管理前端)|9|4|4|1|
|M5 用户与权限(管理前端)|8|5|2|1|
|M6 系统配置(管理前端)|14|6|5|3|
|M7 数据统计(管理前端)|9|3|4|2|
|企微机器人|5|3|1|1|
|A1 认证鉴权(后端接口)|4|2|2|0|
|A2 智能问答(后端接口)|8|5|2|1|
|A3 方案生成(后端接口)|4|3|0|1|
|A4 知识库(后端接口)|10|7|2|1|
|A5 用户权限(后端接口)|3|2|0|1|
|A6 系统配置(后端接口)|4|2|2|0|
|A7 留痕日志(后端接口)|3|2|1|0|
|A8 统计报表(后端接口)|4|1|3|0|
|**合计**|**137**|**77**|**45**|**15**|
---
## 三十一、功能清单统计
|模块|功能项数|高优先级|中优先级|低优先级|
|------|:---:|:---:|:---:|:---:|
|M1 智能问答(用户前端)|16|9|5|2|
|M2 产品推荐(用户前端)|19|9|7|3|
|M3 知识库管理(管理前端)|17|9|5|3|
|M4 留痕与日志(管理前端)|9|4|4|1|
|M5 用户与权限(管理前端)|8|5|2|1|
|M6 系统配置(管理前端)|14|6|5|3|
|M7 数据统计(管理前端)|9|3|4|2|
|企微机器人|5|3|1|1|
|A1 认证鉴权(后端接口)|4|2|2|0|
|A2 智能问答(后端接口)|8|5|2|1|
|A3 方案生成(后端接口)|4|3|0|1|
|A4 知识库(后端接口)|10|7|2|1|
|A5 用户权限(后端接口)|3|2|0|1|
|A6 系统配置(后端接口)|4|2|2|0|
|A7 留痕日志(后端接口)|3|2|1|0|
|A8 统计报表(后端接口)|4|1|3|0|
|**合计**|**137**|**77**|**45**|**15**|
---
## 三十二、验收标准
|验收项|标准|验证方式|||
|--------|------|---------|--------|--------|
|智能问答准确率|30 个测试问题,准确率 >= 85%|人工测试|||
|产品推荐质量|10 组客户数据,方案合理且产品信息准确|人工评审|||
|企微单聊|10 轮连续对话无错误|手动测试|||
|企微群聊|3 人同时 @机器人 不混淆|手动测试|||
|企微 OAuth|登录流程完整走通|手动测试|||
|H5 适配|手机端可正常使用|真机测试|||
|知识库覆盖|9GB MD 文档全部入库且可检索|BaoDan 后台检查|||
|响应时间|AI 回答 < 15 |计时测试|||
|并发能力|5 人同时使用无明显延迟|手动测试|||
|方案导出|PDF/PPT/Word 可正常下载且排版正确|下载验证|||
|角色权限|不同角色看到不同功能和数据范围|切换账号验证|||
|日志完整性|问答记录和操作日志可查询导出|后台验证|||