EasyExcel动态导入导出Excel数据

业务场景

甲方要求,导入一张年度预算表(如下图),部门和项目不固定,存到数据库,申请预算通过后,修改数据值,并可以导出预算表:年底,超出预算表进行核验。

总结一下,就是:导入一张动态的数据EXCEL表:行、列不固定;导入后存入数据库中,并对数据进行查询,修改。

实现思路

设想一下,把excel表格看作一个X轴Y轴的坐标系;每个单元格看作一个坐标系是一个值,那么每个单元格=X坐标+Y坐标+Value;通过这个思路,我们就可以设计出数据表。

代码

数据表

sql 复制代码
CREATE TABLE `custom_excel_data` (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `business_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务名称',
  `business_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务id',
  `x` int DEFAULT NULL COMMENT 'x坐标',
  `y` int DEFAULT NULL COMMENT 'y坐标',
  `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '值',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='自定义excel数据表';

使用jar包

使用jar包是阿里巴巴EasyExcel,3.3.2版本。

java 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

导入核心代码

java 复制代码
package com.demo.common.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 创建一个自定义的监听器
 *
 * @author yangzhiwen
 * @since 2024年3月11日
 */
@Slf4j
public class DynamicDataListener extends AnalysisEventListener<Map<Integer, Object>> {

    private List<Map<Integer, Object>> allData = new ArrayList<>();

    @Override
    public void invoke(Map<Integer, Object> data, AnalysisContext context) {
        // 每一行的数据都在这里处理
        // data是一个Map,键是列索引(从0开始),值是对应单元格的值
        log.info("Row Data: " + data);
        allData.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 所有数据解析完成后的回调
        log.info("数据解析完毕");
    }

    public List<Map<Integer, Object>> getAllData() {
        return allData;
    }

    public static void main(String[] args) throws IOException {
        URL url = new URL("http://10.16.95.68:8000/test/上传视频记录.xlsx");
        File tempFile = File.createTempFile("temp_excel", ".xlsx");

        // 下载远程文件到本地临时文件
        try (InputStream in = url.openStream()) {
            Files.copy(in, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        }

        String tempFilePath = tempFile.getAbsolutePath();

        DynamicDataListener dynamicDataListener = new DynamicDataListener();
        // 使用EasyExcel读取Excel
        EasyExcel.read(tempFilePath, dynamicDataListener)
                // 指定读取哪个sheet,如果不需要指定,则会默认读取第一个sheet
                .sheet("Sheet1")
                // 开始读取
                .doRead();
        // 获取所有读取到的原始数据
        List<Map<Integer, Object>> rawExcelData = dynamicDataListener.getAllData();
        rawExcelData.forEach(e->{
            log.info(String.valueOf(e.size()));
            log.info(e.get(0).toString());
            log.info(e.get(1).toString());
        });
    }
}

导出核心代码

这里是重新生成新的excel表格,并非去改原excel表格的值。

swift 复制代码
@GetMapping("export")  
public void export(HttpServletResponse response) throws Exception {  
    // 定义待导出数据集
    List<List<String>> eList = new ArrayList<>();  
    // ..... 组装list数据进行导出 
    EasyExcelUtil.exportExcel(response, eList, (Class<List<String>>) null,"xxx.xls","xxx.xls");  
}
scss 复制代码
/**
 * 根据模板导出excel
 * @param response
 * @param excelList 数据列表
 * @param fileName  文件名称
 * @param filePath  模版路径
 * @param exportTitle 标题对象(标题可以为空)
 * @throws Exception
 */
public static <T> void exportExcel(HttpServletResponse response, List<T> excelList,String fileName, String filePath,Object exportTitle) throws Exception {
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
    ServletOutputStream out = response.getOutputStream();
    response.setContentType("multipart/form-data");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    InputStream inputStream = EasyExcelUtil.class.getClassLoader().getResourceAsStream(filePath);
    ExcelWriter writer = EasyExcel.write(out).withTemplate(inputStream).build();
    WriteSheet sheet = EasyExcel.writerSheet(0).build();
    //填充列表开启自动换行,自动换行表示每次写入一条list数据是都会重新生成一行空行,此选项默认是关闭的,需要提前设置为true
    FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
    if (ObjectUtils.isNotEmpty(exportTitle))writer.fill(exportTitle, sheet);//填充标题
    writer.fill(excelList, fillConfig, sheet);//填充数据
    //填充完成
    writer.finish();
    out.flush();
}

查询

传入首行、首列的值,就可以定位,目标坐标,从而拿到数据。

perl 复制代码
@Override
public CustomExcelData getByXValueAndYValueAndBusinessId(String businessId, String xValue, String yValue) {
    CustomExcelData xData = this.getOneByBusinessIdAndValue(businessId, xValue);
    CustomExcelData yData = this.getOneByBusinessIdAndValue(businessId, yValue);
    if (Objects.isNull(xData) || Objects.isNull(yData)) {
        return null;
    }
    if (!xData.getY().equals(0) || !yData.getX().equals(0)) {
        return null;
    }
    return this.getOneByBusinessIdAndXY(businessId, xData.getX(),yData.getY());
}

private CustomExcelData getOneByBusinessIdAndValue(String businessId, String value) {
    return this.getOne(Wrappers.<CustomExcelData>lambdaQuery()
            .eq(CustomExcelData::getBusinessId, businessId).eq(CustomExcelData::getValue, value));
}

private CustomExcelData getOneByBusinessIdAndXY(String businessId, Integer x, Integer y) {
    return this.getOne(Wrappers.<CustomExcelData>lambdaQuery()
            .eq(CustomExcelData::getBusinessId, businessId)
            .eq(CustomExcelData::getX, x)
            .eq(CustomExcelData::getY, y));
}

结语

这是一个巧妙的思路:把一个excel抽象成一个坐标系;可以完成一些特定场景的业务。

缺陷也很明显,Excel数据必须严格按照横纵方式进行排列;其他奇怪的格式,就不支持了。

抛砖引玉,或许这个思路对你有用。

专栏:壹米饭认知

专栏:壹米饭解决方案

不定时更新,欢迎订阅、评论、转发;

希望能让你得到启发。

相关推荐
爬山算法21 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
找了一圈尾巴2 小时前
前后端交互通用排序策略
java·交互
哎呦没4 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957586 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解