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