# 保险智能客服系统 — 需求文档 > **版本**:V1.0 > **日期**:2026-05-31 > **技术基础**:BaoDan(AI 引擎)+ 自研企微后端 > **开发人数**: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 " } ``` 响应: ```json { "code": 0, "message": "success", "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 7200 } } ``` **A1.1.4 退出登录接口详细设计**: 请求: ```json POST /auth/logout Headers: { "Authorization": "Bearer " } ``` 响应: ```json { "code": 0, "message": "success" } ``` --- ### A2:智能问答接口 #### A2.1 对话接口 |接口编号|方法|URL|说明|优先级||实现方式| |---------|------|-----|------|:---:|--------|--------| |A2.1.1|POST|/chat/message(SSE 流式)|接收问题 + 会话 ID,调用 RAG 检索 + LLM,SSE 流式返回回答与引用来源|高|自研(调 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 " } { "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 " } ``` 响应: ```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 " } ``` 响应: ```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 " } { "rating": "helpful", "comment": null } ``` 或纠错: ```json POST /chat/messages/{id}/feedback Headers: { "Authorization": "Bearer " } { "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 " } { "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 " } { "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 " } ``` 响应(生成中): ```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 " } { "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 " } { "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 " } 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 " } ``` 响应: ```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 " } ``` 响应: ```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 " } ``` 响应: ```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 " } ``` 响应: ```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 配置 CRUD;Key 写入时服务端加密|高|自研(调 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 " } { "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 " } ``` 响应: ```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 " } ``` 响应(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.1,export=csv 时返回 CSV 文件流 **A7.1.3 系统日志接口详细设计**: 请求: ```json GET /admin/logs/system?page=1&page_size=50&action=login&user_id=user-001 Headers: { "Authorization": "Bearer " } ``` 响应: ```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 " } ``` 响应: ```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 " } ``` 响应: ```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 " } ``` 响应: ```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 " } ``` 响应: ```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 自研表结构 #### 表 1:wecom_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 #### 表 2:recommendation_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 #### 表 3:system_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×tamp=xxx&nonce=xxx&echostr=xxx ``` 验证流程: 1. 将 Token、timestamp、nonce、echostr 按字典序排列拼接 2. 对拼接字符串做 SHA1 哈希 3. 比较哈希结果与 msg_signature 4. 验证通过则返回解密后的 echostr ### 8.3 消息接收(POST) 企微用户发消息时推送的加密 XML: ```xml ``` 解密后的消息体(text 类型): ```xml 1348831860 1234567890123456 1000002 ``` 群聊消息额外字段: ```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 Workflow API(产品推荐) | POST http://baodan-api:5001/v1/workflows/run | Headers: Authorization: Bearer |-- 调用 BaoDan Knowledge API(文档管理,可选) | POST http://baodan-api:5001/v1/datasets/{id}/documents | Headers: Authorization: Bearer ``` ### 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 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 │ Body: {"session_id":"sess-001", "message":"重疾险等待期多少天?", "filters":{"险种":"重疾险"}} │ ▼ [步骤2] 后端 - JWT 鉴权 │ ├─ 读取 Redis: GET token:blacklist: → 检查是否被吊销 │ ├─ 解析 JWT → 提取 user_id、role │ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE baodan_user_id = ? │ │ │ ├─ [成功] → 继续步骤3 │ └─ [失败] → 返回 {"code":1002, "message":"未授权,请重新登录"} │ ▼ [步骤3] 后端 - 调用 BaoDan Chat API(blocking 模式) │ 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 │ 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-150,gender 为 "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.recommendation(Markdown 文本) │ ▼ [步骤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×tamp=xxx&nonce=xxx │ Body: ...加密消息 │ ▼ [步骤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: │ ├─ 比对 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: = 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 过期前 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 为有效 JWT|登录已过期,请重新登录| #### A1.1.4 POST /auth/logout |字段名|类型|必填|长度/范围约束|默认值|枚举值|验证规则|错误提示文案| |------|-----|:---:|------------|------|------|--------|------------| |string|Authorization (Header)|是|-|-|-|Bearer 格式|登录已过期,请重新登录| ### 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-K:5 - Score 阈值:0.6 **节点5 - LLM 节点**: - 拖入 "LLM" 组件 - 模型:`deepseek-chat` - Temperature:0.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 嵌入代码: ``` ``` ### 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 12(24000元/年) - [ ] 整体执行时间 < 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|文件格式不支持|上传弹窗行内提示|仅支持 MD、Word、TXT、PDF 格式的文件|否(需转换格式)| |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 节点 4:LLM 方案生成(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|返回"知识库检索失败,请稍后重试"| |节点4(LLM生成)|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 API(SSE 流式) | -> 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. 重新部署 BaoDan(Docker 或源码) 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| #### 节点 4:LLM 方案生成 |属性|值| |------|---| |节点类型|LLM 节点| |输入|retrieved_docs + validated_params| |输出|recommendation(Markdown 格式方案)| |模型|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)||||| |输入|recommendation(LLM 输出的 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 可正常下载且排版正确|下载验证||| |角色权限|不同角色看到不同功能和数据范围|切换账号验证||| |日志完整性|问答记录和操作日志可查询导出|后台验证|||