SpringBoot-springboot專案接收解析HL7 V2.3.1協議報文

skystrivegao發表於2024-11-29

背景

本協議基於 HL7 v2.3.1 來定義
接收訊息型別為:ORU^R01(樣本結果)

每收到一條樣本結果,需要回應一條樣本應答訊息(ACK)

7Edit工具模擬HL7協議請求

工具安裝包地址:
透過百度網盤分享的檔案:7Edit
連結:https://pan.baidu.com/s/12P-8RdsMyYBAaR7R6r8jGg
提取碼:sky1

File - new - 選擇自己的協議型別(此處我用的是ORU^R01)和版本(此處我用的是 2.3.1)

image

報文例子:

點選檢視程式碼
MSH|^~\&|BC-5300|Mindray|||20081120171602||ORU^R01|1|Q|2.3.1||||||UNICODE
PID|1|7393670^^^^MR|患者ID唯一標識^劉佳|19950804000000|患者姓名|||F
PV1||I|LAB^LABORATORY^|||SUR^SURGEON^DR^^^^^|||||||
OBR|1|234567890|151515^LAB TEST PANEL^L|1|202301011100||||||||N|||F|||||202301011200
OBX|1|ST|151515^GLUCOSE^L|1|102|mg/dL|70_105|N|||F
OBX|2|NM|151516^CHOLESTEROL^L|1|185|mg/dL|<200|N|||F
OBX|3|ST|151517^HEMOGLOBIN^L|1|14.5|g/dL|12.0_16.0|N|||F
OBX|4|NM|151518^WHITE BLOOD CELL COUNT^L|1|7.2|K/uL|4.0_11.0|N|||F

程式碼

pom檔案引入Hapi依賴

點選檢視程式碼
<!--        HL7協議-全自動血液細胞分析儀-->
        <dependency>
            <groupId>ca.uhn.hapi</groupId>
            <artifactId>hapi-base</artifactId>
            <version>2.3</version> <!-- 請根據實際情況選擇最新版本 -->
        </dependency>
        <dependency>
            <groupId>ca.uhn.hapi</groupId>
            <artifactId>hapi-structures-v231</artifactId>
            <version>2.3</version> <!-- 請根據實際情況選擇最新版本 -->
        </dependency>

建立Component元件 配置ourHl7Server服務資訊

點選檢視程式碼
package io.hk.modules.ws.config;

import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.app.HL7Service;
import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * <p>@desc      OURHl7Server服務
 * 用於全自動血液細胞分析儀資料採集
 * 監聽HL7協議傳送請求
 * <p>@author    gaoyt
 * <p>@date      2024/11/22 15:24
 **/
@Component
public class BasicListenerWithoutMessageHandling {

    @Autowired
    private MyReceivingApplication myReceivingApplication;

    private static int PORT_NUMBER = 1919;
    private static HapiContext context = new DefaultHapiContext();

    @PostConstruct
    public void newHL7Service() throws Exception {
        try {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    10, 3100,
                    30, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(100));
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            MinLowerLayerProtocol mllp = new MinLowerLayerProtocol();
            mllp.setCharset("UTF-8");
            context.setLowerLayerProtocol(mllp);
            context.setExecutorService(executor);
            // 是否使用TLS/SSL
            boolean useSecureConnection = false;
            HL7Service ourHl7Server = context.newServer(PORT_NUMBER, useSecureConnection);
            ourHl7Server.registerApplication(myReceivingApplication);
            ourHl7Server.startAndWait();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Hapi註冊單元(獲取報文和解析儲存)

其中重寫processMessage 方法用於HL協議報文解析

點選檢視程式碼
package io.hk.modules.ws.config;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.model.*;
import ca.uhn.hl7v2.parser.PipeParser;
import ca.uhn.hl7v2.protocol.ReceivingApplication;
import ca.uhn.hl7v2.protocol.ReceivingApplicationException;
import ca.uhn.hl7v2.util.Terser;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.hk.common.DateUtil;
import io.hk.common.StringUtil;
import io.hk.common.utils.LogUtil;

import io.hk.common.utils.R;
import io.hk.common.utils.UuidUtil;
import io.hk.modules.ws.entity.IfInspResultDetailEntity;
import io.hk.modules.ws.entity.IfInspResultEntity;
import io.hk.modules.ws.entity.IfHealthInspDataEntity;
import io.hk.modules.ws.service.IfHealthInspDataService;
import io.hk.modules.ws.service.IfInspResultDetailService;
import io.hk.modules.ws.service.IfInspResultService;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.ThreadContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;

/**
 * <p>@desc      Hapi註冊單元
 * <p>@author    gaoyt
 * <p>@date      `2024/11/22 15:27
 **/
@Component
public class MyReceivingApplication implements ReceivingApplication {

    @Autowired
    IfInspResultDetailService ifInspResultDetailService;
    @Autowired
    IfInspResultService ifInspResultService;
    @Autowired
    private SecurityManager securityManager;
    @Autowired
    private IfHealthInspDataService ifHealthInspDataService;

    /**
     * <p>@desc      處理全自動血液細胞分析儀發來的報文資料
     * <p>@author    gaoyt
     * <p>@date      2024/11/22 15:27
     *
     * @param theMessage
     * @param theMetadata
     **/
    @Override
    @Transactional
    public Message processMessage(Message theMessage, Map theMetadata) throws ReceivingApplicationException, HL7Exception {
        String username = "processMessage";
        LogUtil.writelog("====================全自動血液細胞分析儀-獲取HL7報文-開始=============================", username);
        // ACK應答
        Message response = null;
        ThreadContext.bind(securityManager);
        try {
            // 解析儲存HL7報文資料
            R result = analysisModule(theMessage, theMetadata);
            if ("0".equals(result.get("code").toString())) {
                // AA  接收
                response = theMessage.generateACK();
            } else if ("101".equals(result.get("code").toString())) {
                // AE 丟失必填欄位
                response = theMessage.generateACK("AE", new HL7Exception("101"));
            } else {
                // AE 錯誤
                response = theMessage.generateACK("AE", new HL7Exception("207"));
            }
        } catch (Exception e) {
            // AE 錯誤
            try {
                response = theMessage.generateACK("AE", new HL7Exception("207"));
            } catch (Exception ex) {
                LogUtil.writeExceptionLog(ex, username);
            }
            LogUtil.writeExceptionLog(e, username);
        } finally {
            ThreadContext.unbindSecurityManager();
            LogUtil.writelog("====================全自動血液細胞分析儀-獲取HL7報文-結束=============================", username);
        }
        return response;
    }


    public R analysisModule(Message hapiMsg, Map hapiMetadata) {
        R result = R.ok();
        String username = "analysisModule";
        LogUtil.writelog("====================全自動血液細胞分析儀-解析HL7報文-開始=============================", username);
        try {
            // 獲取體檢結果
            R ifInspR = getifInspResultEntity(hapiMsg);
            if ("0".equals(ifInspR.get("code").toString())) {
                IfInspResultEntity ifInspResult = (IfInspResultEntity) ifInspR.get("ifInspResult");
                // 體檢結果ID
                String ifInspResultId = UuidUtil.get32UUID();
                ifInspResult.setSysid(ifInspResultId);
                // 獲取體檢結果明細
                R ifInspResultDetailR = getifInspResultDetail(hapiMetadata, ifInspResult);
                List<IfInspResultDetailEntity> ifInspResultDetailList = (List<IfInspResultDetailEntity>) ifInspResultDetailR.get("ifInspResultDetailList");
                if ("0".equals(ifInspResultDetailR.get("code").toString())) {
                    // 儲存到資料庫
                    R saveResult = saveHapi(ifInspResult, ifInspResultDetailList);
                    if (!"0".equals(saveResult.get("code").toString())) {
                        result.put("code", 1);
                        result.put("msg", "儲存體檢結果失敗");
                        return result;
                    }
                } else if ("101".equals(ifInspResultDetailR.get("code").toString())) {
                    result.put("code", 101);
                    result.put("msg", "體檢結果明細丟失必須的欄位");
                    return result;
                } else {
                    result.put("code", 1);
                    result.put("msg", "獲取體檢結果明細失敗");
                    return result;
                }
            } else if ("101".equals(ifInspR.get("code").toString())) {
                // 必填項校驗不透過
                result.put("code", 101);
                result.put("msg", "體檢結果丟失必須的欄位");
                return result;
            } else {
                result.put("code", 1);
                result.put("msg", "獲取體檢結果失敗");
                return result;
            }
        } catch (Exception e) {
            result.put("code", 1);
            result.put("msg", "異常:" + e.getMessage());
            LogUtil.writeExceptionLog(e, username);
        } finally {
            LogUtil.writelog("====================全自動血液細胞分析儀-解析HL7報文-結束=============================", username);
        }
        return result;
    }

    private R saveHapi(IfInspResultEntity ifInspResult, List<IfInspResultDetailEntity> ifInspResultDetailList) {
        R result = R.ok();
        String username = "saveHapi";
        LogUtil.writelog("====================全自動血液細胞分析儀-儲存HL7報文-開始=============================", username);
        try {
            ifInspResultService.save(ifInspResult);
            ifInspResultDetailService.saveBatch(ifInspResultDetailList);
        } catch (Exception e) {
            result.put("code", 1);
            result.put("msg", "異常:" + e.getMessage());
            LogUtil.writeExceptionLog(e, username);
        } finally {
            LogUtil.writelog("====================全自動血液細胞分析儀-儲存HL7報文-結束=============================", username);
        }
        return result;
    }

    /**
     * <p>@desc      獲取體檢結果
     * <p>@author    gaoyt
     * <p>@date      2024/11/25 10:37
     *
     * @param hapiMsg
     **/
    private R getifInspResultEntity(Message hapiMsg) {
        R result = R.ok();
        String username = "getifInspResult";
        LogUtil.writelog("====================全自動血液細胞分析儀-獲取體檢結果-開始=============================", username);
        IfInspResultEntity ifInspResult = new IfInspResultEntity();
        try {
            // 患者段資訊
            Terser terser = new Terser(hapiMsg);
            // 患者姓名
            String patientName = terser.get("/.PID-5");
            // 患者性別 'M':表示男性(Male) 'F':表示女性(Female)
            String patientSex = terser.get("/.PID-8");
            // 存入輸入庫 0表示男性 1表示女性
            String sex = "M".equals(patientSex) ? "0" : "1";
            // 患者生日
            String patientBirth = terser.get("/.PID-7");
            // 根據出生日期獲取到年齡
            int patientAge = DateUtil.getAgeByBirth(patientBirth);
            // 觀察日期 (察請求的日期和時間)
            String patientCheckDate = terser.get("/.OBR-7");
            Date checkDate = DateUtil.getDateTime(patientCheckDate);
            // fileOrderNumber 預約編號
            String inspNo = terser.get("/.OBR-3");
            // 根據預約編號查詢省份ID
            IfHealthInspDataEntity ifHealthInspData = ifHealthInspDataService.getBaseMapper().selectOne(new QueryWrapper<IfHealthInspDataEntity>()
                    .eq("is_del", 0)
                    .eq("is_active", 0)
                    .eq("insp_no", inspNo));
            // 校驗必填項
            boolean flag = StringUtil.validateStrings(inspNo, patientCheckDate);
            if (!flag) {
                // 存在必填項為空
                result.put("code", 101);
                result.put("msg", "丟失必須的欄位");
                return result;
            }
            // 體檢姓名
            ifInspResult.setInspName(patientName);
            // 性別(0-男,1-女)
            ifInspResult.setSex(sex);
            // 年齡
            ifInspResult.setAge(patientAge);
            // 血常規:BRT
            ifInspResult.setHisItemsGroupCode("BRT");
            // 是否存在影像檔案,N-否、Y-是
            ifInspResult.setIsFile("N");
            // 檢查日期
            ifInspResult.setCheckDate(checkDate);
            // 預約編號
            ifInspResult.setInspNo(inspNo);
            if (ifHealthInspData != null) {
                // 體檢機構ID
                String corpId = ifHealthInspData.getCorpId();
                ifInspResult.setAppId(corpId);
            }
        } catch (Exception e) {
            result.put("code", 1);
            result.put("msg", "異常:" + e.getMessage());
            LogUtil.writeExceptionLog(e, username);
        } finally {
            LogUtil.writelog("====================全自動血液細胞分析儀-解析HL7患者標識段-結束=============================", username);
        }
        result.put("ifInspResult", ifInspResult);
        return result;
    }

    /**
     * <p>@desc      獲取體檢結果明細
     * <p>@author    gaoyt
     * <p>@date      2024/11/25 10:35
     *
     * @param hapiMetadata
     **/
    private R getifInspResultDetail(Map hapiMetadata, IfInspResultEntity ifInspResult) {
        R result = R.ok();
        String username = "getifInspResultDetail";
        LogUtil.writelog("====================全自動血液細胞分析儀-獲取體檢結果明細-開始=============================", username);
        List<IfInspResultDetailEntity> ifInspResultDetailList = new ArrayList<>();
        try {
            // 獲取到報文字串
            String[] message = hapiMetadata.get("raw-message").toString().split("\r");
            // 拼接報文中所有OB前的報文
            String messageBeforeObx = "";
            for (String s : message) {
                if (!s.startsWith("OBX")) {
                    messageBeforeObx = messageBeforeObx + s + "\r";
                }
            }
            for (String s : message) {
                if (s.startsWith("OBX")) {
                    PipeParser parser = new PipeParser();
                    String messageTmp = messageBeforeObx + s;
                    Message parse = parser.parse(messageTmp);
                    Terser terserParse = new Terser(parse);
                    //  檢查專案 Observation Identifie
                    String obxIdentifier = terserParse.get("/.OBX-3");
                    // Observation Value:觀察結果的具體數值或描述 反映了醫療觀察的結果
                    String obxValue = terserParse.get("/.OBX-5");
                    // 觀察結果值的單位 例如,血糖值可能以毫摩爾每升(mmol/L)為單位,這有助於接收方正確理解觀察結果。
                    String obxUnits = terserParse.get("/.OBX-6");
                    // References Range 觀察結果的參考範圍
                    String obxRange = terserParse.get("/.OBX-7");
                    // Abnormal Flags: 用於標識觀察結果是否異常。 常見的標識包括正常、高、低、異常等。
                    // A:異常(Abnormal) N:正常(Normal) H:高於正常範圍(Higher than normal) L:低於正常範圍(Lower than normal) C:危急值(Critical)
                    String obxFlag = terserParse.get("/.OBX-8");
                    // 檢查日期
                    Date obxCheckDate = ifInspResult.getCheckDate();
                    // 校驗必填項
                    boolean flag = StringUtil.validateStrings(obxIdentifier, obxValue, obxUnits, obxRange, obxFlag);
                    if (!flag) {
                        // 存在必填項為空
                        result.put("code", 101);
                        result.put("msg", "丟失必須的欄位");
                        return result;
                    }
                    IfInspResultDetailEntity ifInspResultDetail = new IfInspResultDetailEntity();
                    // 檢查專案
                    ifInspResultDetail.setProjectCode(obxIdentifier);
                    // 檢測值
                    ifInspResultDetail.setResultValue(obxValue);
                    // 單位
                    ifInspResultDetail.setMeasure(obxUnits);
                    // 範圍
                    ifInspResultDetail.setRanges(obxRange);
                    // 結果表示
                    ifInspResultDetail.setResultFlag(obxFlag);
                    // 檢查日期
                    ifInspResultDetail.setCheckDate(obxCheckDate);
                    // 體檢結果ID
                    ifInspResultDetail.setHisInspResultId(ifInspResult.getSysid());

                    ifInspResultDetailList.add(ifInspResultDetail);
                }
            }
        } catch (Exception e) {
            result.put("code", 1);
            result.put("msg", "異常:" + e.getMessage());
            LogUtil.writeExceptionLog(e, username);
        } finally {
            LogUtil.writelog("====================全自動血液細胞分析儀-解析HL7觀察結果段-結束=============================", username);
        }
        result.put("ifInspResultDetailList", ifInspResultDetailList);
        return result;
    }

    @Override
    public boolean canProcess(Message message) {
        return true;
    }

}

相關文章