yxh
昨天 fce96ef468291fb9a0e6a4d34ab371315e9485d4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 / 集成平台)调用的通用接口。
 *
 * <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")
@Validated
@Slf4j
public class ExternalController {
 
    /** 请求头中的 action 键名 */
    private static final String HEADER_ACTION = "action";
 
    @Resource
    private AppointmentService appointmentService;
 
    /**
     * 通用接口。
     *
     * <p>支持的 action 见 {@link ActionTypeEnum},包括:
     * 申请单新增/更新、申请单状态更新、检查预约状态新增/更新。
     *
     * @param dataMap 请求数据(HL7 V3 转换后的 Map)
     * @param headers 请求头,必须包含 {@code action}
     * @return 处理结果
     */
    @PermitAll
    @Operation(summary = "通用接口")
    @PostMapping("/generalInterface")
    public CommonResult<Boolean> generalInterface(
            @RequestBody @NotNull(message = "请求数据不能为空") Map<String, Object> dataMap,
            @RequestHeader @NotEmpty(message = "请求头不能为空") Map<String, String> 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 分派到对应处理器。
     * <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);
        }
    }
}