关于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注解自动注入日志对象,记录解析过程中的重要信息和错误。
相关推荐
用户9047066835716 分钟前
如何使用 Spring MVC 实现 RESTful API 接口
java·后端
刘某某.17 分钟前
数组和小于等于k的最长子数组长度b
java·数据结构·算法
程序员飞哥22 分钟前
真正使用的超时关单策略是什么?
java·后端·面试
用户9047066835724 分钟前
SpringBoot 多环境配置与启动 banner 修改
java·后端
小old弟1 小时前
后端三层架构
java·后端
花花鱼1 小时前
spring boot 2.x 与 spring boot 3.x 及对应Tomcat、Jetty、Undertow版本的选择(理论)
java·后端
温柔一只鬼.1 小时前
Docker快速入门——第二章Docker基本概念
java·docker·容器
要争气1 小时前
5 二分查找算法应用
java·数据结构·算法
郑..方..醒1 小时前
java实现ofd转pdf
java·pdf
道可到1 小时前
阿里面试原题 java面试直接过06 | 集合底层——HashMap、ConcurrentHashMap、CopyOnWriteArrayList
java·后端·面试