yxh
昨天 fce96ef468291fb9a0e6a4d34ab371315e9485d4
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/external/ExternalController.java
@@ -14,12 +14,38 @@
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Map;
import java.util.function.Consumer;
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.APPOINTMENT_CREATE_FAIL;
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
/**
 * 供第三方(HIS / 集成平台)调用的通用接口。
 *
 * <h3>路由方式</h3>
 * 通过请求头 {@code action} 区分服务,取值见 {@link ActionTypeEnum}。
 *
 * <h3>本次改动要点</h3>
 * <ol>
 *   <li><b>补注册 ECG 专属 action</b>:接口文档 6.4/6.5 定义
 *       {@code S050402}(ECG 检查预约状态新增)、{@code S050502}(ECG 检查预约状态更新),
 *       与 pacs 的 {@code S050401}/{@code S050501} 并列。实测日志中这两个编号
 *       尚未出现(0 次),此处**主动兼容**,避免院方切换编号时消息被拒。</li>
 *   <li><b>兼容无后缀写法</b>:文档定义 {@code S0201}/{@code S0202},
 *       而实测报文为 {@code S0201ECG}/{@code S0202ECG},两者均接收。</li>
 *   <li><b>细化错误码</b>:原实现把所有异常统一压成
 *       {@code APPOINTMENT_CREATE_FAIL}("申请单创建失败"),
 *       导致排障困难(本次问题排查成本即来自此)。现按
 *       「未知 action / 报文解析失败 / 记录不存在 / 其他」区分返回。</li>
 * </ol>
 *
 * <h3>关于 S0405</h3>
 * {@code S0405}(申请单状态更新)是**全院通用**服务,医院会把所有检查科室
 * (CT/MRI/DR/超声…)的状态一并推送。业务层已按心电范围过滤,
 * 非心电单只记日志、不处理(详见 {@code HisEcgFilter})。
 *
 * @author 心电排队装机系统
 */
@Tag(name = "供第三方调用接口")
@RestController
@RequestMapping("/ecg/external")
@@ -27,18 +53,20 @@
@Slf4j
public class ExternalController {
    /** 请求头中的 action 键名 */
    private static final String HEADER_ACTION = "action";
    @Resource
    private AppointmentService appointmentService;
    /**
     * 通用接口
     * <p>
     * 支持以下action类型:
     * - S0201ECG: 预约创建
     * - S0202ECG: 预约更新
     * 通用接口。
     *
     * @param dataMap 请求数据
     * @param headers 请求头,必须包含action字段
     * <p>支持的 action 见 {@link ActionTypeEnum},包括:
     * 申请单新增/更新、申请单状态更新、检查预约状态新增/更新。
     *
     * @param dataMap 请求数据(HL7 V3 转换后的 Map)
     * @param headers 请求头,必须包含 {@code action}
     * @return 处理结果
     */
    @PermitAll
@@ -47,34 +75,54 @@
    public CommonResult<Boolean> generalInterface(
            @RequestBody @NotNull(message = "请求数据不能为空") Map<String, Object> dataMap,
            @RequestHeader @NotEmpty(message = "请求头不能为空") Map<String, String> headers) {
        log.info("[generalInterface][开始处理请求 action({}) dataMap({})]", headers.get("action"), dataMap);
        String actionType = headers.get("action");
        ActionTypeEnum action = ActionTypeEnum.getByType(actionType);
        String rawAction = headers.get(HEADER_ACTION);
        ActionTypeEnum action = ActionTypeEnum.getByType(rawAction);
        if (action == null) {
            log.warn("[generalInterface][未知的action类型 action({})]", actionType);
            throw exception(APPOINTMENT_CREATE_FAIL);
            // 不再吞成"申请单创建失败",明确告知 action 不受支持
            log.warn("[generalInterface][不支持的 action({}),请核对接口文档 6.x 交易编号]", rawAction);
            throw exception(HIS_ACTION_NOT_SUPPORTED);
        }
        log.info("[generalInterface][开始处理请求 action({}) dataMap({})]", rawAction, dataMap);
        try {
            // 使用策略模式处理不同的action
            Map<ActionTypeEnum, Consumer<Map<String, Object>>> actionHandlers = Map.of(
                    ActionTypeEnum.S0201ECG, appointmentService::handleAppointmentCreate,
                    ActionTypeEnum.S0202ECG, appointmentService::handleAppointmentUpdate,
                    ActionTypeEnum.S040501HIS, appointmentService::handleAppointmentStateUpdate,
                    ActionTypeEnum.S050401, appointmentService::handleCheckAppointmentUpdate,
                    ActionTypeEnum.S050501, appointmentService::handleCheckAppointmentUpdate
            );
            Consumer<Map<String, Object>> handler = actionHandlers.get(action);
            if (handler == null) {
                throw exception(APPOINTMENT_CREATE_FAIL);
            }
            handler.accept(dataMap);
            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) {
            throw exception(APPOINTMENT_CREATE_FAIL);
            // 未预期异常:报文解析失败、字段缺失导致的 NPE/类型转换异常等
            log.error("[generalInterface][报文处理异常 action({}) dataMap({})]", rawAction, dataMap, e);
            throw exception(HIS_MESSAGE_PARSE_FAIL);
        }
    }
    /**
     * 按 action 分派到对应处理器。
     * <p>
     * 使用 {@link ActionTypeEnum} 的分组方法而非 Map 映射,是为了让
     * 「同一服务的多种编号」(pacs / ECG、有后缀 / 无后缀)走同一处理器,
     * 避免新增编号时漏配。
     */
    private void dispatch(ActionTypeEnum action, Map<String, Object> 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);
        }
    }
}