package cn.lihu.jh.module.ecg.controller.admin.external; import cn.lihu.jh.framework.common.pojo.CommonResult; import cn.lihu.jh.module.ecg.enums.ActionTypeEnum; import cn.lihu.jh.module.ecg.service.appointment.AppointmentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.annotation.security.PermitAll; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.util.Map; import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.lihu.jh.framework.common.pojo.CommonResult.success; import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*; /** * 供第三方(HIS / 集成平台)调用的通用接口。 * *

路由方式

* 通过请求头 {@code action} 区分服务,取值见 {@link ActionTypeEnum}。 * *

本次改动要点

*
    *
  1. 补注册 ECG 专属 action:接口文档 6.4/6.5 定义 * {@code S050402}(ECG 检查预约状态新增)、{@code S050502}(ECG 检查预约状态更新), * 与 pacs 的 {@code S050401}/{@code S050501} 并列。实测日志中这两个编号 * 尚未出现(0 次),此处**主动兼容**,避免院方切换编号时消息被拒。
  2. *
  3. 兼容无后缀写法:文档定义 {@code S0201}/{@code S0202}, * 而实测报文为 {@code S0201ECG}/{@code S0202ECG},两者均接收。
  4. *
  5. 细化错误码:原实现把所有异常统一压成 * {@code APPOINTMENT_CREATE_FAIL}("申请单创建失败"), * 导致排障困难(本次问题排查成本即来自此)。现按 * 「未知 action / 报文解析失败 / 记录不存在 / 其他」区分返回。
  6. *
* *

关于 S0405

* {@code S0405}(申请单状态更新)是**全院通用**服务,医院会把所有检查科室 * (CT/MRI/DR/超声…)的状态一并推送。业务层已按心电范围过滤, * 非心电单只记日志、不处理(详见 {@code HisEcgFilter})。 * * @author 心电排队装机系统 */ @Tag(name = "供第三方调用接口") @RestController @RequestMapping("/ecg/external") @Validated @Slf4j public class ExternalController { /** 请求头中的 action 键名 */ private static final String HEADER_ACTION = "action"; @Resource private AppointmentService appointmentService; /** * 通用接口。 * *

支持的 action 见 {@link ActionTypeEnum},包括: * 申请单新增/更新、申请单状态更新、检查预约状态新增/更新。 * * @param dataMap 请求数据(HL7 V3 转换后的 Map) * @param headers 请求头,必须包含 {@code action} * @return 处理结果 */ @PermitAll @Operation(summary = "通用接口") @PostMapping("/generalInterface") public CommonResult generalInterface( @RequestBody @NotNull(message = "请求数据不能为空") Map dataMap, @RequestHeader @NotEmpty(message = "请求头不能为空") Map headers) { String rawAction = headers.get(HEADER_ACTION); ActionTypeEnum action = ActionTypeEnum.getByType(rawAction); if (action == null) { // 不再吞成"申请单创建失败",明确告知 action 不受支持 log.warn("[generalInterface][不支持的 action({}),请核对接口文档 6.x 交易编号]", rawAction); throw exception(HIS_ACTION_NOT_SUPPORTED); } log.info("[generalInterface][开始处理请求 action({}) dataMap({})]", rawAction, dataMap); try { dispatch(action, dataMap); return success(true); } catch (cn.lihu.jh.framework.common.exception.ServiceException e) { // 业务异常:保留原始错误码与信息,便于 HIS 侧定位 log.error("[generalInterface][业务处理失败 action({}) code({}) msg({})]", rawAction, e.getCode(), e.getMessage()); throw e; } catch (Exception e) { // 未预期异常:报文解析失败、字段缺失导致的 NPE/类型转换异常等 log.error("[generalInterface][报文处理异常 action({}) dataMap({})]", rawAction, dataMap, e); throw exception(HIS_MESSAGE_PARSE_FAIL); } } /** * 按 action 分派到对应处理器。 *

* 使用 {@link ActionTypeEnum} 的分组方法而非 Map 映射,是为了让 * 「同一服务的多种编号」(pacs / ECG、有后缀 / 无后缀)走同一处理器, * 避免新增编号时漏配。 */ private void dispatch(ActionTypeEnum action, Map dataMap) { if (action.isApplyCreate()) { appointmentService.handleAppointmentCreate(dataMap); } else if (action.isApplyUpdate()) { appointmentService.handleAppointmentUpdate(dataMap); } else if (action.isApplyStatusUpdate()) { appointmentService.handleAppointmentStateUpdate(dataMap); } else if (action.isAppointmentStatusCreate() || action.isAppointmentStatusUpdate()) { // 检查预约状态:新增(S050401/S050402) 与 更新(S050501/S050502) // 报文结构相同,走同一处理器 appointmentService.handleCheckAppointmentUpdate(dataMap); } else { // 枚举新增但未接入分派逻辑时的兜底,避免静默忽略 throw exception(HIS_ACTION_NOT_SUPPORTED); } } }