package com.ruoyi.datacollection.service.impl;
import com.ruoyi.common.core.service.HisGatherApi;
import com.ruoyi.common.core.service.dto.SltdGatherReq;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.datacollection.service.IHisDataCollectionService;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* HIS 数据采集调度编排实现 ——
* 由 {@code ruoyi-quartz} 的 {@code RyTask#dealHisData} + {@code collectSegmentData} 原样搬入。
*
*
搬运纪律(本类刻意"零改动")
*
* - 站点分支({@code xh} / {@code hn} / {@code sltd}-{@code nhfy})、时间窗口算法、
* 30 天分段回溯的段长与顺序、每个采集动作的先后、异常吞掉的范围、日志文案
* —— 全部与搬运前一致;
* - 唯一的形式变化:内部 VO({@code ServiceSLTDInhospReqVO} / {@code ServiceSLTDDeptReqVO} /
* {@code HnDataGatherVO})换成契约 DTO {@link SltdGatherReq} / 两个 {@code Date},
* 由 smartor 侧 Bridge 逐字段搬回原 VO;字段设置顺序与"显式置 null"都保留;
*
- {@code CollectionUtils.isNotEmpty(...)} 换成等价的 {@code list != null && !list.isEmpty()}
* (避免为一次判空给模块引 commons-collections4);
* - {@code sys_config} 的读法刻意保持 {@code selectConfigList(条件)} + 取第 0 行
* (含原实现的 LIKE 匹配与"不排序取首行"语义),没有改成 {@code selectConfigByKey}
* —— 那是"顺手优化",会改变查询语义,回归时无法逐条比对。
*
*
* 它不管什么
* 真正的采集动作(HTTP 取数、入库)不在本类:那里仍在 smartor,经
* {@link HisGatherApi} 契约调用,后续分批搬进本模块。
*
* @see com.ruoyi.datacollection.api.DataCollectionFacade 对外门面
*/
@Slf4j
@Service
public class HisDataCollectionServiceImpl implements IHisDataCollectionService {
/**
* 省立同德 / 南华附一的机构ID(原实现里的字面量 {@code "20001001"})
*/
private static final String SLTD_ORG_ID = "20001001";
/**
* 当时站点标识(原 {@code RyTask} 的 {@code @Value("${spring.profiles.active}")})
*/
@Value("${spring.profiles.active}")
private String active;
/**
* HIS 采集能力契约(当前实现在 smartor:纯转发到既有 Service)。
*
* ⚠️ 刻意直接注入而不是 {@code ObjectProvider} 惰性取:
* 采集实现是数据链路的必需件,"取不到就静默不采"正是最危险的失败形态,
* 这里让它启动期就暴露(宁可启动失败,也不要静默少数据)。
*/
@Autowired
private HisGatherApi hisGatherApi;
/**
* 参数配置服务(ruoyi-system 的 API):读 {@code sltd.gather.sysuser} / {@code sltd.gather.campusid}
*/
@Autowired
private ISysConfigService configService;
/**
* HIS数据采集
* 定时任务:采集患者信息、出院、门诊数据
*
* @param todayZeroStr 当前的时间 例如:2026-05-06 00:00:00
*/
@Override
public void dealHisData(String todayZeroStr) {
log.info("【dealHisData】开始执行HIS数据采集任务");
// 获取昨天0点到今天0点的时间范围
LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN);
if (StringUtils.isNotBlank(todayZeroStr)) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
todayZero = LocalDateTime.parse(todayZeroStr, formatter);
}
LocalDateTime yesterdayZero = todayZero.minusDays(1);
log.info("【dealHisData】采集时间范围:{} ~ {}", yesterdayZero, todayZero);
if (active.trim().equals("xh")) {
try {
// 采集入院数据
log.info("【dealHisData】新华开始采集入院数据");
hisGatherApi.gatherXhInhospRecords("0", yesterdayZero, todayZero);
log.info("【dealHisData】新华入院数据采集完成");
// 采集出院数据
log.info("【dealHisData】新华开始采集出院数据");
hisGatherApi.gatherXhInhospRecords("1", yesterdayZero, todayZero);
log.info("【dealHisData】新华出院数据采集完成");
} catch (Exception e) {
log.error("【dealHisData】新华数据采集异常", e);
}
} else if (active.trim().equals("hn")) {
try {
// 河南数据采集
log.info("【dealHisData】河南开始采集数据");
Date startTime = Date.from(yesterdayZero.atZone(ZoneId.systemDefault()).toInstant());
Date endTime = Date.from(todayZero.atZone(ZoneId.systemDefault()).toInstant());
hisGatherApi.gatherHnRecords(startTime, endTime);
log.info("【dealHisData】河南结束采集数据");
} catch (Exception e) {
log.error("【dealHisData】河南数据采集异常", e);
}
} else if (active.trim().equals("sltd") || active.trim().equals("nhfy")) {
//省立同德与华南附一用同一套采集方法(都是来未来提供)
try {
//获取是否需要采集用户、部门信息
SysConfig config = new SysConfig();
config.setConfigKey("sltd.gather.sysuser");
config.setOrgid(SLTD_ORG_ID);
List sysConfigs = configService.selectConfigList(config);
//获取省立同德要采集的院区
SysConfig cf = new SysConfig();
cf.setConfigKey("sltd.gather.campusid");
cf.setOrgid(SLTD_ORG_ID);
List sysConfigcampusids = configService.selectConfigList(cf);
List campusidList = new ArrayList<>();
if (sysConfigcampusids != null && !sysConfigcampusids.isEmpty()) {
String[] split = sysConfigcampusids.get(0).getConfigValue().split(",");
campusidList = Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList());
} else {
log.error("SysConfig中的院区ID为空!");
return;
}
//configValue = 1 不需要采集 configValue = 2需要采集
if (sysConfigs != null && !sysConfigs.isEmpty() && sysConfigs.get(0).getConfigValue().equals("2")) {
log.info("【dealHisData】省立同德“部门信息”开始采集数据");
SltdGatherReq serviceSLTDDeptReqVO = new SltdGatherReq();
serviceSLTDDeptReqVO.setOrgId(SLTD_ORG_ID);
serviceSLTDDeptReqVO.setCampusIds(campusidList);
Boolean aBoolean1 = hisGatherApi.gatherSltdDeptWardArea(serviceSLTDDeptReqVO);
log.info("【dealHisData】省立同德“部门信息”结束采集数据:{}", aBoolean1);
Boolean aBoolean = false;
for (Long cid : campusidList) {
log.info("【dealHisData】省立同德“用户信息”开始采集数据,院区:{}", cid);
SltdGatherReq reqVO = new SltdGatherReq();
reqVO.setOrgId(SLTD_ORG_ID);
reqVO.setCampusId(cid);
ArrayList statusList = new ArrayList<>();
statusList.add("1");
reqVO.setStatusList(statusList);
aBoolean = hisGatherApi.gatherSltdHospUser(reqVO);
}
log.info("【dealHisData】省立同德“用户信息”结束采集数据:{}", aBoolean);
//疾病字典采集
hisGatherApi.gatherIcd10();
}
// nhfy/sltd回溯30天分段采集判断(每段最多4天)
boolean isBackfill = (active.trim().equals("nhfy") || active.trim().equals("sltd")) && StringUtils.isNotBlank(todayZeroStr);
if (isBackfill) {
DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate segStart = todayZero.toLocalDate().minusDays(30);
LocalDate segEndDate = todayZero.toLocalDate();
int segCount = 0;
while (!segStart.isAfter(segEndDate)) {
LocalDate segEnd = segStart.plusDays(3);
if (segEnd.isAfter(segEndDate)) {
segEnd = segEndDate;
}
segCount++;
log.info("【dealHisData】{}回溯第{}段采集,日期范围:{} ~ {}", active.trim(), segCount, segStart.format(dateFmt), segEnd.format(dateFmt));
for (Long cid : campusidList) {
collectSegmentData(cid, segStart.format(dateFmt), segEnd.format(dateFmt));
}
segStart = segEnd.plusDays(1);
}
log.info("【dealHisData】{}回溯采集完成,共{}段", active.trim(), segCount);
}
// 省立同德数据采集(非回溯模式才执行)
if (!isBackfill) {
for (Long cid : campusidList) {
log.info("【dealHisData】省立同德预入院开始采集数据,院区:{}", cid);
String nowTime = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
SltdGatherReq serviceSLTDInhospReqVO = new SltdGatherReq();
serviceSLTDInhospReqVO.setOrgId(SLTD_ORG_ID);
serviceSLTDInhospReqVO.setCampusId(cid);
serviceSLTDInhospReqVO.setEncounterTimeStart(nowTime);
serviceSLTDInhospReqVO.setEncounterTimeEnd(nowTime);
if (active.trim().equals("nhfy")) {
serviceSLTDInhospReqVO.setEncounterTimeStart(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
serviceSLTDInhospReqVO.setEncounterTimeEnd(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
List list = new ArrayList<>();
list.add("FH0109.22");
list.add("FH0109.23");
list.add("FH0109.53");
serviceSLTDInhospReqVO.setStatusList(list);
// log.info("【dealHisData】省立同德“预入院”采集数据入参:{}", serviceSLTDInhospReqVO);
hisGatherApi.gatherSltdHealthcareRecords(serviceSLTDInhospReqVO);
log.info("【dealHisData】省立同德“预入院”結束采集数据,院区:{}", cid);
log.info("【dealHisData】省立同德“入院”开始采集数据,院区:{}", cid);
serviceSLTDInhospReqVO.setOrgId(SLTD_ORG_ID);
serviceSLTDInhospReqVO.setCampusId(cid);
serviceSLTDInhospReqVO.setEncounterTimeStart(null);
serviceSLTDInhospReqVO.setEncounterTimeEnd(null);
serviceSLTDInhospReqVO.setStartHeadTime(nowTime);
serviceSLTDInhospReqVO.setStartTailTime(nowTime);
if (active.trim().equals("nhfy")) {
serviceSLTDInhospReqVO.setStartHeadTime(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
serviceSLTDInhospReqVO.setStartTailTime(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
List listStr = new ArrayList<>();
listStr.add("FH0109.24");
serviceSLTDInhospReqVO.setStatusList(listStr);
// log.info("【dealHisData】省立同德“入院”采集数据入参:{}", serviceSLTDInhospReqVO);
hisGatherApi.gatherSltdHealthcareRecords(serviceSLTDInhospReqVO);
log.info("【dealHisData】省立同德“入院”結束采集数据,院区:{}", cid);
log.info("【dealHisData】省立同德“出院”开始采集数据,院区:{}", cid);
serviceSLTDInhospReqVO.setStartHeadTime(null);
serviceSLTDInhospReqVO.setStartTailTime(null);
serviceSLTDInhospReqVO.setEncounterTimeStart(null);
serviceSLTDInhospReqVO.setEncounterTimeEnd(null);
serviceSLTDInhospReqVO.setPreOutHospitalHeadDate(nowTime);
serviceSLTDInhospReqVO.setPreOutHospitalTailDate(nowTime);
if (active.trim().equals("nhfy")) {
serviceSLTDInhospReqVO.setPreOutHospitalHeadDate(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
serviceSLTDInhospReqVO.setPreOutHospitalTailDate(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
serviceSLTDInhospReqVO.setHealthcareRecordTypeList(null);
List list2 = new ArrayList<>();
// list2.add("FH0108.02");
//出院
list2.add("FH0109.27");
//预出院
list2.add("FH0109.25");
serviceSLTDInhospReqVO.setStatusList(list2);
// log.info("【dealHisData】省立同德“出院”采集数据入参:{}", serviceSLTDInhospReqVO);
hisGatherApi.gatherSltdHealthcareRecords(serviceSLTDInhospReqVO);
log.info("【dealHisData】省立同德“出院”束采集数据,院区:{}", cid);
}
}
} catch (Exception e) {
log.error("【dealHisData】省立同德数据采集异常", e);
}
}
}
/**
* 分段采集数据(预入院、入院、出院)
* 用于nhfy/sltd回溯采集,每次最多查询7天数据
*
* @param cid 院区ID
* @param startDateStr 日期开始 yyyy-MM-dd
* @param endDateStr 日期结束 yyyy-MM-dd
*/
private void collectSegmentData(Long cid, String startDateStr, String endDateStr) {
// // 预入院
log.info("【dealHisData】预入院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr);
SltdGatherReq reqVO = new SltdGatherReq();
reqVO.setOrgId(SLTD_ORG_ID);
reqVO.setCampusId(cid);
reqVO.setEncounterTimeStart(startDateStr);
reqVO.setEncounterTimeEnd(endDateStr);
List preAdmitStatus = new ArrayList<>();
preAdmitStatus.add("FH0109.22");
preAdmitStatus.add("FH0109.23");
preAdmitStatus.add("FH0109.53");
reqVO.setStatusList(preAdmitStatus);
log.info("【dealHisData】预入院采集数据入参:{}", reqVO);
try {
hisGatherApi.gatherSltdHealthcareRecords(reqVO);
} catch (Exception e) {
// 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行
log.error("【dealHisData】预入院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e);
}
log.info("【dealHisData】预入院終了采集数据,院区:{}", cid);
// 入院
log.info("【dealHisData】入院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr);
reqVO = new SltdGatherReq();
reqVO.setOrgId(SLTD_ORG_ID);
reqVO.setCampusId(cid);
reqVO.setStartHeadTime(startDateStr);
reqVO.setStartTailTime(endDateStr);
List admitStatus = new ArrayList<>();
admitStatus.add("FH0109.24");
reqVO.setStatusList(admitStatus);
log.info("【dealHisData】入院采集数据入参:{}", reqVO);
try {
hisGatherApi.gatherSltdHealthcareRecords(reqVO);
} catch (Exception e) {
// 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行
log.error("【dealHisData】入院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e);
}
log.info("【dealHisData】入院終了采集数据,院区:{}", cid);
// 出院
log.info("【dealHisData】出院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr);
reqVO = new SltdGatherReq();
reqVO.setOrgId(SLTD_ORG_ID);
reqVO.setCampusId(cid);
reqVO.setPreOutHospitalHeadDate(startDateStr);
reqVO.setPreOutHospitalTailDate(endDateStr);
List dischargeStatus = new ArrayList<>();
//出院
dischargeStatus.add("FH0109.27");
//预出院
dischargeStatus.add("FH0109.25");
reqVO.setStatusList(dischargeStatus);
log.info("【dealHisData】出院采集数据入参:{}", reqVO);
try {
hisGatherApi.gatherSltdHealthcareRecords(reqVO);
} catch (Exception e) {
// 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行
log.error("【dealHisData】出院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e);
}
log.info("【dealHisData】出院结束采集数据,院区:{}", cid);
}
}