parent
7a73da34f2
commit
ecb584aa9a
@ -0,0 +1,63 @@ |
||||
package digital.laboratory.platform.inspection.config; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
/** |
||||
* 贵阳禁毒-情报平台推送数据配置 |
||||
*/ |
||||
@Component |
||||
@ConfigurationProperties(prefix = "gyjd.labscare.api") |
||||
public class ApiPathProperties { |
||||
/** |
||||
* api的ip |
||||
*/ |
||||
private String host; |
||||
|
||||
/** |
||||
* 委托书推送的接口 |
||||
*/ |
||||
private String entrustLetter; |
||||
|
||||
/** |
||||
* 确认书推送接口 |
||||
*/ |
||||
private String confirmLetter; |
||||
|
||||
/** |
||||
* 生物检材定性记录 |
||||
*/ |
||||
private String biologyQualitativeRecord; |
||||
|
||||
public String getHost() { |
||||
return host; |
||||
} |
||||
|
||||
public void setHost(String host) { |
||||
this.host = host; |
||||
} |
||||
|
||||
public String getEntrustLetter() { |
||||
return host + entrustLetter; |
||||
} |
||||
|
||||
public void setEntrustLetter(String entrustLetter) { |
||||
this.entrustLetter = entrustLetter; |
||||
} |
||||
|
||||
public String getConfirmLetter() { |
||||
return host + confirmLetter; |
||||
} |
||||
|
||||
public void setConfirmLetter(String confirmLetter) { |
||||
this.confirmLetter = confirmLetter; |
||||
} |
||||
|
||||
public String getBiologyQualitativeRecord() { |
||||
return host + biologyQualitativeRecord; |
||||
} |
||||
|
||||
public void setBiologyQualitativeRecord(String biologyQualitativeRecord) { |
||||
this.biologyQualitativeRecord = biologyQualitativeRecord; |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package digital.laboratory.platform.inspection.event; |
||||
|
||||
import lombok.Getter; |
||||
import org.springframework.context.ApplicationEvent; |
||||
|
||||
import java.time.Clock; |
||||
|
||||
/** |
||||
* 推送数据到LabsCare 数据平台上的spring 事件 |
||||
*/ |
||||
@Getter |
||||
public class PushDataToLabsCareEvent extends ApplicationEvent { |
||||
|
||||
/** |
||||
* 实验id |
||||
*/ |
||||
private final String testId; |
||||
|
||||
/** |
||||
* 推送的数据类型, 1 鉴定委托书数据 | 2 鉴定事项确认书数据 | 3 生物检材定性记录报告数据 |
||||
*/ |
||||
private final Integer pushType; |
||||
|
||||
public PushDataToLabsCareEvent(Object source, String testId, Integer pushType) { |
||||
super(source); |
||||
this.testId = testId; |
||||
this.pushType = pushType; |
||||
} |
||||
|
||||
public PushDataToLabsCareEvent(Object source, Clock clock, String testId, Integer pushType) { |
||||
super(source, clock); |
||||
this.testId = testId; |
||||
this.pushType = pushType; |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,51 @@ |
||||
package digital.laboratory.platform.inspection.listener; |
||||
|
||||
|
||||
import digital.laboratory.platform.inspection.event.PushDataToLabsCareEvent; |
||||
import digital.laboratory.platform.inspection.service.PushDataToLabsCareService; |
||||
import digital.laboratory.platform.inspection.threadpool.GlobalThreadPool; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
/** |
||||
* 监听有关推送数据到LabsCare 平台的事件, 采用异步 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class PushDataToLabsCareEventListener implements ApplicationListener<PushDataToLabsCareEvent> { |
||||
|
||||
@Resource |
||||
private PushDataToLabsCareService pushDataToLabsCareService; |
||||
|
||||
/** |
||||
* 处理委托数据推送事件 |
||||
* |
||||
* @param event 委托数据推送事件对象 |
||||
*/ |
||||
@Override |
||||
public void onApplicationEvent(PushDataToLabsCareEvent event) { |
||||
CompletableFuture.runAsync(() -> { |
||||
// 处理不同的推送类型
|
||||
switch (event.getPushType()) { |
||||
case 3: // 推送委托书数据
|
||||
try { |
||||
pushDataToLabsCareService.pushBiologyQualitativeRecordData(event.getTestId()); |
||||
} catch (Exception e) { |
||||
log.error("实验id为 {} 的生物定性检验记录数据推送失败!", event.getTestId(), e); |
||||
} |
||||
break; |
||||
default: |
||||
log.warn("未知的推送类型: {}", event.getPushType()); |
||||
break; |
||||
} |
||||
}, GlobalThreadPool.getInstance()).exceptionally(e -> { |
||||
log.error("推送数据到 LabsCare 失败", e); |
||||
return null; |
||||
}); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,99 @@ |
||||
package digital.laboratory.platform.inspection.service; |
||||
|
||||
import com.deepoove.poi.XWPFTemplate; |
||||
import digital.laboratory.platform.sys.entity.Area; |
||||
import digital.laboratory.platform.sys.entity.SysOrg; |
||||
import digital.laboratory.platform.sys.entity.SysUser; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 通用的feign请求封装接口服务层接口 |
||||
*/ |
||||
public interface CommonFeignService { |
||||
|
||||
/** |
||||
* 远程调用获取用户机构 |
||||
* @param orgId |
||||
* @return |
||||
*/ |
||||
SysOrg remoteGetSysOrg(String orgId); |
||||
|
||||
/** |
||||
* 根据权限和机构远程获取用户列表 |
||||
* |
||||
* @param orgId 组织ID |
||||
* @param permission 权限列表 |
||||
* @return 用户列表 |
||||
*/ |
||||
List<SysUser> remoteGetUsersByPermission(String orgId, List<String> permission); |
||||
|
||||
/** |
||||
* 通过机构ID远程获取机构所在省市信息 |
||||
* |
||||
* @param orgId 机构ID |
||||
* @return 包含机构所在省市信息的Area对象列表 |
||||
* @throws RuntimeException 当根据机构ID获取机构所在省市信息失败时抛出 |
||||
*/ |
||||
List<Area> remoteGetProvinceCityInfo(String orgId); |
||||
|
||||
/** |
||||
* 远程调用获取用户信息 |
||||
* @param username |
||||
* @return |
||||
*/ |
||||
SysUser remoteGetUserByUsername(String username); |
||||
|
||||
/** |
||||
* 远程调用获取用户信息 |
||||
* @param userId |
||||
* @return |
||||
*/ |
||||
SysUser remoteGetUserById(String userId); |
||||
|
||||
/** |
||||
* 远程调用生成word,并转成pdf |
||||
* |
||||
* @param template |
||||
* @param originalFilename 文件名 |
||||
* @param savePath 保存到minio路径 |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
boolean remoteGenerateWord2PDF(XWPFTemplate template, String originalFilename, String savePath) throws Exception; |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件 |
||||
* @param filePath minio上的文件路径 |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
ByteArrayInputStream remoteGetFile(String filePath) throws Exception; |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件 |
||||
* @param filePath minio上的文件路径 |
||||
* @param fileName 名称 |
||||
* @param httpServletResponse |
||||
* @throws Exception |
||||
*/ |
||||
void remoteGetFile(String filePath, String fileName, HttpServletResponse httpServletResponse) throws Exception; |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件列表 |
||||
* @param filePath minio上的文件路径 |
||||
*/ |
||||
List<String> remoteGetFileList(String filePath); |
||||
|
||||
/** |
||||
* 远程调用-上传文件 |
||||
* @param file 上传的文件对象 |
||||
* @param path 上传到minio的位置 |
||||
* @return |
||||
*/ |
||||
Map<String, String> remoteUploadFile(MultipartFile file, String path); |
||||
} |
@ -0,0 +1,17 @@ |
||||
package digital.laboratory.platform.inspection.service; |
||||
|
||||
/** |
||||
* @author ChenJiangBao |
||||
* @version 1.0 |
||||
* @description: 推送数据到labscare平台接口 |
||||
* @date 2025/3/31 9:28 |
||||
*/ |
||||
public interface PushDataToLabsCareService { |
||||
|
||||
/** |
||||
* 推送生物检材定性记录 |
||||
* @param testId 实验id |
||||
*/ |
||||
void pushBiologyQualitativeRecordData(String testId); |
||||
|
||||
} |
@ -0,0 +1,262 @@ |
||||
package digital.laboratory.platform.inspection.service.impl; |
||||
|
||||
import cn.hutool.core.io.IoUtil; |
||||
import cn.hutool.core.io.file.FileNameUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.deepoove.poi.XWPFTemplate; |
||||
import digital.laboratory.platform.common.core.constant.CommonConstants; |
||||
import digital.laboratory.platform.common.core.util.R; |
||||
import digital.laboratory.platform.common.feign.RemoteWord2PDFService; |
||||
import digital.laboratory.platform.common.oss.service.OssFile; |
||||
import digital.laboratory.platform.inspection.service.CommonFeignService; |
||||
import digital.laboratory.platform.sys.dto.UserInfo; |
||||
import digital.laboratory.platform.sys.entity.Area; |
||||
import digital.laboratory.platform.sys.entity.SysOrg; |
||||
import digital.laboratory.platform.sys.entity.SysUser; |
||||
import digital.laboratory.platform.sys.feign.RemoteOrgService; |
||||
import digital.laboratory.platform.sys.feign.RemoteUserService; |
||||
import feign.Response; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.io.output.ByteArrayOutputStream; |
||||
import org.springframework.mock.web.MockMultipartFile; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import javax.annotation.Resource; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.util.Base64; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 通用的feign请求封装接口服务层接口 实现类 |
||||
*/ |
||||
@Slf4j |
||||
@Service |
||||
public class CommonFeignServiceImpl implements CommonFeignService { |
||||
|
||||
@Resource |
||||
private RemoteOrgService remoteOrgService; |
||||
|
||||
@Resource |
||||
private RemoteUserService remoteUserService; |
||||
|
||||
@Resource |
||||
private RemoteWord2PDFService remoteWord2PDFService; |
||||
|
||||
@Resource |
||||
private OssFile ossFile; |
||||
|
||||
/** |
||||
* 根据用户获取远程系统机构信息 |
||||
* |
||||
* @param orgId 用户信息 |
||||
* @return 对应的远程系统机构信息 |
||||
* @throws RuntimeException 如果未找到对应的机构信息,则抛出运行时异常 |
||||
*/ |
||||
@Override |
||||
public SysOrg remoteGetSysOrg(String orgId) { |
||||
SysOrg sysOrg = null; |
||||
R<SysOrg> r = remoteOrgService.getByIdWithoutToken(orgId); |
||||
if (r != null && r.getCode() == CommonConstants.SUCCESS) { |
||||
sysOrg = r.getData(); |
||||
} else { |
||||
throw new RuntimeException(String.format("没有找到 orgId 为 %s 的机构, 请确认用户所属机构的正确性!", orgId)); |
||||
} |
||||
return sysOrg; |
||||
} |
||||
|
||||
/** |
||||
* 取指定环节可用的用户列表 |
||||
* 当流程进行到某个环节的时候, 需要某个用户对这个环节进行处理(通过或不通过)。 |
||||
* 这里取可用的用户列表 |
||||
* <p> |
||||
* 涉及到的环境有以下几个: |
||||
* 1、创建委托及提交 |
||||
* 委托的提交者就是委托的创建者。不存在不通过的可能。 |
||||
* 2、审核 |
||||
* 审核者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托审核权限 |
||||
* 3、审批 |
||||
* 审核者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托审批权限 |
||||
* 4、送检确认 |
||||
* 送检确认者有几个条件: (1)必须与委托的创建者是同一个机构, 或上级机构的人 (2)必须拥有委托送检确认权限 |
||||
* 5、受理 |
||||
* 受理者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托受理权限 |
||||
* |
||||
* @return |
||||
*/ |
||||
/** |
||||
* 根据权限和机构远程获取用户列表 |
||||
* |
||||
* @param orgId 组织ID |
||||
* @param permission 权限列表 |
||||
* @return 用户列表 |
||||
*/ |
||||
@Override |
||||
public List<SysUser> remoteGetUsersByPermission(String orgId, List<String> permission) { |
||||
R<List<SysUser>> r = remoteUserService.innerGetUsersByPermission(orgId, permission); |
||||
if (r != null && r.getCode() == CommonConstants.SUCCESS) { |
||||
return r.getData(); |
||||
} else { |
||||
throw new RuntimeException(String.format("根据权限 [%s] 和 机构id [%s] 远程获取用户列表!", permission, orgId)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 通过机构ID远程获取机构所在省市信息 |
||||
* |
||||
* @param orgId 机构ID |
||||
* @return 包含机构所在省市信息的Area对象列表 |
||||
* @throws RuntimeException 当根据机构ID获取机构所在省市信息失败时抛出 |
||||
*/ |
||||
@Override |
||||
public List<Area> remoteGetProvinceCityInfo(String orgId) { |
||||
List<Area> result = null; |
||||
R<List<Area>> r = remoteOrgService.fetchProvinceCityInfoByOrgId(orgId); |
||||
if (r != null && r.getCode() == CommonConstants.SUCCESS) { |
||||
result = r.getData(); |
||||
} else { |
||||
throw new RuntimeException("根据机构id获取机构所在省市信息失败!"); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 远程调用或者用户信息 |
||||
* @param username |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public SysUser remoteGetUserByUsername(String username){ |
||||
R<UserInfo> info = remoteUserService.innerGetUserInfoByUsername(username); |
||||
if (info != null && info.getCode() == CommonConstants.FAIL) { |
||||
throw new RuntimeException(String.format("获取用户名为 %s 的用户信息失败!", username)); |
||||
} |
||||
return info.getData().getSysUser(); |
||||
} |
||||
|
||||
/** |
||||
* 远程调用获取用户信息 |
||||
* @param userId |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public SysUser remoteGetUserById(String userId){ |
||||
R<SysUser> info = remoteUserService.innerGetById(userId); |
||||
if (info != null && info.getCode() == CommonConstants.FAIL) { |
||||
throw new RuntimeException(String.format("获取用户名id为 %s 的用户信息失败!", userId)); |
||||
} |
||||
return info.getData(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean remoteGenerateWord2PDF(XWPFTemplate template, String originalFilename, String savePath) throws Exception{ |
||||
ByteArrayOutputStream fosWord = new ByteArrayOutputStream(); |
||||
template.write(fosWord); |
||||
template.close(); |
||||
|
||||
//------------
|
||||
ByteArrayInputStream fisWord = new ByteArrayInputStream(fosWord.toByteArray()); |
||||
fosWord.close(); |
||||
|
||||
MockMultipartFile mockMultipartFile = new MockMultipartFile("file", originalFilename + ".docx", "image/jpg", fisWord); |
||||
Response response = remoteWord2PDFService.word2pdf(mockMultipartFile); |
||||
fisWord.close(); |
||||
|
||||
|
||||
ByteArrayOutputStream outPDF = new ByteArrayOutputStream(); |
||||
IoUtil.copy(response.body().asInputStream(), outPDF, IoUtil.DEFAULT_MIDDLE_BUFFER_SIZE); |
||||
ByteArrayInputStream isPDF = new ByteArrayInputStream(outPDF.toByteArray()); |
||||
outPDF.close(); |
||||
|
||||
boolean b = ossFile.fileSave(savePath, isPDF); |
||||
|
||||
isPDF.close(); |
||||
|
||||
log.info("转换为 PDF 结束"); |
||||
return b; |
||||
} |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件 |
||||
* @param filePath minio上的文件路径 |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
@Override |
||||
public ByteArrayInputStream remoteGetFile(String filePath) throws Exception { |
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
||||
ossFile.fileGet(filePath, bos); |
||||
|
||||
byte[] templateArray = bos.toByteArray(); |
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(templateArray); |
||||
bos.close(); |
||||
return bis; |
||||
} |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件 |
||||
* @param filePath minio上的文件路径 |
||||
* @param fileName 名称 |
||||
* @param httpServletResponse |
||||
* @throws Exception |
||||
*/ |
||||
@Override |
||||
public void remoteGetFile(String filePath, String fileName, HttpServletResponse httpServletResponse) throws Exception { |
||||
ossFile.fileGet(filePath, httpServletResponse.getOutputStream()); |
||||
if (StrUtil.isNotBlank(fileName)) { |
||||
httpServletResponse.setContentType(fileName); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 远程调用根据文件路径获取文件列表 |
||||
* @param filePath minio上的文件路径 |
||||
*/ |
||||
@Override |
||||
public List<String> remoteGetFileList(String filePath) { |
||||
if (StrUtil.isNotBlank(filePath)) { |
||||
List<String> fileNameList = ossFile.fileList(filePath); |
||||
List<String> fileList = fileNameList.stream().map(fileName -> { |
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
||||
try { |
||||
ossFile.fileGet(filePath + "/" + fileName, byteArrayOutputStream); |
||||
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
log.error("文件获取失败, 文件路径为: {}", filePath + "/" + fileName); |
||||
} |
||||
return null; |
||||
}).collect(Collectors.toList()); |
||||
return fileList; |
||||
} else { |
||||
throw new RuntimeException("文件路径不能为空!"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 远程调用-上传文件 |
||||
* @param file 上传的文件对象 |
||||
* @param path 上传到minio的位置 |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public Map<String, String> remoteUploadFile(MultipartFile file, String path) { |
||||
boolean r = ossFile.fileUpload(file, path); |
||||
if (r) { |
||||
HashMap<String, String> resultData = new HashMap<>(); |
||||
resultData.put("fileName", FileNameUtil.getName(file.getOriginalFilename())); |
||||
resultData.put("path", path); |
||||
log.info("文件上传成功!"); |
||||
return resultData; |
||||
} else { |
||||
String failMsg = String.format("文件名为 %s 的文件上传失败!", file.getOriginalFilename()); |
||||
log.error(failMsg); |
||||
throw new RuntimeException(failMsg); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,356 @@ |
||||
package digital.laboratory.platform.inspection.service.impl; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import cn.hutool.json.JSONObject; |
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||
import digital.laboratory.platform.inspection.config.ApiPathProperties; |
||||
import digital.laboratory.platform.inspection.constant.TestRecordSampleDataConstant; |
||||
import digital.laboratory.platform.inspection.convert.TestRecordSampleDataConverter; |
||||
import digital.laboratory.platform.inspection.entity.TestRecordInstrument; |
||||
import digital.laboratory.platform.inspection.entity.TestRecordMethod; |
||||
import digital.laboratory.platform.inspection.entity.TestRecordReagent; |
||||
import digital.laboratory.platform.inspection.entity.TestRecordSampleData; |
||||
import digital.laboratory.platform.inspection.mapper.TestRecordSampleDataMapper; |
||||
import digital.laboratory.platform.inspection.service.*; |
||||
import digital.laboratory.platform.inspection.vo.TestRecordReagentVO; |
||||
import digital.laboratory.platform.inspection.vo.TestRecordSampleDataExpandVO; |
||||
import digital.laboratory.platform.inspection.vo.TestRecordSampleDataVO; |
||||
import digital.laboratory.platform.inspetion.api.entity.EntrustInfo; |
||||
import digital.laboratory.platform.inspetion.api.entity.SampleInfo; |
||||
import digital.laboratory.platform.inspetion.api.entity.TestRecord; |
||||
import digital.laboratory.platform.othersys.utils.HttpsUtils; |
||||
import digital.laboratory.platform.sys.entity.Drug; |
||||
import digital.laboratory.platform.sys.entity.SysUser; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.http.*; |
||||
import org.springframework.scheduling.annotation.Scheduled; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.web.client.RestClientException; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.time.format.DateTimeFormatter; |
||||
import java.util.*; |
||||
import java.util.function.Function; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* @author ChenJiangBao |
||||
* @version 1.0 |
||||
* @description: 推送数据到labscare平台接口 |
||||
* @date 2025/3/31 9:31 |
||||
*/ |
||||
@Slf4j |
||||
@Service |
||||
public class PushDataToLabsCareServiceImpl implements PushDataToLabsCareService { |
||||
|
||||
@Resource |
||||
private TestRecordService testRecordService; |
||||
|
||||
@Resource |
||||
private EntrustInfoService entrustInfoService; |
||||
|
||||
@Resource |
||||
private SampleInfoService sampleInfoService; |
||||
|
||||
@Resource |
||||
private TestRecordReagentService testRecordReagentService; |
||||
|
||||
@Resource |
||||
private TestRecordSampleDataMapper testRecordSampleDataMapper; |
||||
|
||||
@Resource |
||||
private TestRecordInstrumentService testRecordInstrumentService; |
||||
|
||||
@Resource |
||||
private TestRecordMethodService testRecordMethodService; |
||||
|
||||
@Resource |
||||
private InspectRecordService inspectRecordService; |
||||
|
||||
@Resource |
||||
private ProcessInspectDataService processInspectDataService; |
||||
|
||||
@Resource |
||||
private CommonFeignService commonFeignService; |
||||
|
||||
@Resource |
||||
private ApiPathProperties apiPathProperties; |
||||
|
||||
|
||||
private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); |
||||
|
||||
// "#NULL#" 作为空数据的占位符, 对于系统中没有的信息以占位符代替
|
||||
public final static String NULL_PLACEHOLDER = "#NULL#"; |
||||
|
||||
private final static String BIOLOGY_QUALITATIVE_RECORD = "BiologyQualitativeRecord"; |
||||
|
||||
/** |
||||
* 做一个定时推送,对于推送失败的委托进行重新推送, 每天凌晨1点推送 |
||||
*/ |
||||
// @Scheduled(cron = "30 * * * * ?") // 测试
|
||||
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点执行
|
||||
public void timingPushDataToLabsCare() { |
||||
// 查询检验鉴定推送失败的委托
|
||||
List<EntrustInfo> entrustList = entrustInfoService.list( |
||||
Wrappers.<EntrustInfo>lambdaQuery() |
||||
.and(wrapper -> wrapper |
||||
.eq(EntrustInfo::getPushFlag, "") |
||||
.or() |
||||
.isNull(EntrustInfo::getPushFlag) |
||||
.or() |
||||
.like(EntrustInfo::getPushFlag, "false") |
||||
) |
||||
); |
||||
if (CollUtil.isEmpty(entrustList)) { |
||||
return; |
||||
} |
||||
Map<String, EntrustInfo> entrustInfoMap = entrustList.stream().collect(Collectors.toMap(EntrustInfo::getId, Function.identity())); |
||||
// 获取所有完成实验的数据
|
||||
List<TestRecord> testRecordList = testRecordService.list(Wrappers.<TestRecord>lambdaQuery() |
||||
.eq(TestRecord::getStatus, 5).in(TestRecord::getBusinessId, entrustList.stream() |
||||
.map(EntrustInfo::getId).collect(Collectors.toList())) |
||||
); |
||||
if (CollUtil.isEmpty(testRecordList)) { |
||||
return; |
||||
} |
||||
|
||||
for (TestRecord testRecord : testRecordList) { |
||||
EntrustInfo entrustInfo = entrustInfoMap.get(testRecord.getBusinessId()); |
||||
String pushFlag = entrustInfo.getPushFlag(); |
||||
if (StrUtil.isBlank(pushFlag)) { |
||||
pushBiologyQualitativeRecordData(testRecord.getId()); |
||||
} else { |
||||
List<String> flagList = StrUtil.split(pushFlag, StrUtil.COMMA).stream().filter(str -> str.contains("false")).collect(Collectors.toList()); |
||||
for (String flag : flagList) { |
||||
if (flag.contains(BIOLOGY_QUALITATIVE_RECORD)) { |
||||
pushBiologyQualitativeRecordData(testRecord.getId()); |
||||
} else { |
||||
|
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 推送生物检材定性记录 |
||||
* @param testId 实验id |
||||
*/ |
||||
@Override |
||||
public void pushBiologyQualitativeRecordData(String testId) { |
||||
TestRecord testRecord = testRecordService.getById(testId); |
||||
if (testRecord == null) { |
||||
log.error("实验id为 [{}] 的实验信息查询为空!", testId); |
||||
return; |
||||
} |
||||
// 获取委托信息
|
||||
EntrustInfo entrustInfo = entrustInfoService.getById(testRecord.getBusinessId()); |
||||
|
||||
JSONObject recordJsonPayload = buildBiologyQualitativeRecordJsonPayload(entrustInfo, testRecord); |
||||
|
||||
String successFlag = BIOLOGY_QUALITATIVE_RECORD + ":true"; |
||||
String failureFlag = BIOLOGY_QUALITATIVE_RECORD + ":false"; |
||||
try { |
||||
ResponseEntity<String> response = exchangeRemoteApi(recordJsonPayload, apiPathProperties.getBiologyQualitativeRecord()); |
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody().contains("\"status\":200")) { |
||||
updatePushFlag(entrustInfo, BIOLOGY_QUALITATIVE_RECORD, successFlag); |
||||
log.info("推送生物定性检验记录数据成功, 受理编号: {}, 响应: {}", entrustInfo.getAcceptNo(), response.getBody()); |
||||
} else { |
||||
updatePushFlag(entrustInfo, BIOLOGY_QUALITATIVE_RECORD, failureFlag); |
||||
log.warn("推送生物定性检验记录数据失败, 受理编号: {}, 状态码: {}, 响应: {}", |
||||
entrustInfo.getAcceptNo(), response.getStatusCode(), response.getBody()); |
||||
} |
||||
} catch (RestClientException e) { |
||||
log.error("推送生物定性检验记录数据到 LabsCare 失败, 受理编号: {}", entrustInfo.getAcceptNo(), e); |
||||
} |
||||
} |
||||
|
||||
private JSONObject buildBiologyQualitativeRecordJsonPayload(EntrustInfo entrustInfo, TestRecord testRecord) { |
||||
// 查询封装需要用到的数据
|
||||
List<SampleInfo> sampleInfoList = sampleInfoService |
||||
.lambdaQuery() |
||||
.in(SampleInfo::getId, testRecord.getSampleTestList()) |
||||
.list(); |
||||
// 检材转map
|
||||
Map<String, SampleInfo> sampleInfoMap = sampleInfoList.stream().collect(Collectors.toMap(SampleInfo::getAcceptNo, Function.identity())); |
||||
// 对受理编号进行分隔
|
||||
List<String> splitAcceptNo = StrUtil.split(entrustInfo.getAcceptNo(), "-"); |
||||
|
||||
// 设置使用的标准物质和试剂
|
||||
List<TestRecordReagentVO> references = testRecordReagentService.voListByWrapper(Wrappers.<TestRecordReagent>lambdaQuery() |
||||
.in(TestRecordReagent::getId, testRecord.getReagentConsumablesList()) |
||||
.eq(TestRecordReagent::getCategory, "标准物质")); |
||||
// 本次实验使用的标准品
|
||||
String stdStr = references.stream().map(TestRecordReagentVO::getReagentConsumableName).collect(Collectors.joining("、")); |
||||
|
||||
// 获取标准物质对应的毒品成分
|
||||
Map<String, Drug> drugMap = references.stream().map(TestRecordReagentVO::getDrug).collect(Collectors.toMap(Drug::getEnglishName, Function.identity())); |
||||
|
||||
// 获取检测人用户信息
|
||||
SysUser testUser = commonFeignService.remoteGetUserById(testRecord.getTestUserId()); |
||||
|
||||
// 检测数据信息
|
||||
Map<String, List<TestRecordSampleDataVO>> testDataGroupBySampleNO = testRecordSampleDataMapper |
||||
.queryTestRecordSampleDataVOList(Wrappers.<TestRecordSampleData>lambdaQuery() |
||||
.eq(TestRecordSampleData::getTestId, testRecord.getId()) |
||||
.eq(TestRecordSampleData::getSampleType, TestRecordSampleDataConstant.SAMPLE_TYPE_ANALYTE) |
||||
) // 数据库查询, 这个是非标准物质的检测数据
|
||||
.stream().collect(Collectors.groupingBy(TestRecordSampleDataVO::getSampleNo)); // 根据查询出来的结果进行分组
|
||||
|
||||
Map<String, TestRecordSampleDataVO> stdTestDataMap = testRecordSampleDataMapper |
||||
.queryTestRecordSampleDataVOList(Wrappers.<TestRecordSampleData>lambdaQuery() |
||||
.eq(TestRecordSampleData::getTestId, testRecord.getId()) |
||||
.eq(TestRecordSampleData::getSampleType, TestRecordSampleDataConstant.SAMPLE_TYPE_STD) |
||||
) // 数据库查询, 这个是非标准物质的检测数据
|
||||
.stream().collect(Collectors.toMap(TestRecordSampleDataVO::getCompoundName, Function.identity())); // 根据查询出来的结果进行分组
|
||||
|
||||
// 查询使用的仪器
|
||||
List<String> deviceIdList = testRecord.getDeviceIdList(); |
||||
List<TestRecordInstrument> instruments = CollectionUtils.isEmpty(deviceIdList) |
||||
? Collections.emptyList() |
||||
: testRecordInstrumentService.list(Wrappers.<TestRecordInstrument>lambdaQuery() |
||||
.in(TestRecordInstrument::getId, deviceIdList)); |
||||
String useInstrumentNameStr = CollectionUtils.isEmpty(instruments) |
||||
? NULL_PLACEHOLDER |
||||
: instruments.stream() |
||||
.map(TestRecordInstrument::getInstrumentName) |
||||
.collect(Collectors.joining(",")); |
||||
|
||||
// 使用的方法
|
||||
List<String> testMethodList = testRecord.getTestMethodList(); |
||||
List<TestRecordMethod> testRecordMethodList = CollectionUtils.isEmpty(testMethodList) |
||||
? Collections.emptyList() |
||||
: testRecordMethodService.list(Wrappers.<TestRecordMethod>lambdaQuery() |
||||
.in(TestRecordMethod::getId, testMethodList)); |
||||
String useMethodNameStr = CollectionUtils.isEmpty(instruments) |
||||
? NULL_PLACEHOLDER |
||||
: testRecordMethodList.stream() |
||||
.map(TestRecordMethod::getMethodName) |
||||
.collect(Collectors.joining(",")); |
||||
|
||||
// 开始封装数据参数
|
||||
JSONObject jsonObject = new JSONObject(); |
||||
jsonObject.set("jcxz", inspectRecordService.buildMaterialCharacterDesc(sampleInfoList)); // 检材性状描述
|
||||
jsonObject.set("jdwsh", String.format("[%s]%s号", splitAcceptNo.get(0), splitAcceptNo.get(1))); // 鉴定文书号
|
||||
jsonObject.set("jysj", testRecord.getTestStartDate().format(formatter)); // 检验时间
|
||||
jsonObject.set("jyy1qm", testUser.getName()); // 检测人1
|
||||
jsonObject.set( |
||||
"jyyq", |
||||
String.format("对所送检材中是否含有 %s 成分进行定性检测", stdStr) |
||||
); // 检验要求,根据模板上的值来生成
|
||||
jsonObject.set("sjr1", entrustInfo.getDeliver1Name()); // 送检人1
|
||||
jsonObject.set("sjr2", entrustInfo.getDeliver2Name()); // 送检人2
|
||||
jsonObject.set("slrq", entrustInfo.getAcceptDate().format(formatter)); // 受理日期
|
||||
jsonObject.set("sprqm", testUser.getName()); // 检测人2
|
||||
jsonObject.set("swmbw", stdStr); // 生物检测目标物
|
||||
jsonObject.set("tpfj", NULL_PLACEHOLDER); // 图谱
|
||||
jsonObject.set("wtdw", entrustInfo.getEntrustDepartment()); |
||||
|
||||
// 生物检材定性记录样品信息
|
||||
List<JSONObject> sampleJsonList = new ArrayList<>(); |
||||
testDataGroupBySampleNO.forEach((sampleNo, testDataList) -> { |
||||
JSONObject sampleJson = new JSONObject(); |
||||
sampleJson.set("caseName", sampleNo); // 样品编号
|
||||
|
||||
// 该样品编号对应的检测数据信息
|
||||
List<JSONObject> gauging = testDataList.stream().map(data -> { |
||||
JSONObject testDataJson = new JSONObject(); |
||||
testDataJson.set("blsjxdwc", data.getRtTimeError()); // 保留时间相对误差(%)
|
||||
TestRecordSampleDataVO stdData = stdTestDataMap.get(data.getCompoundName()); |
||||
testDataJson.set("bzwzblsj", stdData.getTargetRtTime()); // 标准物质保留时间(min)
|
||||
testDataJson.set("bzwzfdb", stdData.getIonAbundanceRatio()); // 标准物质离子对相对丰度比
|
||||
testDataJson.set("bzwzwb", stdData.getCompoundName()); // 标准物质名称
|
||||
testDataJson.set("dcypfdb", data.getIonAbundanceRatio()); // 待测样品离子对相对丰度比
|
||||
testDataJson.set("dxlzd", data.getExpandList().stream().map(TestRecordSampleDataExpandVO::getQualitativeIonPair).collect(Collectors.joining(","))); // 定性离子对
|
||||
testDataJson.set("dxyq", useInstrumentNameStr); // 使用仪器
|
||||
testDataJson.set("fdbwc", data.getIonAbundanceRatioError()); // 丰度比相对偏差
|
||||
testDataJson.set("ffStr", useMethodNameStr); // 所用的方法
|
||||
testDataJson.set("gradientA", NULL_PLACEHOLDER); // 梯度洗脱参考条件-A(%)
|
||||
testDataJson.set("gradientB", NULL_PLACEHOLDER); // 梯度洗脱参考条件-B(%)
|
||||
testDataJson.set("gradientTime", NULL_PLACEHOLDER); // 梯度洗脱参考条件-时间(min)
|
||||
testDataJson.set("jcxz", sampleInfoMap.get(data.getSampleNo()).getForm()); // 检材性状描述
|
||||
testDataJson.set("jdwsh", String.format("[%s]%s号", splitAcceptNo.get(0), splitAcceptNo.get(1))); // 鉴定文书号
|
||||
testDataJson.set("jdyj", inspectRecordService.buildInspectOpinion(Collections.singletonList(TestRecordSampleDataConverter.voToEntity(data)))); |
||||
testDataJson.set("jgpd", data.getWhetherCheckOut()); // 结果判定
|
||||
testDataJson.set("jysj", testRecord.getTestStartDate().format(formatter)); // 检验时间
|
||||
testDataJson.set("jyyq", String.format("对所送检材中是否含有 %s 成分进行定性检测", stdData.getCompoundCnName())); // 检验要求
|
||||
testDataJson.set("mbwblsj", data.getTargetRtTime()); // 目标物保留时间(min)
|
||||
testDataJson.set("projectName", "定性检测"); // 化合物名称, 对方公司并未标明该字段信息
|
||||
// 获取对应的化合物信息
|
||||
Drug drug = drugMap.get(data.getCompoundName()); |
||||
testDataJson.set("pzdy", StrUtil.join(",", drug.getMainCollisionEnergy(), drug.getMinorCollisionEnergy())); // 碰撞电压
|
||||
testDataJson.set("rjwb", NULL_PLACEHOLDER); // 溶剂
|
||||
testDataJson.set("sjr1", entrustInfo.getDeliver1Name()); // 送检人1
|
||||
testDataJson.set("sjr2", entrustInfo.getDeliver2Name()); // 送检人2
|
||||
testDataJson.set("slrq", entrustInfo.getAcceptDate().format(formatter)); // 受理日期
|
||||
testDataJson.set("swbzgzyzb", NULL_PLACEHOLDER); // 标准工作溶液制备
|
||||
testDataJson.set("swdcypzb", NULL_PLACEHOLDER); // 待测样品制备
|
||||
testDataJson.set("swmbw", drug.getName()); // 目标物
|
||||
testDataJson.set("swsj", NULL_PLACEHOLDER); // 上机
|
||||
testDataJson.set("swyxtj", NULL_PLACEHOLDER); // 液相条件
|
||||
testDataJson.set("swzptj", NULL_PLACEHOLDER); // 质谱条件
|
||||
testDataJson.set("wjcjgpd", "±" + processInspectDataService.getHairCaseIonAbundanceRatioWithinErrorRange(stdData.getIonAbundanceRatio().doubleValue())); //相对离子丰度比的最大允许相对误差(%)
|
||||
testDataJson.set("wtdw", entrustInfo.getEntrustDepartment()); |
||||
testDataJson.set("wzdy", StrUtil.join(",", drug.getMainDeclusteringPotential(), drug.getMinorDeclusteringPotential())); |
||||
return testDataJson; |
||||
}).collect(Collectors.toList()); |
||||
sampleJson.set("gauging", gauging); // 生物检材定性记录检测信息
|
||||
sampleJsonList.add(sampleJson); |
||||
}); |
||||
jsonObject.set("sample", sampleJsonList); |
||||
return jsonObject; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 通过POST请求与远程API进行交互 |
||||
* |
||||
* @param jsonObject 包含请求数据的JSONObject对象 |
||||
* @param url 远程API的URL |
||||
* @return ResponseEntity<String> 包含远程API响应数据的ResponseEntity对象 |
||||
*/ |
||||
private ResponseEntity<String> exchangeRemoteApi(JSONObject jsonObject, String url) { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setContentType(MediaType.APPLICATION_JSON); // 设置为 application/json
|
||||
HttpEntity<JSONObject> requestEntity = new HttpEntity<>(jsonObject, headers); |
||||
ResponseEntity<String> response = HttpsUtils |
||||
.genRestTemplate() |
||||
.exchange( |
||||
url, |
||||
HttpMethod.POST, |
||||
requestEntity, |
||||
String.class |
||||
); |
||||
return response; |
||||
} |
||||
|
||||
/** |
||||
* 更新推送标志 |
||||
* |
||||
* @param entrust 委托对象 |
||||
* @param pushType 推送类型 EntrustLetter 委托书 | ItemConfirmLetter 事项确认书 |
||||
* @param newFlag 新的推送标志 |
||||
*/ |
||||
private void updatePushFlag(EntrustInfo entrust, String pushType, String newFlag) { |
||||
List<String> flagList = Optional.ofNullable(StrUtil.split(entrust.getPushFlag(), StrUtil.COMMA)) |
||||
.orElse(new ArrayList<>()); |
||||
|
||||
// 判断是否已有 指定的推送类型,如果有就替换,否则添加
|
||||
boolean exists = flagList.stream().anyMatch(flag -> flag.contains(pushType)); |
||||
if (exists) { |
||||
flagList.replaceAll(flag -> flag.contains(pushType) ? newFlag : flag); |
||||
} else { |
||||
flagList.add(newFlag); |
||||
} |
||||
// 更新标识, 防止定时任务那里连续推送数据时推送标识更新出错
|
||||
entrust.setPushFlag(String.join(",", flagList)); |
||||
// 更新数据库
|
||||
entrustInfoService.update(Wrappers.<EntrustInfo>lambdaUpdate() |
||||
.eq(EntrustInfo::getId, entrust.getId()) |
||||
.set(EntrustInfo::getPushFlag, String.join(",", flagList))); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,52 @@ |
||||
package digital.laboratory.platform.inspection.threadpool; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.PreDestroy; |
||||
import java.util.concurrent.*; |
||||
|
||||
/** |
||||
* 定义全局线程池 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class GlobalThreadPool { |
||||
|
||||
// 定义全局线程池,使用单列模式
|
||||
private static final ExecutorService THREAD_POOL = new ThreadPoolExecutor( |
||||
10, // 核心线程数
|
||||
50, // 最大线程数
|
||||
60L, TimeUnit.SECONDS, // 空闲线程存活时间
|
||||
new LinkedBlockingQueue<Runnable>(100), // 任务队列
|
||||
Executors.defaultThreadFactory(), // 线程工厂
|
||||
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
|
||||
); |
||||
|
||||
// 私有化构造方法,防止外部实例化
|
||||
private GlobalThreadPool() {} |
||||
|
||||
// 获取全局线程池实列
|
||||
public static ExecutorService getInstance() { |
||||
return THREAD_POOL; |
||||
} |
||||
|
||||
// 关闭线程池
|
||||
public static void shutdown() { |
||||
THREAD_POOL.shutdown(); |
||||
} |
||||
|
||||
// 在应用关闭前执行
|
||||
@PreDestroy |
||||
public void destroy() { |
||||
log.info("Spring 应用关闭,正在关闭线程池..."); |
||||
shutdown(); |
||||
try { |
||||
if (!THREAD_POOL.awaitTermination(60, TimeUnit.SECONDS)) { |
||||
THREAD_POOL.shutdownNow(); |
||||
} |
||||
} catch (InterruptedException e) { |
||||
THREAD_POOL.shutdownNow(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue