HIS多医院路由详细设计.md 11 KB


doc_id: DDS-202606-004 feature_id: FEAT-202606-005-saas-tenant-isolation type: detailed-design title: HIS 多医院动态路由详细设计 status: reviewing owner: 医梦研发团队 created_at: 2026-06-27 updated_at: 2026-06-27 reviewers: [] related_docs:

  • INT-202606-005
  • DEV-202606-005
  • DDS-202606-001 related_modules:
  • emoon-openplatform
  • emoon-ai-agent
  • emoon-ai-mcp-api
  • emoon-ai-mcp tags:
  • SaaS
  • HIS
  • 多医院路由
  • Provider SPI ---

HIS 多医院动态路由详细设计

实现范围

本设计覆盖 SaaS P0 中的 HIS 多医院动态路由代码:

  • 根据可信的 tenantId + projectId + hospitalId 精确选择 HIS Provider。
  • 支持配置文件声明医院路由,不新增数据库表。
  • 为不同 HIS 厂商保留 Provider SPI,不猜测或统一厂商协议。
  • MCP 标准工具调用和卡片动作编排使用同一套路由规则。
  • 保留现有单 HisClient 和 Mock 路径,避免阻塞已有本地联调。

本设计不覆盖:

  • 真实医院 HIS/LIS 接口协议实现。
  • 专线、证书、DNS、防火墙和网络连通。
  • 凭证明文存储或密钥管理系统建设。
  • 管理后台动态维护路由。
  • 数据库 SQL 执行和真实双医院验收。

本次改造原则

  1. 所有医院统一按租户和项目隔离。HIS 路由不得削弱 Workflow、RAG、Agent、Card、 会话、任务、设备和文件已有的租户边界。
  2. 默认不修改现有 HTTP URL、方法、请求体和响应结构。内部方法优先增加兼容重载; 如实现中确需修改外部接口,必须先同步更新本专题接口影响说明。
  3. 所有涉及的 DDL 和 DML 只写入 SaaS化数据库候选变更与执行清单.md, 本阶段不连接目标数据库、不执行迁移 SQL。

总体方案

flowchart LR
    Tool["ToolCallRequest"]
    Card["Card Action"]
    Conversation["Tenant-scoped Conversation"]
    Context["HisRouteContext"]
    Router["HisClientRouter"]
    Config["HisRoutingProperties"]
    SPI["HisClientProvider SPI"]
    Mock["Legacy Mock HisClient"]
    A["Hospital A Adapter"]
    B["Hospital B Adapter"]

    Tool --> Context
    Card --> Conversation --> Context
    Context --> Router
    Config --> Router
    Router --> SPI
    Router --> Mock
    SPI --> A
    SPI --> B

路由只负责选择客户端。字段转换、签名、证书、错误码归一和厂商协议由具体 HisClientProvider 创建或持有的 HisClient 完成。

数据模型

本次不新增数据库表和字段。

路由上下文

内部值对象 HisRouteContext 包含:

字段 必填条件 来源
tenantId 严格模式必填 鉴权项目解析
projectId 严格模式必填 鉴权项目解析
hospitalId 严格模式必填 MCP 请求或租户作用域内会话

路由键固定为三个字段的完整组合,不支持通配符、仅医院匹配或跨租户回退。

配置模型

HisRoutingProperties 使用前缀 emoon.his.routing

emoon:
  his:
    routing:
      strict: false
      fallback-enabled: true
      routes:
        - route-id: hospital-a
          tenant-id: tenant-a
          project-id: "7"
          hospital-id: hospital-a
          provider: vendor-a
          endpoint: ${HIS_HOSPITAL_A_ENDPOINT:}
          credential-ref: ${HIS_HOSPITAL_A_CREDENTIAL_REF:}
          connect-timeout: 3s
          read-timeout: 10s

约束:

  • route-id 和三字段路由键均必须唯一。
  • provider 必须能匹配一个 Provider SPI 实现。
  • endpoint 允许为空,由 Provider 决定是否必需。
  • credential-ref 只保存凭证引用,不保存用户名、密码、Token 或私钥明文。
  • 配置校验失败时禁止启动严格路由。

服务和对象职责

类或组件 职责 备注
HisRouteContext 承载租户、项目、医院上下文 不包含凭证
HisRouteDefinition 表示单条路由配置 不输出敏感字段
HisRoutingProperties 绑定并校验配置文件 默认兼容模式
HisClientProvider 根据路由定义提供厂商客户端 Provider 不参与业务编排
HisClientRouter 精确匹配路由、选择 Provider、执行兼容回退 不处理 HIS 字段
HisToolInvokeService ToolCallRequest 传递路由上下文 标准 MCP 入口
McpToolService 提供带路由上下文的工具重载 旧方法保持签名兼容并标记弃用
AgentActionOrchestrator 从作用域会话解析医院并传给 MCP 不信任动作请求租户
ConversationTerminalService 按项目和租户读取会话路由事实 不跨租户查询

内部接口

建议内部接口保持最小:

public interface HisClientRouter {
    HisClient route(HisRouteContext context);
}

public interface HisClientProvider {
    String providerType();
    HisClient getClient(HisRouteDefinition definition);
}

Provider 可自行缓存客户端。Router 不持有或打印真实凭证。

HisClient 增加默认方法 isMock(),默认返回 false;现有 MockHisClientSqliteMockHisClient 返回 true。工具响应中的 mock 标记必须取自 实际路由客户端,真实 Provider 不得继续返回 mock=true

调用链

MCP 标准工具入口

ToolController / HisMcpController
→ ToolCallRequest(projectId, tenantId, hospitalId)
→ HisToolInvokeService
→ HisClientRouter.route(context)
→ HisClientProvider
→ Hospital HisClient

卡片动作入口

CardActionController
→ AgentActionOrchestrator(conversationId, projectId, tenantId)
→ ConversationTerminalService.findByScope(...)
→ hospitalId
→ McpToolService(context)
→ HisClientRouter.route(context)
→ Hospital HisClient

前端卡片动作请求不新增 tenantIdhospitalId 参数。医院事实来自当前租户下的会话, 避免客户端伪造医院路由。

路由和兼容规则

兼容模式

默认配置:

strict: false
fallback-enabled: true

行为:

  • 完整上下文命中配置时使用对应 Provider。
  • 未配置、上下文缺失或 Provider 不存在时使用现有默认 HisClient
  • 记录 routeId 或上下文缺失类型,不记录 endpoint、credentialRef 和请求敏感字段。

严格模式

生产环境应配置:

strict: true
fallback-enabled: false

以下情况直接失败,不调用任何 HIS:

  • 任一路由上下文字段为空。
  • 没有精确匹配路由。
  • Provider 不存在或重复。
  • 路由配置重复或被禁用。

严格模式禁止回退到其他医院路由,也禁止仅凭 hospitalId 匹配。

异常处理

场景 行为
上下文不完整 严格模式失败;兼容模式回退
路由不存在 严格模式失败;兼容模式回退
Provider 不存在 严格模式失败;兼容模式回退并告警
重复路由 配置校验失败,阻止严格模式启动
Provider 创建客户端失败 返回工具调用失败,不切换其他医院
HIS 超时或熔断 由 Provider/客户端转换为平台异常,不跨路由重试

错误信息不得包含 endpoint、凭证引用内容、Token、证书路径或患者敏感数据。

安全和合规

  1. 路由租户和项目来自后端鉴权上下文。
  2. 卡片动作的医院来自租户作用域内会话,不接受前端覆盖。
  3. 配置文件只保存 credentialRef,真实凭证使用环境变量或密钥系统。
  4. 日志只记录 routeId、tenantId、projectId、hospitalId 和 traceId。
  5. 禁止日志记录 Header、签名原文、患者身份证、手机号或完整请求体。
  6. 不允许路由失败后尝试其他医院,避免医疗数据跨院发送。

仓库当前 application.yml 存在历史明文配置,本次只保证新增 HIS 路由不继续使用该模式; 历史配置清理应单独执行密钥轮换,不能仅删除文件内容而不更换已暴露凭证。

配置和部署

推荐发布顺序:

  1. 保持 strict=false 部署代码,验证原 Mock 路径无回归。
  2. 配置医院 A、医院 B 的路由和 Provider。
  3. 分别验证两家医院查询与写工具。
  4. 确认无未配置调用后切换 strict=truefallback-enabled=false
  5. 观察错误率、超时和路由未命中日志,不记录敏感信息。

本次不修改现有 HTTP URL、请求体和响应结构。

实现状态

截至 2026-06-27,以下代码已完成:

  • 路由配置对象、三元精确索引、严格模式和兼容回退。
  • Mock 客户端互斥注册与真实/Mock 类型标识。
  • MCP 标准工具入口和 McpToolService 上下文重载。
  • Agent、Card 与对话降级链医院上下文传递。
  • 跨租户会话不存在时禁止 Card 链调用 HIS。

未完成内容:

  • 真实 HIS 厂商 HisClientProvider 实现。
  • 目标环境 endpoint、credential-ref、超时和网络策略配置。
  • 两家医院专线环境的真实联调与严格模式切换。

本实现没有新增数据库表、字段或迁移 SQL。

测试策略

单元测试

  • 完整三字段命中指定 Provider。
  • 两个租户使用相同业务参数时选择不同客户端。
  • 严格模式拒绝空上下文和未配置路由。
  • 兼容模式未命中时回退现有客户端。
  • 重复路由和重复 Provider 被拒绝。
  • Provider 异常时不尝试其他医院。

应用服务测试

  • HisToolInvokeService 使用 ToolCallRequest 的三字段上下文。
  • AgentActionOrchestrator 从作用域会话获取 hospitalId
  • 跨租户会话查询不到时不调用 HIS。
  • 旧的无上下文 McpToolService 方法仍调用默认客户端。

回归验证

mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp \
  -DskipTests=false -Dprofiles.active= test
mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-agent \
  -DskipTests=false -Dprofiles.active= test
mvn -pl emoon-openplatform -am -DskipTests compile
mvn -pl emoon-admin -DskipTests=false -Dprofiles.active= \
  -Dtest=AiPlatformArchitectureTest test

真实环境验收

  • 医院 A 请求只到达医院 A endpoint。
  • 医院 B 请求只到达医院 B endpoint。
  • 交换医院 ID、项目 ID 或租户 ID 后均不得调用 HIS。
  • 两家医院使用相同幂等键时互不影响。
  • 严格模式下未配置医院返回明确失败,不使用 Mock。

验收标准

  • 现有接口路径、请求体和旧内部方法保持兼容。
  • 代码可按三字段精确路由到不同 Provider。
  • 严格模式不存在跨医院回退。
  • 新增配置不含 HIS 明文凭证。
  • 自动化测试证明两个租户路由到不同客户端。
  • 真实医院协议和网络未接入时,不宣称真实双医院联调完成。