# AI 诊断(兽医、机构)— 技术方案 > 依据:`AI诊断(兽医、机构)功能需求.md`(v1.1);关联 `在线接诊(兽医)技术方案.md`(v1.1)、`在线接诊(兽医)功能需求.md` > **本文档版本:1.6**(`biz_consult_user` 使用用户表,见 **§3.0**) --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **整体** | RuoYi **v3.9.2**(**springboot2** 分支)单体后端 + 若依 **Vue2** 管理端 + 移动端 | | **运行时** | JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **问诊 IM 表** | **复用** `biz_consult_session`、`biz_consult_message`(与在线接诊共用,见 **§2**);另增 `biz_consult_user` 记录提问人首次/末次使用时间(**§3.0**) | | **近实时** | 复用 **Redis Pub/Sub** `consult:push` + STOMP(与在线接诊 **§5.1** 一致);提问落库即推;AI 回复落库后推,**≤ 1 秒** | | **文件** | `POST /common/upload`;消息存 URL | | **AI 能力(对话框)** | 管理端/移动端 **直连或经本服务转发** `GET /v1/models`、`POST /v1/chat/completions`(`com.ruoyi.web.kb.controller.KbOpenAiProxyController`,配置 `ruoyi.kb`);详见 `doc/大模型/大模型网关转发接口说明.md`、前端方案 §4 | | **AI 能力(服务端兜底)** | `ConsultAiGateway`:仅当落库请求 **未带** `aiReplyContent` 时由服务端调模型;**超时 60s**、**非流式**整段回复 | | **后台准入** | 若依菜单 + `diseaseTreatment:aiOnlineConsult:*` 权限;**不校验** `biz_medical_resource` 绑定 | | **预约/接诊** | **不关联** `biz_service_appointment`、在线接诊接口 | **后端分层(AI 诊断)** | 层 | 职责 | | --- | --- | | `Controller` | 后台 `.../onlineConsult/ai`;移动端 `.../app/consult/ai` | | `Service` | 列表/新建/消息/提问;调用 AI 生成回复;`hide` | | `ConsultAiGateway` | 文本/图片调用模型;视频/语音返回固定引导文案 | | `Mapper` + XML | 扩展 `BizConsultSessionMapper`(`consult_type=2` 查询);`BizConsultUserMapper`(**§3.0**) | | `support` | 复用 `ConsultSessionRules`、`ConsultThreeMonthsWindow`、`ConsultMessagePreview`、`VetConsultValidation`;新增 `AiConsultValidation` | **代码包**:`com.ruoyi.web.modules.diagnosis`(与在线接诊、医疗资源同域) **大模型转发包**:`com.ruoyi.web.kb`(与知识库共用 `ruoyi.kb`,见 **§1.2**) **DDL**:`sql/biz_consult_session.sql`、`sql/biz_consult_message.sql`(会话/消息,与在线接诊共用);`sql/biz_consult_user.sql`(AI 诊断使用用户,**§3.0**) ### 1.2 大模型 OpenAI 兼容转发(`/v1`) 与知识库同属大模型网关体系,实现类不在 `diagnosis` 包而在 `kb` 包: | 对外路径 | 实现 | 用途 | | --- | --- | --- | | `GET /v1/models` | `KbOpenAiProxyController` → `KbOpenAiProxyServiceImpl` | 模型列表(原样转发上游) | | `POST /v1/chat/completions` | 同上;`stream:false` JSON,`stream:true` SSE | 管理端 AI 诊断、移动端 AI 助手 **对话框问答** | | 项 | 说明 | | --- | --- | | **鉴权(本服务)** | **须若依登录 Token**(`KbOpenAiProxyController` 无 `@Anonymous`);未登录 **401** | | **鉴权(上游)** | 服务端 `ruoyi.kb.api-key` 访问上游,**不**使用客户端 `Authorization` | | **配置** | `ruoyi.kb`(`scheme`/`host`/`port`/`api-key`/`stream-read-timeout-ms`) | | **与业务接口关系** | 会话 CRUD、消息落库仍走 `.../onlineConsult/ai/**`、`.../app/consult/ai/**`;前端大模型返回后 `POST .../message` 带 `aiReplyContent`,**不**再走 `ConsultAiGateway` | | **设计/测试** | `doc/大模型/大模型网关转发接口说明.md`、`doc/大模型/大模型网关转发测试用例.md` | --- ## 2. 关联方案设计(AI 诊断 / 在线接诊) ### 2.1 统一数据模型(`consult_type` 隔离) | `consult_type` | 模块 | `receiver_provider_id` | `vet_visible` 语义(本期) | | --- | --- | --- | --- | | `1` | 在线接诊(兽医) | 兽医 `biz_medical_resource.id` | 牧民删除后对**兽医**不可见 | | **`2`** | **AI 诊断** | **必须为 NULL** | 复用字段:提问人删除后对**本人**列表不可见(`0`) | | `3` | 专家问诊(预留) | 专家资源 ID | 待定 | **禁止**:AI 列表/消息 SQL 出现 `consult_type=1`;接诊 SQL 出现 `consult_type=2`。 ### 2.2 能力对照 | 能力 | 在线接诊(兽医) | AI 诊断(本文档) | | --- | --- | --- | | Base Path | `/diseaseTreatment/onlineConsult/vet` | `/diseaseTreatment/onlineConsult/ai` | | 角色 | 兽医(100) | 兽医(100)+ 机构(102 等) | | 列表筛选 | `askerName`、`contentKeyword` | **`contentKeyword` 仅** | | 新建会话 | 无 | **`POST /session`** | | 发送 | 人 → 人 | 人 → **AI 服务** → AI 消息入库 | | 列表标题字段 | `askerName` | **`sessionTitle`** | | 移动端 Path | `/app/onlineConsult` | `/app/consult/ai` | ### 2.3 推送频道(共用基础设施) ```text 提问/AI 回复落库 → 更新 last_message_* → Redis consult:push → STOMP /topic/consult/ai/{askerUserId} ``` Payload 同在线接诊 **§4.2**(`senderRole`:1 提问人,**3** AI 助手)。 ### 2.4 与在线接诊代码复用 | 复用项 | 说明 | | --- | --- | | `biz_consult_*` 表 | 同表异 `consult_type` | | `ConsultMessagePreview` | 摘要 50 字 / `[图片]` 等 | | `ConsultThreeMonthsWindow` | 3 个月自然日 | | `ConsultRedisPushService` | 推送 | | `BizConsultMessageMapper` | 消息插入、分页查询 | | `VetConsultValidation.validateSendBody` | 发送体校验(AI 模块复用) | **独立实现**:`BizAiOnlineConsultServiceImpl`、`BizAiOnlineConsultController`、`ConsultAiGateway`、`selectAiSessionList` 等 Mapper 方法。 ### 2.5 与「我的预约」 无表关联、无接口校验。 --- ## 3. 数据库设计 会话/消息在共用表上按 `consult_type=2` 读写;另增 **AI 诊断使用用户** 表记录提问人首次/末次使用时间。 ### 3.0 表 `biz_consult_user`(AI 诊断使用用户) | 字段 | 类型 | 说明 | | --- | --- | --- | | `user_id` | `bigint(20)` | 主键;与提问人 `asker_user_id` 一致 | | `first_use_time` | `datetime` | 开始使用 AI 诊断时间 | | `last_use_time` | `datetime` | 最后一次使用 AI 诊断时间 | **写入规则** | 时机 | 行为 | | --- | --- | | `POST .../session` 新建会话 | 若 `user_id` 不存在则 **INSERT**,`first_use_time`、`last_use_time` 均为当前时间 | | 更新 `biz_consult_session.last_message_time` | 同步 **UPDATE** 对应 `asker_user_id` 的 `last_use_time`(与 `last_message_time` 一致) | **DDL**:`sql/biz_consult_user.sql`。 ### 3.1 表 `biz_consult_session`(AI 会话写入约定) | 字段 | AI 会话取值 | | --- | --- | | `consult_type` | **2** | | `asker_user_id` / `asker_name` | 当前登录用户 | | `receiver_user_id` | **NULL** | | `receiver_provider_id` | **NULL**(禁止写入兽医/机构资源 ID) | | `receiver_name` | **NULL** | | `session_title` | 新建时 **「新会话」**;首条有效文本提问后更新为摘要(≤50 字) | | `real_session_id` | **varchar(64)**,可为空;大模型网关 `session_id`,由 `POST .../message` 写入/更新,供多轮 `session_id` 回传 | | `last_message_time` / `last_message_preview` | 每次提问或 AI 回复后更新 | | `vet_visible` | 默认 **1**;提问人 `hide` 后置 **0** | **索引使用**:`idx_asker (consult_type, asker_user_id, last_message_time)`。 **多会话**:同一 `asker_user_id` 可有多行 `consult_type=2`(**不**使用 `uk_vet_session` 限制 AI;该唯一键仅对 `receiver_provider_id` 非空的兽医会话生效)。 ### 3.2 表 `biz_consult_message` | `sender_role` | 含义 | | --- | --- | | **1** | 提问人(兽医/机构/牧民) | | **3** | **AI 助手**(`sender_user_id=0`,`sender_name=AI助手`) | 在线接诊使用的 **2**(接诊兽医)在 AI 会话中**不出现**。 | 字段 | 说明 | | --- | --- | | `ai_category` | **varchar(32)**,可为空;仅 **提问人消息**(`sender_role=1`)写入;AI 回复为 `NULL`。纯数字时须为 `1`~`4`(与 `AiConsultConstants` 分类一致),亦可存业务编码字符串 | | `cost_time` | **bigint**,可为空;大模型对话耗时(**毫秒**),仅 **AI 消息**(`sender_role=3`)写入;来自 `/v1/chat/completions` 响应头 **`X-COST`** | | `asker_ip` | **varchar(128)**,可为空;**提问人所属 IP**,仅 **AI 诊断提问人消息**(`sender_role=1`)写入;由 `IpUtils.getIpAddr()` 从当前 HTTP 请求解析 | ### 3.3 AI 问诊疫病预警定时任务 | 项 | 说明 | | --- | --- | | **Quartz** | `aiConsultDiseaseWarningTask.scanAndWarn`;cron 建议 `0 0/30 * * * ?`(每 30 分钟);SQL 见 `sql/biz_sys_job_ai_consult_disease_warning.sql` | | **统计** | 当月(Asia/Shanghai,`send_time` 从月初至当前)`biz_consult_message` ⋈ `biz_consult_session`(`consult_type=2`),条件:`sender_role<>3`、`sender_user_id<>0`、`ai_category=yak-disease`(固定),按 `asker_ip` 计数 | | **阈值** | `diagnosis.ai-consult-disease-warning.threshold`(默认 **3**) | | **地区** | 对达阈值 IP 调用太平洋 IP 库解析区县,展示为 `pasture_name`(如「巴青区」) | | **落库** | `biz_yak_disease_warning`:`warning_type=5`,`data_source=ai_consult_analysis`;同月同 `pasture_name` 已存在则 **更新** `alert_message`/`alert_time`,否则 **插入** | | **文案** | `{区名}本月截止当前时间AI问诊记录集中({N}条),触发疫病预警。` | **已有库迁移**:`sql/biz_consult_user.sql`(或 `sql/alter_rename_biz_diagnosis_user_to_biz_consult_user.sql`);`sql/alter_biz_consult_session_real_session_id.sql`、`sql/alter_biz_consult_message_ai_category.sql`、`sql/alter_biz_consult_message_cost_time.sql`、`sql/alter_biz_consult_message_asker_ip.sql`。 ### 3.3 AI 列表查询语义 **默认列表**(`searchMode=false`): ```sql -- 时间字段见 §3.5 推荐 SQL(COALESCE 或创建时写入 last_message_time) WHERE consult_type = 2 AND asker_user_id = #{askerUserId} AND vet_visible = 1 AND COALESCE(last_message_time, create_time) >= #{threeMonthsAgo} ORDER BY COALESCE(last_message_time, create_time) DESC ``` **搜索**(`searchMode=true`): - `vet_visible = 1`;**不限制** 3 个月。 - `contentKeyword` 非空时匹配 `session_title`、`last_message_preview`、消息文本子查询(含 AI 回复文本)。 ### 3.4 实现约定(对齐功能需求 v1.1) | # | 约定项 | 实现要点 | | --- | --- | --- | | 1 | 3 个月窗 | `ConsultThreeMonthsWindow.calcThreeMonthsAgo()`,`Asia/Shanghai` | | 2 | 搜索/重置 | 仅 `searchMode=true` 超窗;空关键字 →「请输入筛选条件」 | | 3 | 摘要 | `ConsultMessagePreview.build` | | 4 | 首问标题 | 首条 `msg_type=1` 提问成功后 `update session_title` | | 5 | 消息分页 | 未传 `beforeId` 取最新 50 条,响应升序 | | 6 | 隐藏 | `updateSessionVisible` → `vet_visible=0`;管理端/移动端 `POST .../hide`,仅 `asker_user_id=当前用户` | | 7 | AI 超时 | `ConsultAiGateway` 调用 **60s** 超时,失败不插入 AI 消息 | | 8 | 非流式 | 模型返回整段文本后一次 `insert` + 推送 | ### 3.5 实现约定补充(v1.1 已确认) | # | 约定项 | 本期实现 | | --- | --- | --- | | 1 | **空会话进列表** | `POST /session` 创建时同步写入 `last_message_time = create_time`(或列表 SQL 使用 `COALESCE(last_message_time, create_time)` 参与 3 个月过滤与排序),保证「新会话」**立即**出现在默认列表 | | 2 | **HTTP 读超时** | `POST .../message` 同步等待 AI 最长 **60s**;Tomcat/网关 **read timeout ≥ 65s**,避免先于 AI 调用返回 504 | | 3 | **视频/语音固定回复** | `ConsultAiGateway` 常量文案(写入 AI 消息):`已收到视频,请补充文字描述症状。` / `已收到语音,请补充文字描述。` | | 4 | **图片识图失败** | 模型不可用或识图失败时,**仍插入**一条 AI 文本消息(`sender_role=3`),内容为:`图片识别暂不可用,请补充文字描述症状。`;**不**仅返回 HTTP 错误而无 AI 气泡 | | 5 | **移动端搜索** | `GET /app/consult/ai/session/list` 支持与后台相同参数:`contentKeyword`、`searchMode`(规则同 **§3.3**) | | 6 | **AI 上下文** | 本期 **单轮**:每次仅将**当前提问**(文本或图片 URL + 可选说明)送模型;**不带**历史多轮上下文(多轮对话产品化另迭代) | | 7 | **同会话并发** | 同一会话在 AI 处理中(服务端标记或分布式锁)**拒绝**第二次 `POST .../message`,返回「AI 正在回复中,请稍候」 | | 8 | **免责声明** | `POST /session` 与首次 `GET /session/list` 响应附带字段 `disclaimer`(常量,与功能需求 §5.4 #6 一致);前端必须展示 | | 9 | **`vet_visible` 口径** | 以**本文档 §2.1** 为准:AI 会话复用该字段表示**提问人列表可见**;在线接诊技术方案中「AI 不适用」表述作废 | **列表 SQL 推荐(含空会话)**: ```sql WHERE consult_type = 2 AND asker_user_id = #{askerUserId} AND vet_visible = 1 AND COALESCE(last_message_time, create_time) >= #{threeMonthsAgo} ORDER BY COALESCE(last_message_time, create_time) DESC ``` --- ## 4. 接口设计(后台 AI 诊断) 统一响应:`AjaxResult` / `TableDataInfo`。 **权限(示例)**:`diseaseTreatment:aiOnlineConsult:list|query|send|add|remove` **Base Path**:`/diseaseTreatment/onlineConsult/ai` **准入**:登录 + 接口权限;列表/新建按 `asker_user_id = 当前用户` 隔离,**不**调用 `VetProviderResolver` / `OrgProviderResolver` 校验医疗资源绑定。 | # | 说明 | Method | URI | 权限 | 要点 | | --- | --- | --- | --- | --- | | 4.1 | 历史会话列表 | GET | `/session/list` | `...:list` | Query:`pageNum`、`pageSize`(默认 20)、`contentKeyword`、`searchMode`;`asker_user_id` 服务端注入 | | 4.2 | 新增会话 | POST | `/session` | `...:add` | 创建 `consult_type=2` 空会话;`session_title=新会话`;`last_message_time=create_time`(**§3.5 #1**);返回 `sessionId`、`disclaimer` | | 4.3 | 历史消息 | GET | `/session/{sessionId}/messages` | `...:query` | 校验 `consult_type=2` + `asker_user_id` + `vet_visible=1`;`beforeId`、`pageSize`(默认 50) | | 4.4 | 提问(触发 AI) | POST | `/session/{sessionId}/message` | `...:send` | Body:`msgType`、`content`、`mediaDuration?`、`aiReplyContent?`、`realSessionId?`、`aiCategory?`、`costTime?`(毫秒,来自 `X-COST`);落库时更新 `last_message_*` 与 **`update_time`**;流程见 **§5** | | 4.5 | 隐藏会话 | POST | `/session/{sessionId}/hide` | `...:remove` | 管理端隐藏 AI 诊断会话;`vet_visible=0`;仅本人会话;隐藏后列表/消息/提问不可用 | ### 4.1 列表 `rows[]` 字段 `sessionId`、`sessionTitle`、`lastMessagePreview`、`lastMessageTime`、`realSessionId`(大模型网关会话 ID,可为空)。 ### 4.2 消息 `rows[]` / 推送字段 `messageId`、`sessionId`、`senderRole`(1 提问人 / **3** AI)、`senderName`、`msgType`、`content`、`mediaDuration`、`aiCategory`(可为空)、`askerIp`(可为空,仅提问人消息)、`sendTime`。 ### 4.3 提问 Body 示例 **服务端调 `ConsultAiGateway`(无 `aiReplyContent`)** ```json { "msgType": 1, "content": "牛不吃草、精神沉郁,请问可能原因?" } ``` **前端大模型直连后落库(推荐)** ```json { "msgType": 1, "content": "牛不吃草、精神沉郁,请问可能原因?", "aiReplyContent": "可能原因包括…", "realSessionId": "gateway-session-001", "aiCategory": "2", "costTime": 1523 } ``` | Body 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `msgType` | int | Y | 1 文本 2 图 3 视频 4 语音 | | `content` | string | 条件 | 文本或媒体 URL | | `mediaDuration` | int | N | 音视频秒数 | | `aiReplyContent` | string | N | 非空时服务端**仅落库**,不调 `ConsultAiGateway` | | `realSessionId` | string | N | 大模型 `session_id`;写入 `biz_consult_session.real_session_id`(别名 `real_session_id`、`session_id`、`llmSessionId`) | | `aiCategory` | string | N | 写入提问人消息 `ai_category`(别名 `ai_category`、`category`);最长 32 | | `costTime` | long | N | 写入 **AI 消息** `cost_time`(别名 `cost_time`、`xCost`);毫秒,来自大模型响应头 `X-COST` | **提问成功 `data`** ```json { "userMessage": { "id": 1, "senderRole": 1, "aiCategory": "2", "...": "..." }, "aiMessage": { "id": 2, "senderRole": 3, "...": "..." }, "realSessionId": "gateway-session-001" } ``` ### 4.4 业务错误(`msg` 示例) | 场景 | 文案 | | --- | --- | | 越权会话 | 无权查看该会话 | | 会话不可见 | 会话不存在或已删除 | | 搜索为空 | 请输入筛选条件 | | 文本为空 | 请输入消息内容 | | 媒体为空 | 请上传文件 | | 文件不合规 | 文件格式或大小不符合要求 | | AI 超时 | AI 回复超时,请稍后重试 | | AI 失败 | AI 回复失败,请稍后重试 | | AI 处理中 | AI 正在回复中,请稍候 | | AI 提问分类无效 | AI提问分类无效 / AI提问分类长度不能超过32个字符 | --- ## 5. 提问与 AI 回复流程(核心) ```text 1. 校验会话归属(type=2, asker=当前用户, vet_visible=1) 2. 若会话 AI 处理中 → 拒绝(§3.5 #7) 3. 校验 Body(VetConsultValidation、AiConsultValidation.validateAiCategory) 4. 若 Body.realSessionId 非空 → 更新 biz_consult_session.real_session_id 5. 标记会话 processing=1(内存/Redis 锁) 6. INSERT 提问消息(sender_role=1,含 ai_category 若有) 7. 更新 session.last_message_*(提问侧摘要) 8. 推送 NEW_MESSAGE(提问)→ 前端立即展示 9a. 若 aiReplyContent 非空:直接作为 AI 文本,跳过 ConsultAiGateway(前端直连大模型落库通道) 9b. 否则调用 ConsultAiGateway(≤60s,单轮 §3.5 #6): - msg_type=1:文本问答 - msg_type=2:图文;失败 → §3.5 #4 固定 AI 文案仍入库 - msg_type=3/4:§3.5 #3 固定引导文案入库 10. 成功:INSERT AI 消息(sender_role=3,ai_category=NULL) 11. 更新 session.last_message_*;首问更新 session_title 12. 推送 NEW_MESSAGE(AI 回复,落库后 ≤1s) 13. 释放 processing;响应 data:{ userMessage, aiMessage, realSessionId? } 14. 网关硬失败(非图片软失败且无 aiReplyContent):不写 AI 消息;HTTP 错误 + §4.4 文案 ``` ### 5.1 同步、超时与展示策略(v1.1 已确认) | 项 | 约定 | | --- | --- | | HTTP | `POST .../message` **同步阻塞**等待 AI,最长 **60s**;读超时 **≥65s**(§3.5 #2) | | 响应体 | 成功时返回 `userMessage` + `aiMessage`;前端发送后即可展示提问,等待态文案「AI 正在思考…」 | | 推送 | AI 消息落库后 **≤1s** 经 `/topic/consult/ai/{userId}` 推送(与 HTTP 响应**双通道**,以先到者刷新为准) | | 非流式 | 不做 SSE/逐字;整段 `content` 一次入库(§3.5 #6、功能需求 §5.4 #10) | | 免责声明 | `disclaimer` 由 **`POST /session`**、**`GET /session/list`** 返回,前端**必须展示**(§3.5 #8) | --- ## 6. 移动端接口 **Base Path**:`/app/consult/ai` **鉴权**:登录 Token;**不**要求绑定医疗资源(牧民可用)。 | # | 说明 | Method | URI | 要点 | | --- | --- | --- | --- | --- | | M1 | 新建会话 | POST | `/session` | Body 可选 `category`:`1`~`4`;返回 `sessionId`、`promptTemplate`、`disclaimer`;`last_message_time=create_time` | | M2 | 会话列表 | GET | `/session/list` | `asker_user_id=当前用户`;`contentKeyword`、`searchMode`(**§3.5 #5**);分页;默认 3 个月 | | M3 | 历史消息 | GET | `/session/{id}/messages` | 同 4.3 | | M4 | 提问 | POST | `/session/{id}/message` | 同 4.4 | | M5 | 隐藏会话 | POST | `/session/{id}/hide` | `vet_visible=0`;管理端列表同步不展示 | **快捷入口(§5.4 #4)**:`M1` + `category` → 新建会话(**不**复用当前会话)+ 返回模板字符串,由客户端填入输入框。 **模板示例(服务端常量)** | category | 预填模板(示例) | | --- | --- | | 1 | 想了解一些养殖技巧,我的情况是: | | 2 | 想咨询疾病诊疗问题,症状描述: | | 3 | 想了解用药指导,具体情况: | | 4 | 想咨询设备操作问题,设备/现象: | --- ## 7. 近实时、上传、菜单 与在线接诊技术方案 **§5.1、§5.2** 一致;订阅主题为 `/topic/consult/ai/{userId}`。 | 菜单名 | 权限标识 | 路由 component | | --- | --- | --- | | AI 诊断(兽医、机构) | `diseaseTreatment:aiOnlineConsult:list` | `diseaseTreatment/onlineConsult/ai/index` | 按钮:`query`、`send`、`add`(新建会话)、`remove`(**隐藏会话**)。角色 **100**、**102**(机构,以平台为准)。权限 SQL:`sql/biz_ai_online_consult_remove_perm.sql`。 --- ## 8. 实现要点与类清单 | 项 | 说明 | | --- | --- | | 越权 | `sessionId` 操作必须 `consult_type=2` 且 `asker_user_id=当前用户` | | AI 会话创建 | `receiver_*` 全 NULL;`insert` 后 `session_title=新会话` | | 后台准入 | 权限注解;无医疗资源绑定校验 | | 与接诊隔离 | Mapper 方法命名区分 `selectAi*` / `selectVet*` | | 配置项 | `application.yml`:`consult.ai.endpoint`、`apiKey`、`timeoutMs=60000`(示例键名) | **建议新增类** - `AiConsultValidation`(搜索空条件等) - `ConsultAiGateway` / `ConsultAiGatewayImpl` - `IBizAiOnlineConsultService` / `BizAiOnlineConsultServiceImpl` - `BizAiOnlineConsultController` - `BizAppAiConsultController` - `BizConsultSessionMapper.selectAiSessionList` / `insertAiSession` / `selectAiSessionByIdAndAsker`(XML 补充) **可继续复用在线接诊已实现的** - `ConsultSessionRules`、`ConsultRedisPushService`、`BizConsultMessageMapper` --- ## 9. 验收要点 | 编号 | 场景 | 通过标准 | | --- | --- | --- | | AI-01 | 新建会话 | 后台 `POST /session` 返回新 `sessionId`;列表出现「新会话」 | | AI-02 | 提问与回复 | 发送文本后 ≤60s 内返回 AI 消息;落库后 ≤1s 界面可见 | | AI-03 | 默认/搜索 | 同在线接诊 AC-02、AC-03(`consult_type=2`) | | AI-04 | 隐藏 | 移动端/管理端 `hide` 后列表无该会话 | | AI-05 | 隔离 | 接诊列表无 AI 会话;AI 列表无接诊会话 | | AI-06 | 机构/兽医 | 具备权限即可 list/create,不要求绑定医疗资源 | | AI-07 | 视频/语音 | 上传后 AI 回复为引导补文字文案,非解析内容 | | AI-08 | 快捷入口 | 点「疾病诊疗」等 → 新会话 + 预填模板 | | AI-09 | 空会话列表 | `POST /session` 后未提问,左侧仍可见「新会话」 | | AI-10 | 处理中拒发 | AI 未返回前再次发送 →「AI 正在回复中,请稍候」 | | AI-11 | 图片失败 | 识图失败仍有 AI 引导气泡(§3.5 #4) | --- ## 10. 交付清单 - [ ] Mapper/XML:`selectAiSessionList` 等(`consult_type=2`) - [ ] `ConsultAiGateway` + 配置 - [ ] `BizAiOnlineConsultServiceImpl` + 后台 Controller - [ ] `BizAppAiConsultController`(M1~M5) - [ ] 菜单权限 SQL(**§7**) - [ ] 单元测试 + MockMvc(AI 列表/新建/提问 Mock Gateway) - [ ] 与在线接诊联调:推送、摘要、3 个月窗一致 --- ## 11. 修订记录 | 版本 | 说明 | | --- | --- | | 1.0 | 初稿:共用 biz_consult_*;consult_type=2;后台/移动端接口;AI 回复流程与 60s 超时;关联在线接诊 v1.1;vet_visible 复用为列表可见 | | 1.1 | 评审补充:§3.5 空会话列表、HTTP 超时、固定文案、图片软失败、移动端搜索、单轮上下文、处理中拒发、免责声明必返;§5.1 双通道;验收 AI-09~11 | | 1.2 | 补充 §1.2:`KbOpenAiProxyController` 转发 `/v1/models`、`/v1/chat/completions`;区分对话框直连/转发与 `ConsultAiGateway` 兜底 | | 1.3 | §1.2:`/v1/**` 须若依 Token,取消匿名访问 | | 1.4 | 管理端 **§4.5** `POST /session/{id}/hide` 隐藏 AI 诊断会话;权限 `...:remove` | | 1.5 | **§3.1** `biz_consult_session.real_session_id`;**§3.2** `biz_consult_message.ai_category`(varchar32);**§4.3** 落库 Body/响应字段;**§5** 前端 `aiReplyContent` 落库通道 | | 1.6 | **§3.0** `biz_consult_user`(原 `biz_diagnosis_user`):新建会话写入首次用户、更新 `last_message_time` 同步 `last_use_time` | | 1.7 | 表名统一为 `biz_consult_user`;Java `BizConsultUser` / `BizConsultUserMapper` |