1. 背景
最近在使用RPA(机器人流程自动化)做数据采集的时候。发现那个RPA采集,一次只能采集相同格式的数据,然后入到Excel或者库中。由于院内系统的业务限制,导致采集的数据是多个Excel,并且我们这边的需求是采集到一个Excel中,然后入库,作为院方新系统的数据来源。【别问为啥不直接入数据库,问就是条件不支持】。
ps:你想想,一个人的详情,里面是折叠框的形式,每个折叠框下面的表格的格式还不一致。真就恶心坏了。
所以,我们只能采用多个Excel的形式,然后使用代码做Excel合并后入库,最后再做数据清洗。【其实,那个RPA应该还支持Excel处理,RPA我也是刚研究2天,了解了个大概,不过因为时间比较着急,只把数据采集研究了一下。Excel处理还没来得及研究,就被催进度了。就只能先把采集流程配置完毕】
ps:RPA采集用的是实在智能RPA,有空的话,分享一下使用体验
2. 多个Excel的数据处理方案
2.1 方案1【通过反射处理字段映射】
2.1.1 RPA数据采集Excel效果图
以糖尿病档案为例:数据采集后的结果是四张Excel,表示一个人的详情信息【基本信息与其他三个Excel的格式还不一样,就很恶心】
基本信息:
降糖药物治疗情况:
目前并发症_合并症情况:
胰岛素治疗情况:
2.1.2 合并Excel进行数据处理
描述:本服务使用Hutool和MyBatis-Plus等库来读取糖尿病患者的Excel档案文件,解析其中的信息,并将解析后的数据存储到数据库中。此外,处理完的文件会被移动到备份目录。
2.1.2.1 实体信息
注意Excel对应:
//对应的是:目前并发症_合并症情况.xls
@ApiModelProperty(value = "合并症情况")
private String comorbidityStatus;
//对应的是:胰岛素治疗情况.xls
@ApiModelProperty(value = "胰岛素治疗情况")
private String insulinTreatmentStatus;//对应的是:降糖药物治疗情况.xls
@ApiModelProperty(value = "降糖药物治疗情况")
private String hypoglycemicDrugTreatmentStatus;
package com.chinaunicom.medical.business.cdm.dao.cdm.entity;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OdsDiabetesFileInfo {
@ApiModelProperty(value = "主食类(克/天)")
private String stapleFoodPerDay;
@ApiModelProperty(value = "出生日期")
private String birthDate;
@ApiModelProperty(value = "医疗机构类型")
private String medicalInstitutionType;
@ApiModelProperty(value = "吸烟情况")
private String smokingStatus;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "尿白蛋白(mg/24h)")
private String urineAlbumin;
@ApiModelProperty(value = "常住类型")
private String residenceType;
@ApiModelProperty(value = "建档日期")
private String fileEstablishmentDate;
@ApiModelProperty(value = "建档时空腹血糖(mmol/L)")
private String fastingBloodGlucoseAtEstablishment;
@ApiModelProperty(value = "建档时糖化血红蛋白(mmol/L)")
private String glycosylatedHemoglobinAtEstablishment;
@ApiModelProperty(value = "录入日期")
private String entryDate;
@ApiModelProperty(value = "心理调整")
private String psychologicalAdjustment;
@ApiModelProperty(value = "心电图")
private String electrocardiogram;
@ApiModelProperty(value = "既往餐后2小时血糖(%)")
private String previousPostprandialBloodGlucose;
@ApiModelProperty(value = "本人电话")
private String personalPhoneNumber;
@ApiModelProperty(value = "每周运动次数(次)")
private String weeklyExerciseTimes;
@ApiModelProperty(value = "水果类(克/天)")
private String fruitPerDay;
@ApiModelProperty(value = "治疗措施")
private String treatmentMeasures;
@ApiModelProperty(value = "甘油三酯(mmol/L)")
private String triglycerides;
@ApiModelProperty(value = "用户编号")
private String userId;
@ApiModelProperty(value = "盐摄入量")
private String saltIntake;
@ApiModelProperty(value = "确诊时并发症")
private String complicationsAtDiagnosis;
@ApiModelProperty(value = "确诊时间")
private String diagnosisTime;
@ApiModelProperty(value = "神经病变检查")
private String neuropathyExamination;
@ApiModelProperty(value = "禽鱼肉蛋类(克/天)")
private String meatEggFishPoultryPerDay;
@ApiModelProperty(value = "管理结果")
private String managementResult;
@ApiModelProperty(value = "胰岛素治疗")
private String insulinTreatment;
@ApiModelProperty(value = "腰围(cm)")
private String waistCircumference;
@ApiModelProperty(value = "舒张压(mmHg)")
private String diastolicBloodPressure;
@ApiModelProperty(value = "足背动脉搏动")
private String dorsalisPedisPulse;
@ApiModelProperty(value = "身高(cm)")
private String height;
@ApiModelProperty(value = "运动情况")
private String exerciseStatus;
@ApiModelProperty(value = "降糖药物治疗")
private String hypoglycemicDrugTreatment;
@ApiModelProperty(value = "食用油(克/天)")
private String edibleOilPerDay;
@ApiModelProperty(value = "餐后血糖(mmol/L)")
private String postprandialBloodGlucose;
@ApiModelProperty(value = "饮酒情况")
private String alcoholConsumptionStatus;
@ApiModelProperty(value = "饮酒量(两/天)")
private String alcoholIntakePerDay;
@ApiModelProperty(value = "高密度脂蛋白胆固醇(mmol/L)")
private String highDensityLipoproteinCholesterol;
//对应的是:目前并发症_合并症情况.xls
@ApiModelProperty(value = "合并症情况")
private String comorbidityStatus;
//对应的是:胰岛素治疗情况.xls
@ApiModelProperty(value = "胰岛素治疗情况")
private String insulinTreatmentStatus;
//对应的是:降糖药物治疗情况.xls
@ApiModelProperty(value = "降糖药物治疗情况")
private String hypoglycemicDrugTreatmentStatus;
@ApiModelProperty(value = "同步状态 0:未同步 1:同步")
private Integer syncStatus;
}
2.1.2.2 mapper
package com.chinaunicom.medical.business.cdm.dao.cdm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chinaunicom.medical.business.cdm.dao.cdm.entity.OdsDiabetesFileInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
@Mapper
public interface OdsDiabetesFileInfoMapper extends BaseMapper<OdsDiabetesFileInfo> {
}
2.1.2.3 业务类
2.1.2.3.1 总体实现
package com.chinaunicom.medical.business.cdm.analysis.excel.diabetes;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chinaunicom.medical.business.cdm.dao.cdm.entity.OdsDiabetesFileInfo;
import com.chinaunicom.medical.business.cdm.dao.cdm.mapper.OdsDiabetesFileInfoMapper;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
public class OdsDiabetesFileInfoAnalysis extends ServiceImpl<OdsDiabetesFileInfoMapper, OdsDiabetesFileInfo> {
private static final String baseInfoFileSuffix = "_基本信息.xls";
private static final String hypoglycemicDrugsFileSuffix = "_降糖药物治疗情况.xls";
private static final String complicationFileSuffix = "_目前并发症_合并症情况.xls";
private static final String insulinTreatmentFileSuffix = "胰岛素治疗情况.xls";
// @PostConstruct
// public void run() throws IOException {
// analysis("/数据采集-详情样例/糖尿病档案", "./");
// }
public void analysis(String fileDirPath, String bakDirPath){
List<String> fileNameList = FileUtil.listFileNames(fileDirPath).stream().sorted().collect(Collectors.toList());
if (CollUtil.isEmpty(fileNameList)) {
return;
}
List<String> handledList = new ArrayList<>();
Map<String, Map<String, String>> dataMap = new ConcurrentHashMap<>();
fileNameList.forEach(fileName -> {
String patientNo = fileName.replace(baseInfoFileSuffix, "")
.replace(hypoglycemicDrugsFileSuffix, "")
.replace(complicationFileSuffix, "")
.replace(insulinTreatmentFileSuffix, "");
if (!dataMap.containsKey(patientNo)) {
dataMap.put(patientNo, new TreeMap<>());
}
Map<String, String> patientData = dataMap.get(patientNo);
patientData.put("用户编号", patientNo);
patientData.putAll(getPatientData(fileDirPath, fileName));
handledList.add(fileName);
});
log.info("解析成功{}", JSONUtil.toJsonStr(dataMap));
if (dataMap.size() > 0) {
Class clazz = OdsDiabetesFileInfo.class;
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
Map<String, Method> methodMap = Arrays.stream(methods).collect(Collectors.toMap(method -> method.getName().toLowerCase(), Function.identity()));
//
Map<String, Method> filedAndSetMethodMap = new HashMap<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(ApiModelProperty.class)) {
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
filedAndSetMethodMap.put(apiModelProperty.value(), methodMap.get("set" + field.getName().toLowerCase()));
}
}
List<OdsDiabetesFileInfo> dataList = new ArrayList<>();
for (Map.Entry<String, Map<String, String>> entry : dataMap.entrySet()) {
Map<String, String> patientData = entry.getValue();
OdsDiabetesFileInfo excelData = new OdsDiabetesFileInfo();
for (Map.Entry<String, String> patientDataEntry : patientData.entrySet()) {
Method method = filedAndSetMethodMap.get(patientDataEntry.getKey());
if (Objects.nonNull(method)) {
try {
method.invoke(excelData, patientDataEntry.getValue());
} catch (Exception e) {
log.error(ExceptionUtils.getMessage(e));
}
}
}
this.getBaseMapper().insert(excelData);
dataList.add(excelData);
}
}
moveToBakDir(handledList, fileDirPath, bakDirPath);
}
private Map<String, String> getPatientData(String fileDirPath, String fileName) {
if (fileName.contains(baseInfoFileSuffix)) {
return getPatientBaseInfoData(fileDirPath, fileName);
}
Map<String, String> result = new TreeMap<>();
if (fileName.contains(hypoglycemicDrugsFileSuffix)) {
List<Map<String, String>> data = getPatientListData(fileDirPath, fileName);
result.put("降糖药物治疗情况", JSONUtil.toJsonStr(data));
return result;
}
if (fileName.contains(complicationFileSuffix)) {
List<Map<String, String>> data = getPatientListData(fileDirPath, fileName);
result.put("合并症情况", JSONUtil.toJsonStr(data));
return result;
}
if (fileName.contains(insulinTreatmentFileSuffix)) {
List<Map<String, String>> data = getPatientListData(fileDirPath, fileName);
result.put("胰岛素治疗情况", JSONUtil.toJsonStr(data));
return result;
}
return Collections.emptyMap();
}
private List<Map<String, String>> getPatientListData(String fileDirPath, String fileName) {
ExcelReader reader;
List<Map<String, String>> dataList = new ArrayList<>();
try {
reader = ExcelUtil.getReader(FileUtil.getInputStream(fileDirPath + File.separator + fileName), true);
int lastRowNum = reader.getSheet().getLastRowNum();
Map<Integer, String> titleMap = new HashMap<>();
Row titleRow = reader.getSheet().getRow(1);
short titleRowLastCellNum = titleRow.getLastCellNum();
for (int i = 0; i < titleRowLastCellNum; i++) {
titleMap.put(i, titleRow.getCell(i).getStringCellValue());
}
for (int i = 2; i <= lastRowNum; i++) {
Map<String, String> data = new HashMap<>();
Row row = reader.getSheet().getRow(i);
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
data.put(titleMap.get(j), row.getCell(j).getStringCellValue());
}
dataList.add(data);
}
return dataList;
} catch (Exception e) {
log.error("解析信息异常,当前文件{}", fileDirPath + File.separator + fileName, e);
}
return dataList;
}
private Map<String, String> getPatientBaseInfoData(String fileDirPath, String fileName) {
ExcelReader reader;
Map<String, String> data = new TreeMap<>();
try {
reader = ExcelUtil.getReader(FileUtil.getInputStream(fileDirPath + File.separator + fileName), true);
int lastRowNum = reader.getSheet().getLastRowNum();
for (int i = 1; i <= lastRowNum; i++) {
Row row = reader.getSheet().getRow(i);
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j += 2) {
if ((j & 2) == 0) {
data.put(row.getCell(j).getStringCellValue().replace(":", "").replace("*", ""),
row.getCell(j + 1).getStringCellValue());
}
}
}
return data;
} catch (Exception e) {
log.error("解析基本信息异常,当前文件{}", fileDirPath + File.separator + fileName, e);
}
return data;
}
private void moveToBakDir(List<String> handledList, String fileDirPath, String bakDirPath) {
File bakDirFile = new File(bakDirPath);
if(!bakDirFile.exists()){
bakDirFile.mkdirs();
}
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
for (String fileName : handledList) {
FileUtil.move(new File(fileDirPath + File.separator + fileName), new File(bakDirPath + File.separator + dateStr + "-" + fileName), true);
}
}
}
2.1.2.3.2 代码解析
技术栈
- 编程语言:Java
- 框架:Spring Boot, MyBatis Plus
- 工具库:Hutool, Apache POI
类结构
- 类名:
OdsDiabetesFileInfoAnalysis
- 继承:
ServiceImpl<OdsDiabetesFileInfoMapper, OdsDiabetesFileInfo>
- 注解:
@Slf4j
:用于日志记录@Service
:标识这是一个Spring管理的服务组件主要功能
- 文件读取与解析
- 方法:
analysis(String fileDirPath, String bakDirPath)
- 功能:从指定路径读取所有相关文件,解析每个文件中的患者信息,并将其存储在内存映射中。
- 参数:
fileDirPath
:包含待处理文件的目录路径bakDirPath
:处理完成后文件的备份目录路径- 数据映射与存储
- 方法:内部逻辑处理
- 功能:将解析出的数据映射到
OdsDiabetesFileInfo
对象,并调用数据库操作方法保存至数据库。- 文件备份
- 方法:
moveToBakDir(List<String> handledList, String fileDirPath, String bakDirPath)
- 功能:将处理过的文件移动到备份目录,文件名后附加时间戳以区分不同批次的处理结果。
关键方法详解
1.
analysis(String fileDirPath, String bakDirPath)
- 流程:
- 从指定目录fileDirPath读取所有文件名,并按名称排序。
- 对每个文件进行处理,提取患者编号,并根据文件类型调用不同的解析方法。
- 将解析得到的数据存入内存映射中。
- 将内存映射中的数据转换为
OdsDiabetesFileInfo
对象,并插入数据库。- 移动已处理的文件到备份目录。
2.
getPatientData(String fileDirPath, String fileName)
- 功能:根据文件类型调用相应的解析方法,返回解析后的数据。
3.
getPatientBaseInfoData(String fileDirPath, String fileName)
- 功能:解析患者的基本信息文件,返回一个包含基本信息的映射表。
4.
getPatientListData(String fileDirPath, String fileName)
- 功能:解析患者列表数据文件,返回一个包含多条记录的列表,每条记录是一个映射表。
5.
moveToBakDir(List<String> handledList, String fileDirPath, String bakDirPath)
- 功能:创建备份目录(如果不存在),并将处理过的文件移动到此目录,文件名后附加时间戳。
异常处理
- 错误日志:使用
log.error()
记录解析过程中的任何异常信息,便于后续排查问题。配置项
- 文件后缀定义:
baseInfoFileSuffix
:基本信息文件后缀hypoglycemicDrugsFileSuffix
:降糖药物治疗情况文件后缀complicationFileSuffix
:并发症情况文件后缀insulinTreatmentFileSuffix
:胰岛素治疗情况文件后缀使用说明
- 启动方式:可以通过调用
analysis
方法来启动服务,通常是在Spring容器初始化后自动执行。- 输入输出:输入为两个字符串参数,分别指明待处理文件的目录和处理后文件的备份目录;输出为处理完成的日志信息。
注意事项
- 数据一致性:确保数据库操作的事务性,避免数据丢失或损坏。
- 性能考虑:对于大量文件的处理,可能需要考虑异步处理或分批处理,以提高效率。
- 安全性:处理敏感信息时,应注意保护患者隐私,遵守相关法律法规。
2.1.2.3.3 总结
其实这段代码的核心就是,如何与Excel的列一一对应:
- Excel列名与字段名的映射:
- 在getPatientData方法中,解析Excel文件时会将列名和列值存储在一个映射表中。例如,getPatientBaseInfoData方法会将基本信息文件中的列名和列值存储在data映射表中。
- 这些列名(如用户编号)将作为键存储在patientData映射表中。
- 字段与设置方法的映射:
- filedAndSetMethodMap中存储了字段名(通过ApiModelProperty注解的值)与设置方法的映射关系。
- 例如,ApiModelProperty(value = "用户编号")注解的字段会映射到setUserNo方法。
- 数据填充:
- 在处理每个患者的数据时,通过patientDataEntry.getKey()获取Excel列名(如用户编号),然后在filedAndSetMethodMap中查找对应的设置方法(如setUserNo)。
- 通过method.invoke(excelData, patientDataEntry.getValue())调用设置方法,将Excel列值设置到OdsDiabetesFileInfo对象的相应字段中。
- 通过这种方式,Excel中的列名与Java对象的字段名建立了映射关系,从而实现了数据的自动填充和持久化。
代码详解
1. 检查数据映射是否为空if (dataMap.size() > 0) {
作用:首先检查dataMap是否包含数据。如果dataMap为空,则直接退出方法,不做任何处理。
2. 获取OdsDiabetesFileInfo类的字段和方法Class clazz = OdsDiabetesFileInfo.class; Field[] fields = clazz.getDeclaredFields(); Method[] methods = clazz.getDeclaredMethods();
作用:获取OdsDiabetesFileInfo类的所有字段和方法。这些字段和方法将用于后续的数据映射。
3. 创建方法映射Map<String, Method> methodMap = Arrays.stream(methods).collect(Collectors.toMap(method -> method.getName().toLowerCase(), Function.identity()));
作用:将所有方法的名字(转换为小写)和方法对象存储在一个映射表中。这样可以通过方法名快速查找方法对象。
4. 创建字段与设置方法的映射
Map<String, Method> filedAndSetMethodMap = new HashMap<>(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; if (field.isAnnotationPresent(ApiModelProperty.class)) { ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class); filedAndSetMethodMap.put(apiModelProperty.value(), methodMap.get("set" + field.getName().toLowerCase())); } }
作用:遍历OdsDiabetesFileInfo类的所有字段,检查字段是否带有ApiModelProperty注解。如果有,则将注解的值(通常是字段的描述或标签)作为键,对应的设置方法(setter方法)作为值,存储在filedAndSetMethodMap中。
关键点:
ApiModelProperty注解通常用于描述API文档中的字段,这里用作字段的标识符。
methodMap.get("set" + field.getName().toLowerCase()):通过字段名找到对应的设置方法(如setUserNo)。
5. 处理每个患者的数据List<OdsDiabetesFileInfo> dataList = new ArrayList<>(); for (Map.Entry<String, Map<String, String>> entry : dataMap.entrySet()) { Map<String, String> patientData = entry.getValue(); OdsDiabetesFileInfo excelData = new OdsDiabetesFileInfo(); for (Map.Entry<String, String> patientDataEntry : patientData.entrySet()) { Method method = filedAndSetMethodMap.get(patientDataEntry.getKey()); if (Objects.nonNull(method)) { try { method.invoke(excelData, patientDataEntry.getValue()); } catch (Exception e) { log.error(ExceptionUtils.getMessage(e)); } } } this.getBaseMapper().insert(excelData); dataList.add(excelData); }
作用:遍历dataMap中的每个患者数据,将每个患者的数据映射到一个新的OdsDiabetesFileInfo对象,并插入数据库。
详细步骤:
- 遍历患者数据:for (Map.Entry<String, Map<String, String>> entry : dataMap.entrySet()),遍历每个患者的映射数据。
- 创建对象:OdsDiabetesFileInfo excelData = new OdsDiabetesFileInfo();,为每个患者创建一个新的OdsDiabetesFileInfo对象。
- 映射字段:for (Map.Entry<String, String> patientDataEntry : patientData.entrySet()),遍历患者数据中的每个字段。
- 查找设置方法:Method method = filedAndSetMethodMap.get(patientDataEntry.getKey());,通过字段名(如用户编号)找到对应的设置方法(如setUserNo)。
- 调用设置方法:method.invoke(excelData, patientDataEntry.getValue());,调用设置方法将字段值设置到OdsDiabetesFileInfo对象中。
- 插入数据库:this.getBaseMapper().insert(excelData);,将填充好的OdsDiabetesFileInfo对象插入数据库。
- 添加到列表:dataList.add(excelData);,将插入成功的对象添加到列表中,以便后续处理。
2.2 方案二【一个个的set】
2.2.1 RPA数据采集Excel效果图
以体检记录为例:数据采集后的结果是3张Excel,表示一个人的详情信息【Excel的格式还不一样,就很恶心】
基本信息:
一般状况:
用药详情:
2.2.2 合并Excel进行数据处理
描述:该函数的主要功能是解析指定目录下的多个Excel文件,并将解析结果保存到数据库中。具体步骤如下:
- 参数校验:检查输入的文件目录是否存在,如果不存在则直接返回。
- 定义文件后缀:定义了多个特定的文件后缀,用于识别不同类型的Excel文件。
- 获取文件列表:读取指定目录下的所有文件名。
- 解析文件:
- 遍历文件列表,找到包含特定后缀的文件。
- 创建一个 OdsMedicalExaminationDetailExcelData 对象,用于存储解析结果。
- 使用多线程分别解析不同类型的Excel文件(如基本信息、查体信息等)。
- 等待任务完成:使用 CountDownLatch 等待所有解析任务完成。
- 保存结果:将解析结果批量保存到数据库中。
- 移动文件:将解析成功的文件移动到成功目录,解析失败的文件移动到失败目录。
2.2.2.1 实体信息
体检记录常规格式实体:
package com.chinaunicom.medical.business.cdm.dao.cdm.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* @Description
* @Author ZhaoShuhao
* @Date: 2024-11-01 09:58:05
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@Schema(name=" ods_medical_examination_detail_excel_data ", description=" 原始健康体检记录Excel详情表")
@TableName(value = "ods_medical_examination_detail_excel_data",autoResultMap = true)
public class OdsMedicalExaminationDetailExcelData {
@Schema(name="name",description="姓名:")
private String name;
@Schema(name="idCard",description="身份证号:")
private String idCard;
@Schema(name="number",description="档案编号:")
private String number;
@Schema(name="medicalExaminationDate",description="*体检日期:")
private String medicalExaminationDate;
@Schema(name="medicalExaminationDoctor",description="*体检医生:")
private String medicalExaminationDoctor;
@Schema(name="medicalExaminationOrg",description="体检机构")
private String medicalExaminationOrg;
@Schema(name="phone",description="联系电话")
private String phone;
@Schema(name="responsibleDoctor",description="责任医生:")
private String responsibleDoctor;
@Schema(name="fundus",description="眼底")
private String fundus;
@Schema(name="fundusInfo",description="眼底异常描述")
private String fundusInfo;
@Schema(name="skin",description="皮肤")
private String skin;
@Schema(name="skinInfo",description="皮肤其他")
private String skinInfo;
@Schema(name="sclera",description="巩膜")
private String sclera;
@Schema(name="scleraInfo",description="巩膜其他")
private String scleraInfo;
@Schema(name="lymphNodeType",description="淋巴结检查结果类别")
private String lymphNodeType;
@Schema(name="lymphNodeInfo",description="淋巴结其他")
private String lymphNodeInfo;
@Schema(name="lungs",description="肺")
private String lungs;
@Schema(name="barrelChest",description="桶状胸")
private String barrelChest;
@Schema(name="breathing",description="呼吸音")
private String breathing;
@Schema(name="breathingInfo",description="呼吸音异常")
private String breathingInfo;
@Schema(name="rale",description="罗音")
private String rale;
@Schema(name="raleInfo",description="罗音其他")
private String raleInfo;
@Schema(name="heart",description="心脏")
private String heart;
@Schema(name="heartRate",description="心率 次/分钟")
private String heartRate;
@Schema(name="heartType",description="心律类别")
private String heartType;
@Schema(name="noise",description="杂音")
private String noise;
@Schema(name="noiseInfo",description="心脏杂音描述")
private String noiseInfo;
@Schema(name="abdominalTenderness",description="腹部压痛")
private String abdominalTenderness;
@Schema(name="abdominalTendernessInfo",description="腹部压痛详情")
private String abdominalTendernessInfo;
@Schema(name="abdominalMass",description="腹部包块")
private String abdominalMass;
@Schema(name="abdominalMassInfo",description="腹部包块描述")
private String abdominalMassInfo;
@Schema(name="abdominalLiverEnlargement",description="腹部肝大")
private String abdominalLiverEnlargement;
@Schema(name="abdominalLiverEnlargementInfo",description="腹部肝大描述")
private String abdominalLiverEnlargementInfo;
@Schema(name="abdominalSplenomegaly",description="腹部脾大")
private String abdominalSplenomegaly;
@Schema(name="abdominalSplenomegalyInfo",description="腹部脾大描述")
private String abdominalSplenomegalyInfo;
@Schema(name="abdominalMobilityVoiced",description="腹部移动性浊音")
private String abdominalMobilityVoiced;
@Schema(name="abdominalMobilityVoicedInfo",description="腹部移动性浊音描述")
private String abdominalMobilityVoicedInfo;
@Schema(name="categoryLowerLimbResultsType",description="下肢水肿检查结果类别")
private String categoryLowerLimbResultsType;
@Schema(name="dorsalisPedisArteryPulsation",description="足背动脉搏动")
private String dorsalisPedisArteryPulsation;
@Schema(name="dre",description="肛门指诊")
private String dre;
@Schema(name="dreInfo",description="肛门指诊描述")
private String dreInfo;
@Schema(name="mammaryGland",description="乳腺")
private String mammaryGland;
@Schema(name="mammaryGlandInfo",description="乳腺描述")
private String mammaryGlandInfo;
@Schema(name="vulva",description="外阴")
private String vulva;
@Schema(name="vulvaInfo",description="外阴详情")
private String vulvaInfo;
@Schema(name="vagina",description="阴道")
private String vagina;
@Schema(name="vaginaInfo",description="阴道详情")
private String vaginaInfo;
@Schema(name="neckUterus",description="宫颈")
private String neckUterus;
@Schema(name="neckUterusInfo",description="宫颈详情")
private String neckUterusInfo;
@Schema(name="uterineBody",description="宫体")
private String uterineBody;
@Schema(name="uterineBodyInfo",description="宫体详情")
private String uterineBodyInfo;
@Schema(name="uterineAdnexa",description="子宫附件")
private String uterineAdnexa;
@Schema(name="uterineAdnexaInfo",description="子宫附件详情")
private String uterineAdnexaInfo;
@Schema(name="checkOther",description="查体_其他")
private String checkOther;
@Schema(name="historyProgramVaccination",description="非免疫规划预防接种历史")
private String historyProgramVaccination;
@Schema(name="bloodRoutine",description="血常规")
private String bloodRoutine;
@Schema(name="hemoglobin",description="血红蛋白g/L")
private String hemoglobin;
@Schema(name="leukocyte",description="白细胞×10^9/L")
private String leukocyte;
@Schema(name="platelet",description="血小板×10^9/L")
private String platelet;
@Schema(name="bloodRoutineOther",description="血常规其他")
private String bloodRoutineOther;
@Schema(name="urinalysis",description="尿常规")
private String urinalysis;
@Schema(name="urinaryProtein",description="尿蛋白")
private String urinaryProtein;
@Schema(name="glucose",description="尿糖")
private String glucose;
@Schema(name="kbt",description="尿酮体")
private String kbt;
@Schema(name="ery",description="尿潜血")
private String ery;
@Schema(name="urinalysisOther",description="尿常规其他")
private String urinalysisOther;
@Schema(name="fastingBloodGlucose",description="空腹血糖 mmol/L")
private String fastingBloodGlucose;
@Schema(name="electrocardiogram",description="心电图")
private String electrocardiogram;
@Schema(name="electrocardiogramInfo",description="心电图异常描述")
private String electrocardiogramInfo;
@Schema(name="urinaryMicroalbumin",description="尿微量白蛋白 mg/dL")
private String urinaryMicroalbumin;
@Schema(name="fecalOccultBlood",description="大便潜血")
private String fecalOccultBlood;
@Schema(name="glycatedHemoglobinValue",description="糖化血红蛋白值 %")
private String glycatedHemoglobinValue;
@Schema(name="hbsag",description="乙肝表面抗原(HBsAg)")
private String hbsag;
@Schema(name="liverFunction",description="肝功能")
private String liverFunction;
@Schema(name="serumAlanineAminotransferase",description="血清谷丙转氨酶U/L")
private String serumAlanineAminotransferase;
@Schema(name="sgot",description="血清谷草转氨酶U/L")
private String sgot;
@Schema(name="albumin",description="白蛋白g/L")
private String albumin;
@Schema(name="totalBilirubin",description="总胆红素μmol/L")
private String totalBilirubin;
@Schema(name="renalFunction",description="肾功能")
private String renalFunction;
@Schema(name="serumCreatinine",description="血清肌酐μmol/L")
private String serumCreatinine;
@Schema(name="bloodUreaNitrogen",description="血尿素氮mmol/L")
private String bloodUreaNitrogen;
@Schema(name="bloodPotassiumConcentration",description="血钾浓度mmol/L")
private String bloodPotassiumConcentration;
@Schema(name="bloodSodiumConcentration",description="血钠浓度 mmol/L")
private String bloodSodiumConcentration;
@Schema(name="bloodFat",description="血脂")
private String bloodFat;
@Schema(name="totalCholesterolLevel",description="总胆固醇值mmol/L")
private String totalCholesterolLevel;
@Schema(name="triglyceride",description="甘油三酯mmol/L")
private String triglyceride;
@Schema(name="serumLowLipoproteinCholesterol",description="血清低密度脂蛋白胆固醇mmol/L")
private String serumLowLipoproteinCholesterol;
@Schema(name="serumHighLipoproteinCholesterol",description="血清高密度脂蛋白胆固醇mmol/L")
private String serumHighLipoproteinCholesterol;
@Schema(name="xray",description="X射线")
private String xray;
@Schema(name="xrayInfo",description="胸透X线片异常描述 ")
private String xrayInfo;
@Schema(name="abdominalBultrasound",description="腹部B超")
private String abdominalBultrasound;
@Schema(name="abdominalBultrasoundInfo",description="腹部B超异常描述 ")
private String abdominalBultrasoundInfo;
@Schema(name="otherBultrasound",description="其他B超")
private String otherBultrasound;
@Schema(name="otherBultrasoundInfo",description="其他B超异常描述")
private String otherBultrasoundInfo;
@Schema(name="cervicalSmear",description="宫颈涂片")
private String cervicalSmear;
@Schema(name="cervicalSmearInfo",description="宫颈涂片异常描述")
private String cervicalSmearInfo;
@Schema(name="otherAuxiliaryExaminations",description="其他辅助检查")
private String otherAuxiliaryExaminations;
@Schema(name="familyBedHistory",description="家庭病床历史")
private String familyBedHistory;
@Schema(name="healthEvaluation",description="健康评价")
private String healthEvaluation;
@Schema(name="healthAbnormal",description="健康异常")
private String healthAbnormal;
@Schema(name="healthGuidance",description="健康指导")
private String healthGuidance;
@Schema(name="riskFactorControl",description="危险因素控制")
private String riskFactorControl;
@Schema(name="otherRiskFactorControl",description="其他危险因素控制")
private String otherRiskFactorControl;
@Schema(name="suggestedWeight",description="建议体重 kg")
private String suggestedWeight;
@Schema(name="suggestGettingVaccinated",description="建议接种疫苗")
private String suggestGettingVaccinated;
@Schema(name="physicalExerciseFrequency",description="体育锻炼 锻炼频率")
private String physicalExerciseFrequency;
@Schema(name="exerciseTimePerSession",description="每次锻炼时间:分钟")
private String exerciseTimePerSession;
@Schema(name="persistExerciseTime",description="坚持锻炼时间:年")
private String persistExerciseTime;
@Schema(name="exerciseMethods",description="锻炼方式")
private String exerciseMethods;
@Schema(name="eatingHabits",description="饮食习惯")
private String eatingHabits;
@Schema(name="smokingSituation",description="吸烟情况 吸烟状况")
private String smokingSituation;
@Schema(name="dailySmokingVolume",description="日吸烟量:支")
private String dailySmokingVolume;
@Schema(name="ageSmoking",description="开始吸烟年龄:岁")
private String ageSmoking;
@Schema(name="smokingCessationAge",description="戒烟年龄:岁 ")
private String smokingCessationAge;
@Schema(name="drinkingSituation",description="饮酒情况 饮酒频率")
private String drinkingSituation;
@Schema(name="dailyAlcoholConsumption",description="日饮酒量:两")
private String dailyAlcoholConsumption;
@Schema(name="quitDrinking",description="是否戒酒")
private String quitDrinking;
@Schema(name="abstinenceAge",description="戒酒年龄:岁")
private String abstinenceAge;
@Schema(name="startDrinkingAge",description="开始饮酒年龄:岁")
private String startDrinkingAge;
@Schema(name="drunkennessInYear",description="近一年内是否曾醉酒")
private String drunkennessInYear;
@Schema(name="alcoholConsumptionType",description="饮酒种类")
private String alcoholConsumptionType;
@Schema(name="historyExposureOccupational",description="职业病危害因素接触史")
private String historyExposureOccupational;
@Schema(name="occupationalDiseaseType",description="职业病工种")
private String occupationalDiseaseType;
@Schema(name="occupationalDiseaseTime",description="职业病从业时间")
private String occupationalDiseaseTime;
@Schema(name="dust",description="粉尘")
private String dust;
@Schema(name="dustProtectionMeasures",description="粉尘防护措施")
private String dustProtectionMeasures;
@Schema(name="dustProtectionMeasuresInfo",description="粉尘防护措施描述")
private String dustProtectionMeasuresInfo;
@Schema(name="radiation",description="放射物质")
private String radiation;
@Schema(name="radiationProtection",description="放射物质防护措施")
private String radiationProtection;
@Schema(name="radiationProtectionInfo",description="放射物质防护措施描述")
private String radiationProtectionInfo;
@Schema(name="physicalFactors",description="物理因素")
private String physicalFactors;
@Schema(name="physicalFactorsProtection",description="物理因素防护措施")
private String physicalFactorsProtection;
@Schema(name="physicalFactorsProtectionInfo",description="物理因素防护措施描述")
private String physicalFactorsProtectionInfo;
@Schema(name="chemicalSubstances",description="化学物质")
private String chemicalSubstances;
@Schema(name="chemicalSubstancesProtection",description="化学物质防护措施")
private String chemicalSubstancesProtection;
@Schema(name="chemicalSubstancesProtectionInfo",description="化学物质防护措施描述")
private String chemicalSubstancesProtectionInfo;
@Schema(name="otherTypesToxins",description="其他毒物种类")
private String otherTypesToxins;
@Schema(name="otherTypesToxinsProtection",description="其他毒物种类防护措施")
private String otherTypesToxinsProtection;
@Schema(name="otherTypesToxinsProtectionInfo",description="其他毒物种类防护措施描述")
private String otherTypesToxinsProtectionInfo;
@Schema(name="cerebrovascularDisease",description="脑血管疾病")
private String cerebrovascularDisease;
@Schema(name="cerebrovascularDiseaseOther",description="脑血管疾病其他")
private String cerebrovascularDiseaseOther;
@Schema(name="kidneyDisease",description="肾脏疾病")
private String kidneyDisease;
@Schema(name="kidneyDiseaseOther",description="肾脏疾病其他")
private String kidneyDiseaseOther;
@Schema(name="heartDisease",description="心脏疾病")
private String heartDisease;
@Schema(name="heartDiseaseOther",description="心脏疾病其他")
private String heartDiseaseOther;
@Schema(name="vascularDisease",description="血管疾病")
private String vascularDisease;
@Schema(name="vascularDiseaseOther",description="血管疾病其他")
private String vascularDiseaseOther;
@Schema(name="eyeDiseases",description="眼部疾病")
private String eyeDiseases;
@Schema(name="eyeDiseasesOther",description="眼部疾病其他")
private String eyeDiseasesOther;
@Schema(name="nervousSystemDisease",description="神经系统疾病")
private String nervousSystemDisease;
@Schema(name="nervousSystemDiseaseOther",description="神经系统疾病描述")
private String nervousSystemDiseaseOther;
@Schema(name="otherSystemDisease",description="其他系统疾病")
private String otherSystemDisease;
@Schema(name="otherSystemDiseaseName",description="其他系统疾病名称")
private String otherSystemDiseaseName;
@Schema(name="bodyTemperature",description="体温 ℃")
private String bodyTemperature;
@Schema(name="pulseRate",description="脉率 次/分钟")
private String pulseRate;
@Schema(name="respiratoryRate",description="呼吸频率 次/分钟")
private String respiratoryRate;
@Schema(name="leftSystolicBloodPressure",description="左侧收缩压 mmHg")
private String leftSystolicBloodPressure;
@Schema(name="leftDiastolicBloodPressure",description="左侧舒张压 mmHg")
private String leftDiastolicBloodPressure;
@Schema(name="rightSystolicBloodPressure",description="右侧收缩压 mmHg")
private String rightSystolicBloodPressure;
@Schema(name="rightDiastolicBloodPressure",description="右侧舒张压 mmHg")
private String rightDiastolicBloodPressure;
@Schema(name="height",description="身高 cm")
private String height;
@Schema(name="weight",description="体重 kg")
private String weight;
@Schema(name="waistCircumference",description="腰围 cm")
private String waistCircumference;
@Schema(name="bodyMassIndex",description="体质指数 Kg/m2")
private String bodyMassIndex;
@Schema(name="oldHealthSelfAssessment",description="老年人健康状态自我评估")
private String oldHealthSelfAssessment;
@Schema(name="oldLifeSelfAssessment",description="老年人生活自理能力自我评估")
private String oldLifeSelfAssessment;
@Schema(name="oldCognition",description="老年人认知功能")
private String oldCognition;
@Schema(name="oldCognitionScore",description="老年人认知功能评分")
private String oldCognitionScore;
@Schema(name="oldEmotion",description="老年人情感状态初筛结果")
private String oldEmotion;
@Schema(name="oldEmotionScore",description="老年人抑郁评分")
private String oldEmotionScore;
@Schema(name="medicationDetails",description="用药详情")
private String medicationDetails;
@Schema(name="oralCavity",description="口腔")
private String oralCavity;
@Schema(name="oralCavityOutType",description="口唇外观类别")
private String oralCavityOutType;
@Schema(name="toothCategory",description="齿列类别")
private String toothCategory;
@Schema(name="pharyngealExaminationResults",description="咽部检查结果")
private String pharyngealExaminationResults;
@Schema(name="vision",description="视力")
private String vision;
@Schema(name="leftVision",description="左眼裸眼视力值")
private String leftVision;
@Schema(name="rightVision",description="右眼裸眼视力值")
private String rightVision;
@Schema(name="leftNewVision",description="左眼矫正视力值")
private String leftNewVision;
@Schema(name="rightNewVision",description="右眼矫正视力值")
private String rightNewVision;
@Schema(name="hearingResults",description="听力检测结果")
private String hearingResults;
@Schema(name="motorFunctionStatus",description="运动功能状态")
private String motorFunctionStatus;
@Schema(name="symptom",description="症状")
private String symptom;
@Schema(name="medicationSituation",description="用药情况:")
private String medicationSituation;
@Schema(name="hospitalCourse",description="住院治疗情况")
private String hospitalCourse;
@Schema(name="excelNumber",description="excel文件名编号")
private String excelNumber;
@Schema(name="status",description="状态")
private long status;
@Schema(name="extend",description="扩展字段")
private String extend;
@Schema(name="create_time",description="创建时间")
private LocalDateTime createTime;
@Schema(name="syncStatus",description="同步状态")
private Integer syncStatus;
}
用药详情特殊格式实体:
package com.chinaunicom.medical.business.cdm.model.medical.record;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**_用药详情
* @author ZhaoShuhao
* @data 2024/11/1 15:45
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MedicationDetails {
@Schema(description = "用药名称")
private String medicationName;
@Schema(description = "用法")
private String medicationInfo;
@Schema(description = "用量")
private String medicationCount;
@Schema(description = "用药时间")
private String medicationTime;
@Schema(description = "服药依从性")
private String medicationCompliance;
}
2.2.2.2 mapper
package com.chinaunicom.medical.business.cdm.dao.cdm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chinaunicom.medical.business.cdm.dao.cdm.entity.OdsMedicalExaminationDetailExcelData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
/**
* @author ZhaoShuhao
* @description 针对表【ods_medical_examination_detail_excel_data(原始健康体检记录Excel详情表)】的数据库操作Mapper
* @createDate 2024-11-01 09:58:54
* @Entity mybatisxTest.model.OdsMedicalExaminationDetailExcelData
*/
@Mapper
public interface OdsMedicalExaminationDetailExcelDataMapper extends BaseMapper<OdsMedicalExaminationDetailExcelData> {
@Select("select min(create_time) from ods_medical_examination_detail_excel_data where sync_status = 0")
LocalDateTime getMinUnSyncDataTime();
@Select("update ods_medical_examination_detail_excel_data set sync_status = sync_status + 1 where create_time between #{startTime} and #{endTime}")
void batchSetSynced(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
}
2.2.2.3 业务类
2.2.2.3.1 总体实现
package com.chinaunicom.medical.business.cdm.analysis.excel.medicalexamination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chinaunicom.medical.business.cdm.dao.cdm.entity.OdsMedicalExaminationDetailExcelData;
import com.chinaunicom.medical.business.cdm.dao.cdm.mapper.OdsMedicalExaminationDetailExcelDataMapper;
import com.chinaunicom.medical.business.cdm.model.medical.record.FamilyBedHistory;
import com.chinaunicom.medical.business.cdm.model.medical.record.HistoryNonImmunizationRecord;
import com.chinaunicom.medical.business.cdm.model.medical.record.HospitalCourse;
import com.chinaunicom.medical.business.cdm.model.medical.record.MedicationDetails;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* 体检记录详情信息,Excel解析
*/
@Slf4j
@Service
public class OdsMedicalExaminationDetailExcelAnalysis extends ServiceImpl<OdsMedicalExaminationDetailExcelDataMapper, OdsMedicalExaminationDetailExcelData> {
private final AtomicInteger threadTaskCount = new AtomicInteger(0);
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(20,20,5, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
private final List<OdsMedicalExaminationDetailExcelData> dataList = new CopyOnWriteArrayList<>();
private final List<String> successFileList = Collections.synchronizedList(new ArrayList<>());
private final List<String> failFileList = Collections.synchronizedList(new ArrayList<>());
/**
* C:\Users\KeepHappy\Desktop\清华长庚-慢病-数据采集\数据采集-详情样例\体检记录
* @param fileAnalysisDirPath 需要解析的Excel文件路径
* @param successFileDirPath 解析成功的路径
* @param failFileDirPath 解析失败的路径
*/
@SneakyThrows
public void analysis(String fileAnalysisDirPath,String successFileDirPath,String failFileDirPath) {
String fileDirPath = StrUtil.removeSuffix(fileAnalysisDirPath,File.separator);
if(!FileUtil.exist(fileDirPath)){
log.info("{}文件夹不存在,退出解析",fileDirPath);
return;
}
String baseInfoFileSuffix = "_基本信息.xls";
String generalCondition = "_一般状况.xls";
String medicationDetails = "_用药详情.xls";
List<String> fileNameList = FileUtil.listFileNames(fileDirPath);
if(CollUtil.isNotEmpty(fileNameList)){
new ArrayList<>(fileNameList).stream().filter(f -> StrUtil.contains(f, baseInfoFileSuffix)).forEach(baseInfoFileName ->{
String filePrefix = StrUtil.removeSuffix(baseInfoFileName, baseInfoFileSuffix);
Set<String> patientFileNameList = fileNameList.stream().filter(f -> f.contains(filePrefix)).collect(Collectors.toSet());
OdsMedicalExaminationDetailExcelData excelData = new OdsMedicalExaminationDetailExcelData();
excelData.setExcelNumber(StrUtil.subBefore(baseInfoFileName, baseInfoFileSuffix, true));
dataList.add(excelData);
//解析 基本信息
executor.execute(()->analysisBaseInfoExcel(excelData,fileDirPath+"\\"+baseInfoFileName));
//解析 __一般状况
patientFileNameList.stream().filter( fileName -> fileName.contains(generalCondition)).findFirst().ifPresent( fileName ->{
executor.execute(()->analysisGeneralCondition(excelData,fileDirPath+"\\"+fileName));
});
//解析 __用药详情
patientFileNameList.stream().filter( fileName -> fileName.contains(medicationDetails)).findFirst().ifPresent( fileName ->{
executor.execute(()->analysisMedicationDetails(excelData,fileDirPath+"\\"+fileName));
});
});
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(()->{
try {
ThreadUtil.sleep(1000);
while (threadTaskCount.get()>0){
ThreadUtil.sleep(1000);
}
executor.shutdownNow();
log.info("解析成功{}",JSONUtil.toJsonStr(dataList));
if(CollUtil.isNotEmpty(dataList)){
saveBatch(dataList);
}
//解析后,移动文件
String successFilePath = StrUtil.removeSuffix(successFileDirPath, File.separator);
String failFilePath = StrUtil.removeSuffix(failFileDirPath, File.separator);
FileUtil.mkdir(successFilePath);
FileUtil.mkdir(failFilePath);
successFileList.forEach(filePath ->{
String fileName = FileUtil.getName(filePath);
FileUtil.move(new File(filePath),new File(successFilePath+File.separator+fileName),true);
});
failFileList.forEach(filePath ->{
String fileName = FileUtil.getName(filePath);
FileUtil.move(new File(filePath),new File(failFilePath+File.separator+fileName),true);
});
} catch (Throwable e) {
log.error("发生异常",e);
}
countDownLatch.countDown();
}).start();
countDownLatch.await();
}
}
/**
* 解析基本信息
*/
private void analysisBaseInfoExcel(OdsMedicalExaminationDetailExcelData excelData, String filePath){
if(!FileUtil.exist(filePath)){
log.info("文件不存在=>{}",filePath);
return;
}
boolean success =false;
ExcelReader reader = null;
try {
threadTaskCount.incrementAndGet();
reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);
Optional.ofNullable(reader.readCellValue(1, 1)).ifPresent(v -> excelData.setName(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 2)).ifPresent(v -> excelData.setMedicalExaminationDate(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 3)).ifPresent(v -> excelData.setMedicalExaminationOrg(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 4)).ifPresent(v -> excelData.setPhone(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 1)).ifPresent(v -> excelData.setNumber(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 2)).ifPresent(v -> excelData.setMedicalExaminationDoctor(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 3)).ifPresent(v -> excelData.setIdCard(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 4)).ifPresent(v -> excelData.setResponsibleDoctor(StrUtil.toString(v).trim()));
success = true;
} catch (Exception e) {
log.error("解析基本信息异常,当前文件{}",filePath,e);
}finally {
threadTaskCount.decrementAndGet();
IoUtil.close(reader);
List<String> destList = success ? successFileList : failFileList;
destList.add(filePath);
}
}
/**
* 解析_一般状况
*/
private void analysisGeneralCondition(OdsMedicalExaminationDetailExcelData excelData, String filePath){
if(!FileUtil.exist(filePath)){
log.info("文件不存在=>{}",filePath);
return;
}
boolean success =false;
ExcelReader reader = null;
try {
threadTaskCount.incrementAndGet();
reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);
Optional.ofNullable(reader.readCellValue(1, 1)).ifPresent(v -> excelData.setBodyTemperature(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 2)).ifPresent(v -> excelData.setRespiratoryRate(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 3)).ifPresent(v -> excelData.setLeftSystolicBloodPressure(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 4)).ifPresent(v -> excelData.setRightSystolicBloodPressure(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 5)).ifPresent(v -> excelData.setHeight(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 6)).ifPresent(v -> excelData.setWaistCircumference(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 7)).ifPresent(v -> excelData.setOldHealthSelfAssessment(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 8)).ifPresent(v -> excelData.setOldLifeSelfAssessment(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 9)).ifPresent(v -> excelData.setOldCognition(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(1, 10)).ifPresent(v -> excelData.setOldEmotion(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 1)).ifPresent(v -> excelData.setPulseRate(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 3)).ifPresent(v -> excelData.setLeftDiastolicBloodPressure(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 4)).ifPresent(v -> excelData.setRightDiastolicBloodPressure(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 5)).ifPresent(v -> excelData.setWeight(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 6)).ifPresent(v -> excelData.setBodyMassIndex(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 9)).ifPresent(v -> excelData.setOldCognitionScore(StrUtil.toString(v).trim()));
Optional.ofNullable(reader.readCellValue(3, 10)).ifPresent(v -> excelData.setOldEmotionScore(StrUtil.toString(v).trim()));
success = true;
} catch (Exception e) {
log.error("解析一般状况信息异常,当前文件{}",filePath,e);
}finally {
threadTaskCount.decrementAndGet();
IoUtil.close(reader);
List<String> destList = success ? successFileList : failFileList;
destList.add(filePath);
}
}
/**
* 解析用药详情
*/
private void analysisMedicationDetails(OdsMedicalExaminationDetailExcelData excelData, String filePath){
if(!FileUtil.exist(filePath)){
log.info("文件不存在=>{}",filePath);
return;
}
boolean success =false;
ExcelReader reader = null;
try {
threadTaskCount.incrementAndGet();
reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);
List<List<Object>> read = reader.read(1);
//size > 1
List<MedicationDetails> medicationDetails = new ArrayList<>();
if(CollUtil.isNotEmpty(read)){
medicationDetails = read.stream().skip(1).map(list -> {
MedicationDetails record = new MedicationDetails();
record.setMedicationName(StrUtil.toString(CollUtil.get(list, 0)));
record.setMedicationInfo(StrUtil.toString(CollUtil.get(list, 1)));
record.setMedicationCount(StrUtil.toString(CollUtil.get(list, 2)));
record.setMedicationTime(StrUtil.toString(CollUtil.get(list, 3)));
record.setMedicationCompliance(StrUtil.toString(CollUtil.get(list, 4)));
return record;
}).collect(Collectors.toList());
}
excelData.setHistoryProgramVaccination(JSONUtil.toJsonStr(medicationDetails));
success = true;
} catch (Exception e) {
log.error("解析用药详情异常,当前文件{}",filePath,e);
}finally {
threadTaskCount.decrementAndGet();
IoUtil.close(reader);
List<String> destList = success ? successFileList : failFileList;
destList.add(filePath);
}
}
}
2.2.2.3.2 代码解析
- 主要功能
- 文件解析:从指定目录读取多个Excel文件,根据文件名解析不同类型的体检信息。
- 多线程处理:使用线程池并发处理多个文件的解析任务,提高处理效率。
- 数据持久化:将解析后的数据批量保存到数据库。
- 文件管理:解析完成后,将文件移动到不同的目录,区分成功和失败的文件。
- 类结构
- OdsMedicalExaminationDetailExcelAnalysis:主要的服务类,实现了ServiceImpl接口,提供了解析Excel文件的核心逻辑。
- OdsMedicalExaminationDetailExcelData:实体类,用于存储解析后的体检记录详情信息。
- MedicationDetails:实体类,用于存储用药详情信息。
- 关键方法
- analysis
- 参数:
- fileAnalysisDirPath:需要解析的Excel文件所在目录。
- successFileDirPath:解析成功后文件的存放目录。
- failFileDirPath:解析失败后文件的存放目录。
- 功能:
- 检查文件夹是否存在,如果不存在则退出解析。
- 列出所有文件名,过滤出包含特定后缀的文件(如“_基本信息.xls”)。
- 对每个文件创建一个OdsMedicalExaminationDetailExcelData对象,并添加到dataList中。
- 使用线程池并发解析“基本信息”、“一般状况”和“用药详情”三个部分的数据。
- 使用CountDownLatch等待所有解析任务完成,然后关闭线程池。
- 将解析后的数据批量保存到数据库。
- 将文件移动到成功或失败的目录。
- analysisBaseInfoExcel
- 参数:
- excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
- filePath:需要解析的Excel文件路径。
- 功能:
- 检查文件是否存在,如果不存在则记录日志并返回。
- 使用ExcelUtil读取Excel文件,解析基本信息并填充到excelData对象中。
- 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
- analysisGeneralCondition
- 参数:
- excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
- filePath:需要解析的Excel文件路径。
- 功能:
- 检查文件是否存在,如果不存在则记录日志并返回。
- 使用ExcelUtil读取Excel文件,解析一般状况信息并填充到excelData对象中。
- 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
- analysisMedicationDetails
- 参数:
- excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
- filePath:需要解析的Excel文件路径。
- 功能:
- 检查文件是否存在,如果不存在则记录日志并返回。
- 使用ExcelUtil读取Excel文件,解析用药详情信息并转换为MedicationDetails对象列表。
- 将用药详情信息转换为JSON字符串并存储在excelData对象中。
- 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
- 数据结构
- OdsMedicalExaminationDetailExcelData:包含体检记录的基本信息、一般状况和用药详情等字段。
- MedicationDetails:包含用药名称、用药信息、用药数量、用药时间和用药依从性等字段。
- 多线程处理
- 线程池:使用ThreadPoolExecutor创建固定大小的线程池,确保并发解析文件时不会消耗过多资源。
- 计数器:使用AtomicInteger记录正在处理的任务数,确保所有任务完成后才进行后续操作。
- 同步列表:使用Collections.synchronizedList创建线程安全的列表,用于存储成功和失败的文件路径。
- 文件管理
- 文件移动:解析完成后,根据解析结果将文件移动到不同的目录,便于后续管理和审计。
- 日志记录
- SLF4J:使用@Slf4j注解自动注入日志对象,记录解析过程中的重要信息和错误。