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 解析失败的路径*/@SneakyThrowspublic 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 > 1List<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注解自动注入日志对象,记录解析过程中的重要信息和错误。