背景
本協議基於 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)
報文例子:
點選檢視程式碼
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;
}
}