导入文件允许合并表格

本来呢,已经有几年没咋写博客了,但是好像网上没什么好的合并导入可以抄的,周末加班了一天弄出来了,想一想也不算造轮子,可以露一手出来,最近也挺喜欢写注释的,应该方便大家抄的

java 复制代码
public class TrainClassArrangeListener extends AnalysisEventListener<TrainClassArrangeVo> implements ExcelListener<TrainClassArrangeVo> {

    private final TrainClassArrangeMapper baseMapper;

    private static final Logger logger = LoggerFactory.getLogger(TrainClassArrangeListener.class);


    // 存储所有行的原始数据
    private final Map<Integer, Map<Integer, String>> allRowDataCache = new ConcurrentHashMap<>();
    // 存储所有数据对象(按行索引)
    private final Map<Integer, TrainClassArrangeVo> dataMap = new ConcurrentHashMap<>();
    // 存储合并单元格信息
    private final List<CellExtra> mergeInfoList = new ArrayList<>();


    private final StringBuilder successMsg = new StringBuilder();
    private final StringBuilder failureMsg = new StringBuilder();

    private int successNum = 0;
    private int failureNum = 0;
    //导入总数
    private int totalRows = 0;

    private final Long planId;


    public TrainClassArrangeListener(TrainClassArrangeMapper baseMapper, Long planId) {
        this.baseMapper = baseMapper;
        this.planId = planId;
    }


    @Override
    public void invoke(TrainClassArrangeVo data, AnalysisContext context) {
        int rowIndex = context.readRowHolder().getRowIndex();
        //把所有导入的list添加计划id
        data.setPlanId(planId);
        Map<Integer, Cell> cellMap = context.readRowHolder().getCellMap();
        logger.debug("读取第{}行数据", rowIndex);

        // 缓存原始单元格数据
        cacheRowData(rowIndex, cellMap);


        // 缓存数据对象
        dataMap.put(rowIndex, data);

        totalRows++;
    }

    /**
     * 将合并的单元格添加mergeInfoList ,进行同意处理
     * @param extra
     * @param context
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        if (extra.getType() == CellExtraTypeEnum.MERGE) {
            mergeInfoList.add(extra);
            logger.debug("发现合并单元格: 行{}-{}, 列{}-{}",
                    extra.getFirstRowIndex(), extra.getLastRowIndex(),
                    extra.getFirstColumnIndex(), extra.getLastColumnIndex());
        }
    }




    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        try {
            logger.info("开始处理合并单元格,共{}个合并区域", mergeInfoList.size());

            // 处理所有合并单元格
            processAllMergedCells();

            // 发送所有处理后的数据
            sendAllData();

            logger.info("Excel解析完成,共处理{}行数据", totalRows);

        } finally {
            // 清理资源
            allRowDataCache.clear();
            dataMap.clear();
            mergeInfoList.clear();
        }
    }

    /**
     * 缓存行数据
     */
    private void cacheRowData(int rowIndex, Map<Integer, Cell> cellMap) {
        if (cellMap == null) return;

        Map<Integer, String> rowData = new HashMap<>();
        for (Map.Entry<Integer, Cell> entry : cellMap.entrySet()) {
            int columnIndex = entry.getKey();
            com.alibaba.excel.metadata.Cell cell = entry.getValue();
            String cellValue = convertCellToString(cell);
            rowData.put(columnIndex, cellValue);
        }
        allRowDataCache.put(rowIndex, rowData);
    }

    /**
     * 处理所有合并单元格
     */
    private void processAllMergedCells() {
        for (CellExtra extra : mergeInfoList) {
            processSingleMerge(extra);
        }
    }

    /**
     * 处理单个合并区域
     */
    private void processSingleMerge(CellExtra extra) {
        // 只处理垂直合并(同一列)
        if (!Objects.equals(extra.getFirstColumnIndex(), extra.getLastColumnIndex())) {
            return;
        }

        int firstRow = extra.getFirstRowIndex();
        int lastRow = extra.getLastRowIndex();
        int columnIndex = extra.getFirstColumnIndex();

        // 获取合并区域第一个单元格的值
        String firstCellValue = getCellValue(firstRow, columnIndex);

        if (firstCellValue != null && !firstCellValue.trim().isEmpty()) {
            logger.debug("处理合并: 列{}, 行{}-{}, 值: {}",
                    columnIndex, firstRow, lastRow, firstCellValue);

            // 为合并区域内的所有行设置相同的值
            for (int row = firstRow; row <= lastRow; row++) {
                TrainClassArrangeVo data = dataMap.get(row);
                if (data != null) {
                    setFieldValue(data, columnIndex, firstCellValue);
                    dataMap.put(row,data);
                    System.out.println(dataMap.get(row));
                }
            }
        }
    }

    /**
     * 发送所有数据
     */
    private void sendAllData() {
        for (int i = 1; i <= totalRows; i++) {
            TrainClassArrangeVo data = dataMap.get(i);
            if (data != null) {
                try {
                    TrainClassArrange arrange = MapstructUtils.convert(data, TrainClassArrange.class);
                    baseMapper.insert(arrange);
                    successNum++;
                } catch (Exception e) {
                    failureNum++;
                    logger.error("发送第{}行数据失败", i, e);
                }
            }
        }
    }



    /**
     * 获取单元格值
     */
    private String getCellValue(int rowIndex, int columnIndex) {
        Map<Integer, String> rowData = allRowDataCache.get(rowIndex);
        return rowData != null ? rowData.get(columnIndex) : null;
    }

    /**
     * 转换单元格为字符串
     */
    private String convertCellToString(com.alibaba.excel.metadata.Cell cell) {
        if (cell == null) return null;

        ReadCellData<?> readCellData = (ReadCellData<?>) cell;
        if (readCellData.getType() == CellDataTypeEnum.NUMBER) {
            Number number = readCellData.getNumberValue();
            return number != null ? number.toString() : null;
        }
        return readCellData.getStringValue();
    }

    /**
     * 设置字段值
     */
    private void setFieldValue(TrainClassArrangeVo data, int columnIndex, String value) {
        if (value == null) return;

        try {
            switch (columnIndex) {
                case 1:data.setDateArrange(value); break;
                case 2:data.setTrainClassName(value); break;
                case 3:data.setTrainee(value);break;
                case 4:data.setTrainContent(value);break;
                case 5:data.setGoalRequire(value);break;
                case 6:data.setOrganizationDept(value);break;
                case 7:data.setUnitOrPerson(value);break;
                case 8:data.setCompleteTrainTime(value);break;
                case 9:data.setTrainHour(value);break;
                case 10:data.setCheckMethod(value);break;
                default:
                    logger.debug("忽略未知列索引: {}", columnIndex);
                    break;
            }
        } catch (Exception e) {
            logger.error("设置字段值失败,列{},值: {}", columnIndex, value, e);
        }
    }

    @Override
    public ExcelResult<TrainClassArrangeVo> getExcelResult() {
        return new ExcelResult<TrainClassArrangeVo>() {
            @Override
            public List<TrainClassArrangeVo> getList() {
                return null;
            }

            @Override
            public List<String> getErrorList() {
                return null;
            }

            @Override
            public String getAnalysis() {
                if (failureNum > 0) {
                    throw  new TrainBusinessException("很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确");
//                    failureMsg.insert(0,
//                            "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确");
//                    ValidateBiz.error(failureMsg.toString());
                } else {
                    successMsg.insert(0,
                            "恭喜您,数据已全部导入成功!共 " + successNum + " 条");
                }
                return successMsg.toString();
            }
        };
    }
}
java 复制代码
/**
 * Excel 导入监听
 *
 * @author Lion Li
 */
public interface ExcelListener<T> extends ReadListener<T> {
    ExcelResult<T> getExcelResult();
}
java 复制代码
@Slf4j
@NoArgsConstructor
public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {

    /**
     * 是否Validator检验,默认为是
     */
    private Boolean isValidate = Boolean.TRUE;

    /**
     * excel 表头数据
     */
    private Map<Integer, String> headMap;

    /**
     * 导入回执
     */
    private ExcelResult<T> excelResult;

    public DefaultExcelListener(boolean isValidate) {
        this.excelResult = new DefaultExcelResult<>();
        this.isValidate = isValidate;
    }

    /**
     * 处理异常
     *
     * @param exception ExcelDataConvertException
     * @param context   Excel 上下文
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        String errMsg = null;
        if (exception instanceof ExcelDataConvertException excelDataConvertException) {
            // 如果是某一个单元格的转换异常 能获取到具体行号
            Integer rowIndex = excelDataConvertException.getRowIndex();
            Integer columnIndex = excelDataConvertException.getColumnIndex();
            errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
                rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
            if (log.isDebugEnabled()) {
                log.error(errMsg);
            }
        }
        if (exception instanceof ConstraintViolationException constraintViolationException) {
            Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
            String constraintViolationsMsg = StreamUtils.join(constraintViolations,
                ConstraintViolation::getMessage, ", ");
            errMsg = StrUtil.format("第{}行数据校验异常: {}",
                context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
            if (log.isDebugEnabled()) {
                log.error(errMsg);
            }
        }
        excelResult.getErrorList().add(errMsg);
        throw new ExcelAnalysisException(errMsg);
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
        log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        if (isValidate) {
            ValidatorUtils.validate(data);
        }
        excelResult.getList().add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.debug("所有数据解析完成!");
    }

    @Override
    public ExcelResult<T> getExcelResult() {
        return excelResult;
    }

}
java 复制代码
//接口调用地方
TrainClassArrangeListener listener = new TrainClassArrangeListener(classArrangeMapper, planId);
            // 读取Excel时启用额外信息读取
            EasyExcel.read(file.getInputStream(), TrainClassArrangeVo.class,
                            listener)
                    .extraRead(CellExtraTypeEnum.MERGE) // 关键:启用合并单元格读取
                    .sheet()
                    .doRead();

我确实不能保证我的每一步都是走对的,但是我保证不断尝试、不断进化,绝不贪图所谓的稳定,找寻生命的意义,不把生命放在所谓的后来