关于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;

java 复制代码
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
java 复制代码
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 总体实现
java 复制代码
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. 移动已处理的文件到备份目录。
  1. getPatientData(String fileDirPath, String fileName)
  • 功能:根据文件类型调用相应的解析方法,返回解析后的数据。
  1. getPatientBaseInfoData(String fileDirPath, String fileName)
  • 功能:解析患者的基本信息文件,返回一个包含基本信息的映射表。
  1. getPatientListData(String fileDirPath, String fileName)
  • 功能:解析患者列表数据文件,返回一个包含多条记录的列表,每条记录是一个映射表。
  1. 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. 检查数据映射是否为空
java 复制代码
if (dataMap.size() > 0) {

作用:首先检查dataMap是否包含数据。如果dataMap为空,则直接退出方法,不做任何处理。

  1. 获取OdsDiabetesFileInfo类的字段和方法
java 复制代码
Class clazz = OdsDiabetesFileInfo.class;
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();

作用:获取OdsDiabetesFileInfo类的所有字段和方法。这些字段和方法将用于后续的数据映射。

  1. 创建方法映射
java 复制代码
Map<String, Method> methodMap = Arrays.stream(methods).collect(Collectors.toMap(method -> method.getName().toLowerCase(), Function.identity()));

作用:将所有方法的名字(转换为小写)和方法对象存储在一个映射表中。这样可以通过方法名快速查找方法对象。

  1. 创建字段与设置方法的映射
java 复制代码
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)。

  1. 处理每个患者的数据
java 复制代码
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 实体信息

体检记录常规格式实体:

java 复制代码
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;
}

用药详情特殊格式实体:

java 复制代码
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
java 复制代码
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 总体实现
java 复制代码
package com.chinaunicom.medical.business.cdm.analysis.excel.medicalexamination;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chinaunicom.medical.business.cdm.dao.cdm.entity.OdsMedicalExaminationDetailExcelData;
import com.chinaunicom.medical.business.cdm.dao.cdm.mapper.OdsMedicalExaminationDetailExcelDataMapper;
import com.chinaunicom.medical.business.cdm.model.medical.record.FamilyBedHistory;
import com.chinaunicom.medical.business.cdm.model.medical.record.HistoryNonImmunizationRecord;
import com.chinaunicom.medical.business.cdm.model.medical.record.HospitalCourse;
import com.chinaunicom.medical.business.cdm.model.medical.record.MedicationDetails;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 体检记录详情信息,Excel解析
 */
@Slf4j
@Service
public class OdsMedicalExaminationDetailExcelAnalysis extends ServiceImpl<OdsMedicalExaminationDetailExcelDataMapper, OdsMedicalExaminationDetailExcelData> {
    private final AtomicInteger threadTaskCount = new AtomicInteger(0);

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(20,20,5, TimeUnit.SECONDS,new LinkedBlockingDeque<>());

    private final List<OdsMedicalExaminationDetailExcelData> dataList = new CopyOnWriteArrayList<>();

    private final List<String> successFileList = Collections.synchronizedList(new ArrayList<>());

    private final List<String> failFileList = Collections.synchronizedList(new ArrayList<>());

    /**
     * C:\Users\KeepHappy\Desktop\清华长庚-慢病-数据采集\数据采集-详情样例\体检记录
     * @param fileAnalysisDirPath   需要解析的Excel文件路径
     * @param successFileDirPath    解析成功的路径
     * @param failFileDirPath       解析失败的路径
     */
    @SneakyThrows
    public  void analysis(String fileAnalysisDirPath,String successFileDirPath,String failFileDirPath) {

        String fileDirPath = StrUtil.removeSuffix(fileAnalysisDirPath,File.separator);

        if(!FileUtil.exist(fileDirPath)){
            log.info("{}文件夹不存在,退出解析",fileDirPath);
            return;
        }

        String baseInfoFileSuffix = "_基本信息.xls";
        String generalCondition = "_一般状况.xls";
        String medicationDetails = "_用药详情.xls";


        List<String> fileNameList = FileUtil.listFileNames(fileDirPath);

        if(CollUtil.isNotEmpty(fileNameList)){
            new ArrayList<>(fileNameList).stream().filter(f -> StrUtil.contains(f, baseInfoFileSuffix)).forEach(baseInfoFileName ->{

                String filePrefix = StrUtil.removeSuffix(baseInfoFileName, baseInfoFileSuffix);
                Set<String> patientFileNameList = fileNameList.stream().filter(f -> f.contains(filePrefix)).collect(Collectors.toSet());

                OdsMedicalExaminationDetailExcelData excelData = new OdsMedicalExaminationDetailExcelData();
                excelData.setExcelNumber(StrUtil.subBefore(baseInfoFileName, baseInfoFileSuffix, true));
                dataList.add(excelData);
                //解析 基本信息
                executor.execute(()->analysisBaseInfoExcel(excelData,fileDirPath+"\\"+baseInfoFileName));
              
                //解析 __一般状况
                patientFileNameList.stream().filter( fileName -> fileName.contains(generalCondition)).findFirst().ifPresent( fileName ->{
                    executor.execute(()->analysisGeneralCondition(excelData,fileDirPath+"\\"+fileName));
                });
                //解析 __用药详情
                patientFileNameList.stream().filter( fileName -> fileName.contains(medicationDetails)).findFirst().ifPresent( fileName ->{
                    executor.execute(()->analysisMedicationDetails(excelData,fileDirPath+"\\"+fileName));
                });
            });

            CountDownLatch countDownLatch = new CountDownLatch(1);

            new Thread(()->{
                try {

                    ThreadUtil.sleep(1000);

                    while (threadTaskCount.get()>0){
                        ThreadUtil.sleep(1000);
                    }

                    executor.shutdownNow();
                    log.info("解析成功{}",JSONUtil.toJsonStr(dataList));

                    if(CollUtil.isNotEmpty(dataList)){
                        saveBatch(dataList);
                    }
                    //解析后,移动文件
                    String successFilePath = StrUtil.removeSuffix(successFileDirPath, File.separator);
                    String failFilePath = StrUtil.removeSuffix(failFileDirPath, File.separator);
                    FileUtil.mkdir(successFilePath);
                    FileUtil.mkdir(failFilePath);

                    successFileList.forEach(filePath ->{
                        String fileName = FileUtil.getName(filePath);
                        FileUtil.move(new File(filePath),new File(successFilePath+File.separator+fileName),true);
                    });
                    failFileList.forEach(filePath ->{
                        String fileName = FileUtil.getName(filePath);
                        FileUtil.move(new File(filePath),new File(failFilePath+File.separator+fileName),true);
                    });
                } catch (Throwable e) {
                    log.error("发生异常",e);
                }
                countDownLatch.countDown();
            }).start();
            countDownLatch.await();
        }

    }


    /**
     * 解析基本信息
     */
    private void analysisBaseInfoExcel(OdsMedicalExaminationDetailExcelData excelData, String filePath){
        if(!FileUtil.exist(filePath)){
            log.info("文件不存在=>{}",filePath);
            return;
        }
        boolean success =false;
        ExcelReader reader = null;
        try {
            threadTaskCount.incrementAndGet();
            reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);

            Optional.ofNullable(reader.readCellValue(1, 1)).ifPresent(v -> excelData.setName(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 2)).ifPresent(v -> excelData.setMedicalExaminationDate(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 3)).ifPresent(v -> excelData.setMedicalExaminationOrg(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 4)).ifPresent(v -> excelData.setPhone(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 1)).ifPresent(v -> excelData.setNumber(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 2)).ifPresent(v -> excelData.setMedicalExaminationDoctor(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 3)).ifPresent(v -> excelData.setIdCard(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 4)).ifPresent(v -> excelData.setResponsibleDoctor(StrUtil.toString(v).trim()));
            success = true;
        } catch (Exception e) {
            log.error("解析基本信息异常,当前文件{}",filePath,e);
        }finally {
            threadTaskCount.decrementAndGet();
            IoUtil.close(reader);
            List<String> destList = success ? successFileList : failFileList;
            destList.add(filePath);
        }
    }

    /**
     * 解析_一般状况
     */
    private void analysisGeneralCondition(OdsMedicalExaminationDetailExcelData excelData, String filePath){
        if(!FileUtil.exist(filePath)){
            log.info("文件不存在=>{}",filePath);
            return;
        }
        boolean success =false;
        ExcelReader reader = null;
        try {
            threadTaskCount.incrementAndGet();
            reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);

            Optional.ofNullable(reader.readCellValue(1, 1)).ifPresent(v -> excelData.setBodyTemperature(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 2)).ifPresent(v -> excelData.setRespiratoryRate(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 3)).ifPresent(v -> excelData.setLeftSystolicBloodPressure(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 4)).ifPresent(v -> excelData.setRightSystolicBloodPressure(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 5)).ifPresent(v -> excelData.setHeight(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 6)).ifPresent(v -> excelData.setWaistCircumference(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 7)).ifPresent(v -> excelData.setOldHealthSelfAssessment(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 8)).ifPresent(v -> excelData.setOldLifeSelfAssessment(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 9)).ifPresent(v -> excelData.setOldCognition(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(1, 10)).ifPresent(v -> excelData.setOldEmotion(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 1)).ifPresent(v -> excelData.setPulseRate(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 3)).ifPresent(v -> excelData.setLeftDiastolicBloodPressure(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 4)).ifPresent(v -> excelData.setRightDiastolicBloodPressure(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 5)).ifPresent(v -> excelData.setWeight(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 6)).ifPresent(v -> excelData.setBodyMassIndex(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 9)).ifPresent(v -> excelData.setOldCognitionScore(StrUtil.toString(v).trim()));
            Optional.ofNullable(reader.readCellValue(3, 10)).ifPresent(v -> excelData.setOldEmotionScore(StrUtil.toString(v).trim()));
            success = true;
        } catch (Exception e) {
            log.error("解析一般状况信息异常,当前文件{}",filePath,e);
        }finally {
            threadTaskCount.decrementAndGet();
            IoUtil.close(reader);
            List<String> destList = success ? successFileList : failFileList;
            destList.add(filePath);
        }
    }
    /**
     * 解析用药详情
     */
    private void analysisMedicationDetails(OdsMedicalExaminationDetailExcelData excelData, String filePath){
        if(!FileUtil.exist(filePath)){
            log.info("文件不存在=>{}",filePath);
            return;
        }
        boolean success =false;
        ExcelReader reader = null;
        try {
            threadTaskCount.incrementAndGet();
            reader = ExcelUtil.getReader(FileUtil.getInputStream(filePath),true);

            List<List<Object>> read = reader.read(1);
            //size > 1
            List<MedicationDetails> medicationDetails = new ArrayList<>();
            if(CollUtil.isNotEmpty(read)){
                medicationDetails = read.stream().skip(1).map(list -> {
                    MedicationDetails record = new MedicationDetails();
                    record.setMedicationName(StrUtil.toString(CollUtil.get(list, 0)));
                    record.setMedicationInfo(StrUtil.toString(CollUtil.get(list, 1)));
                    record.setMedicationCount(StrUtil.toString(CollUtil.get(list, 2)));
                    record.setMedicationTime(StrUtil.toString(CollUtil.get(list, 3)));
                    record.setMedicationCompliance(StrUtil.toString(CollUtil.get(list, 4)));
                    return record;
                }).collect(Collectors.toList());
            }
            excelData.setHistoryProgramVaccination(JSONUtil.toJsonStr(medicationDetails));
            success = true;
        } catch (Exception e) {
            log.error("解析用药详情异常,当前文件{}",filePath,e);
        }finally {
            threadTaskCount.decrementAndGet();
            IoUtil.close(reader);
            List<String> destList = success ? successFileList : failFileList;
            destList.add(filePath);
        }
    }
   
}
2.2.2.3.2 代码解析
  • 主要功能
    • 文件解析:从指定目录读取多个Excel文件,根据文件名解析不同类型的体检信息。
    • 多线程处理:使用线程池并发处理多个文件的解析任务,提高处理效率。
    • 数据持久化:将解析后的数据批量保存到数据库。
    • 文件管理:解析完成后,将文件移动到不同的目录,区分成功和失败的文件。
  • 类结构
    • OdsMedicalExaminationDetailExcelAnalysis:主要的服务类,实现了ServiceImpl接口,提供了解析Excel文件的核心逻辑。
    • OdsMedicalExaminationDetailExcelData:实体类,用于存储解析后的体检记录详情信息。
    • MedicationDetails:实体类,用于存储用药详情信息。
  • 关键方法
    • analysis
      • 参数:
        • fileAnalysisDirPath:需要解析的Excel文件所在目录。
        • successFileDirPath:解析成功后文件的存放目录。
        • failFileDirPath:解析失败后文件的存放目录。
      • 功能:
        • 检查文件夹是否存在,如果不存在则退出解析。
        • 列出所有文件名,过滤出包含特定后缀的文件(如"_基本信息.xls")。
        • 对每个文件创建一个OdsMedicalExaminationDetailExcelData对象,并添加到dataList中。
        • 使用线程池并发解析"基本信息"、"一般状况"和"用药详情"三个部分的数据。
        • 使用CountDownLatch等待所有解析任务完成,然后关闭线程池。
        • 将解析后的数据批量保存到数据库。
        • 将文件移动到成功或失败的目录。
    • analysisBaseInfoExcel
      • 参数:
        • excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
        • filePath:需要解析的Excel文件路径。
      • 功能:
        • 检查文件是否存在,如果不存在则记录日志并返回。
        • 使用ExcelUtil读取Excel文件,解析基本信息并填充到excelData对象中。
        • 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
    • analysisGeneralCondition
      • 参数:
        • excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
        • filePath:需要解析的Excel文件路径。
      • 功能:
        • 检查文件是否存在,如果不存在则记录日志并返回。
        • 使用ExcelUtil读取Excel文件,解析一般状况信息并填充到excelData对象中。
        • 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
    • analysisMedicationDetails
      • 参数:
        • excelData:OdsMedicalExaminationDetailExcelData对象,用于存储解析后的数据。
        • filePath:需要解析的Excel文件路径。
      • 功能:
        • 检查文件是否存在,如果不存在则记录日志并返回。
        • 使用ExcelUtil读取Excel文件,解析用药详情信息并转换为MedicationDetails对象列表。
        • 将用药详情信息转换为JSON字符串并存储在excelData对象中。
        • 记录解析是否成功,并将文件路径添加到成功或失败的列表中。
  • 数据结构
    • OdsMedicalExaminationDetailExcelData:包含体检记录的基本信息、一般状况和用药详情等字段。
    • MedicationDetails:包含用药名称、用药信息、用药数量、用药时间和用药依从性等字段。
  • 多线程处理
    • 线程池:使用ThreadPoolExecutor创建固定大小的线程池,确保并发解析文件时不会消耗过多资源。
    • 计数器:使用AtomicInteger记录正在处理的任务数,确保所有任务完成后才进行后续操作。
    • 同步列表:使用Collections.synchronizedList创建线程安全的列表,用于存储成功和失败的文件路径。
  • 文件管理
    • 文件移动:解析完成后,根据解析结果将文件移动到不同的目录,便于后续管理和审计。
  • 日志记录
    • SLF4J:使用@Slf4j注解自动注入日志对象,记录解析过程中的重要信息和错误。
相关推荐
禁默17 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood23 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑26 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528729 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶29 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework37 分钟前
【jenkins插件】
java
风_流沙42 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v1 小时前
leetCode43.字符串相乘
java·数据结构·算法