关于Java合并多个Excel中的数据【该数据不是常规列表】,并入库保存的方案

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管理的服务组件

主要功能

  1. 文件读取与解析
    • 方法analysis(String fileDirPath, String bakDirPath)
    • 功能:从指定路径读取所有相关文件,解析每个文件中的患者信息,并将其存储在内存映射中。
    • 参数
      • fileDirPath:包含待处理文件的目录路径
      • bakDirPath:处理完成后文件的备份目录路径
  2. 数据映射与存储
    • 方法:内部逻辑处理
    • 功能:将解析出的数据映射到OdsDiabetesFileInfo对象,并调用数据库操作方法保存至数据库。
  3. 文件备份
    • 方法moveToBakDir(List<String> handledList, String fileDirPath, String bakDirPath)
    • 功能:将处理过的文件移动到备份目录,文件名后附加时间戳以区分不同批次的处理结果。

关键方法详解

1. analysis(String fileDirPath, String bakDirPath)

  • 流程
    1. 从指定目录fileDirPath读取所有文件名,并按名称排序。
    2. 对每个文件进行处理,提取患者编号,并根据文件类型调用不同的解析方法。
    3. 将解析得到的数据存入内存映射中。
    4. 将内存映射中的数据转换为OdsDiabetesFileInfo对象,并插入数据库。
    5. 移动已处理的文件到备份目录。

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注解自动注入日志对象,记录解析过程中的重要信息和错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/61062.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【进阶系列】python的模块

模块 创建一个 .py 文件&#xff0c;这个文件就称之为 一个模块 Module 如何使用 import 想要B.py文件中&#xff0c;使用A.py文件&#xff0c;只需要在B.py文件中使用关键字import导入即可。 import A# 若A是一个包的话&#xff0c;可以这样写 import A.函数名from impor…

使用Web Workers提升JavaScript的并行处理能力

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Web Workers提升JavaScript的并行处理能力 使用Web Workers提升JavaScript的并行处理能力 使用Web Workers提升JavaScript的…

从零开始使用GOT-OCR2.0——多模态通用型OCR(非常具有潜力的开源OCR项目):项目环境安装配置 + 测试使用

在之前精读了这个多模态的OCR项目论文&#xff0c;理解了其基本的模型架构&#xff0c;论文精读地址&#xff1a; 【论文精读】GOT-OCR2.0源码论文——打破传统OCR流程的多模态视觉-语言大模型架构&#xff1a;预训练VitDet 视觉模型 阿里通义千问Qwen语言模型-CSDN博客 本文在…

【Golang】——Gin 框架中的表单处理与数据绑定

在 Web 应用开发中&#xff0c;表单是用户与服务器交互的重要手段。Gin 框架对表单处理提供了高效便捷的支持&#xff0c;包括数据绑定、验证等功能。在本篇博客中&#xff0c;我们将详细介绍如何使用 Gin 框架处理表单数据&#xff0c;涵盖基础操作与进阶技巧&#xff0c;帮助…

删除k8s 或者docker运行失败的脚本

vi delete_exited_containers.sh#!/bin/bash# 列出所有停止的容器并存储到数组 list_exited_containers() {echo -e "\nStopped containers:"containers()# 获取停止的容器信息并存入数组while IFS read -r line; docontainers("$line")done < <(do…

Linux(命令格式详细+字符集 图片+大白话)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…

Clip结合Faiss+Flask简易版文搜图服务

一、实现 使用目录结构&#xff1a; templates ---upload.html faiss_app.py 前端代码&#xff1a;upload.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content&quo…

SpringBoot 之整合gRPC

父工程中引入基本的依赖&#xff1a; <modules><module>api</module><module>client</module><module>service</module></modules><parent><artifactId>spring-boot-starter-parent</artifactId><group…

汽车资讯新篇章:Spring Boot技术启航

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

「二」体验HarmonyOS端云一体化开发模板(一)

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 1 前置条件 实名认证的华为开发者账号 安装DevEco Studio 5.0.0 Re…

逆向攻防世界CTF系列41-EASYHOOK

逆向攻防世界CTF系列41-EASYHOOK 看题目是一个Hook类型的&#xff0c;第一次接触&#xff0c;虽然学过相关理论&#xff0c;可以看我的文章 Hook入门(逆向)-CSDN博客 题解参考&#xff1a;https://www.cnblogs.com/c10udlnk/p/14214057.html和攻防世界逆向高手题之EASYHOOK-…

Windows文件资源管理器增强工具

引言&#xff1a; 资源管理器在我们使用电脑时是经常用到的&#xff0c;各种文件资源等的分类整理都离不开它。但是Windows Explorer确实不好用&#xff0c;不智能&#xff0c;不符合人体工程学。特别是在一些场合&#xff0c;在打开的一堆文件夹里&#xff0c;想从中找到自己要…

【Flask+Gunicorn+Nginx】部署目标检测模型API完整解决方案

【Ubuntu 22.04FlaskGunicornNginx】部署目标检测模型API完整解决方案 文章目录 1. 搭建深度学习环境1.1 下载Anaconda1.2 打包环境1.3 创建虚拟环境1.4 报错 2. 安装flask3. 安装gunicorn4. 安装Nginx4.1 安装前置依赖4.2 安装nginx4.3 常用命令 5. NginxGunicornFlask5.1 ng…

Mac系统下配置 Tomcat 运行环境

下载并解压JDK 下载 根据自己需求下载对应版本的 jdk&#xff0c;我演示使用的是最新版的 jdk23&#xff0c;其他版本过程一样。 如果你是 M 芯片可以点击这个链接下载 如果你是 Intel 芯片可以点击这个链接下载 解压 下载完成后双击解压&#xff0c;将解压出来的文件夹放…

Getx:响应式数据,实现数据的局部刷新

Flutter官网demo实现计数器 这个demo中&#xff0c;如果要更新_count&#xff0c;调用setState就会重新build&#xff0c;这样做比较耗费性能&#xff0c;此时可以使用Getx的响应式状态管理器实现局部刷新 import package:flutter/material.dart;class JiShu extends Stateful…

Dowex 50WX8 ion-exchange resin可以用于去除水中的金属离子(如钠、钾、镁、钙等)和其他杂质,提高水质,11119-67-8

一、基本信息 中文名称&#xff1a;Dowex 50WX8 离子交换树脂 英文名称&#xff1a;Dowex 50WX8 ion-exchange resin CAS号&#xff1a;11119-67-8 供应商&#xff1a;陕西新研博美生物科技 外观&#xff1a;米色至浅棕色或绿棕色粉末/微球状 纯度&#xff1a;≥95% 分子…

使用Tengine 对负载均衡进行状态检查(day028)

本篇文章对于在服务器已经安装了nginx,但却希望使用Tengine 的状态检查或其他功能时使用&#xff0c;不需要卸载服务器上的nginx,思路是使用干净服务器&#xff08;未安装过nginx&#xff09;通过编译安装Tengine&#xff0c;通过对./configure的配置&#xff0c;保证安装Tengi…

2024 - 超火的多模态深度学习公共数据纯生信5+思路分享

超火的多模态深度学习公共数据纯生信5思路分享 多模态深度学习具有处理和整合多种类型信息的优势&#xff0c;特别是在预测患者预后方面能够结合不同类型的生物医学数据&#xff0c;如临床数据、基因表达数据、蛋白质组学数据、成像数据等&#xff0c;进而提高预后预测的准确性…

深入解析大带宽服务器:性能优势与选择指南

一、大带宽服务器是什么&#xff1f; 大带宽服务器指的是具备高网络带宽能力的服务器&#xff0c;通常提供1Gbps、10Gbps甚至更高的网络连接能力。与普通带宽服务器相比&#xff0c;大带宽服务器能够在更短时间内传输大量数据&#xff0c;因此常用于高流量、高并发需求的场景&…

【MySQL】RedHat8安装mysql9.1

一、下载安装包 下载地址&#xff1a;MySQL Enterprise Edition Downloads | Oracle MySQL :: MySQL Community Downloads 安装包&#xff1a;mysql-enterprise-9.1.0_el8_x86_64_bundle.tar 官方 安装文档&#xff1a;MySQL Enterprise Edition Installation Guide 二、安装…