• Java真的不难(五十一)SpringBoot使用EasyExcel实现导出


    EasyExcel:

    大家好久不见!

    一、什么是EasyExcel?

    EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
    github地址: https://github.com/alibaba/easyexcel
    官方文档地址https://www.yuque.com/easyexcel/doc/easyexcel

    当然还有一个POI也可以实现操作Excel,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能,POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”

    EasyExcel和POI的区别:
    在这里插入图片描述


    二、EasyExcel的实际使用

    首先导入依赖:

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>2.2.4version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    依赖版本按需使用即可,一些基本的操作大家看官方文档即可,在这里就记录一下自己通过EasyExcel导出的表格及过程

    首先看一下需要导出的模板:

    在这里插入图片描述
    区域说明:
    A:单个单元格填充
    B:列表填充
    C:列表填充(但单元格格式与B区域不同)
    D:单个单元格填充

    一、A区域的填充数据模板的设置:

    这个区域填充还是很简单的,只要给每个表格取个参数名,用{}包裹,Java里面用Map来填充即可。Map的键对应{}里面的参数名,对应的值就是填充进去的值:
    在这里插入图片描述
    例如这样,以下数据都是从数据库查出后填入Map即可:

     HashMap<String, Object> workOrderFileData = new HashMap<>();
            for (ProjectWorkOrderFileData p : WorkOrderFileList) {
                workOrderFileData.put("projectCode", p.getProjectCode());
                workOrderFileData.put("projectName", p.getProjectName());
                workOrderFileData.put("inspectedEnt", p.getInspectedEnt());
                workOrderFileData.put("customerAddress", p.getCustomerAddress());
                workOrderFileData.put("linkMan", p.getLinkMan());
                workOrderFileData.put("linkPhone", p.getLinkPhone());
                workOrderFileData.put("bizCreateTime", p.getBizCreateTime().substring(0, 10));
                workOrderFileData.put("customerName", p.getCustomerName());
                workOrderFileData.put("prodCompany", p.getProdCompany());
                workOrderFileData.put("testCode", p.getTestCode());
                workOrderFileData.put("batchCode", p.getBatchCode());
                workOrderFileData.put("region", region);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    二、B和C区域的填充数据模板的设置:

    因为B区域是有两个列表,所以我们一定在模板内区分开来,给每个列表取个参数名,然后给每个字段取个参数名,通过列表参数名点(.)字段参数名即可:
    即:{} 代表普通变量, {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list
    注意: 用{} 来表示你要用的变量 如果本来就有"{“,”}" 特殊字符 用"{“,”}"代替
    在这里插入图片描述
    对于这两组数据,Java里面使用两个列表储存即可,列表内可以存一个对象:

    ArrayList<SampleData> SampleDataList = service.selectSampleData(proId);
    ArrayList<AnalysisMethodData> analysisMethodDataList = service.selectAnalysisMethodData(proId);
    
    • 1
    • 2

    对象里面的字段名即是该列表所有字段名:

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    @Builder
    public class SampleData {
        private int number;
        private String typeName;
        private String redFolderName;
        private String redAnalyzeItems;
        private int spotFrequency;
        private int frequency;
        private int day;
        private int cycleOrder;
        private int timesOrder;
        private int sampleOrder;
        private String remarks;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    D区域的填充和A区域是一样的,通过Map来填充即可,因为是一对一的关系


    三、两个列表之间单元格样式不一样的解决办法

    通过模板可以看到BC两个列表之前的单元格样式不一样,如果不做处理,在填充的时候,C区域的样式会根据B区域的样式来填充,也就是C区域合并的单元格会被拆分,所以需要一个工具类:

    public class MyHandler extends AbstractMergeStrategy {
    
        @Override
        protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
            if (relativeRowIndex == null || relativeRowIndex == 0) {
                return;
            }
            int rowIndex = cell.getRowIndex();
            int colIndex = cell.getColumnIndex();
            sheet = cell.getSheet();
            Row preRow = sheet.getRow(rowIndex - 1);
            Cell preCell = preRow.getCell(colIndex);//获取上一行的该格
            List<CellRangeAddress> list = sheet.getMergedRegions();
            CellStyle cs = cell.getCellStyle();
            cell.setCellStyle(cs);
            for (CellRangeAddress cellRangeAddress : list) {
                if (cellRangeAddress.containsRow(preCell.getRowIndex()) && cellRangeAddress.containsColumn(preCell.getColumnIndex())) {
                    int lastColIndex = cellRangeAddress.getLastColumn();
                    int firstColIndex = cellRangeAddress.getFirstColumn();
                    CellRangeAddress cra = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), firstColIndex, lastColIndex);
                    sheet.addMergedRegion(cra);
                    RegionUtil.setBorderBottom(BorderStyle.THIN, cra, sheet);
                    RegionUtil.setBorderLeft(BorderStyle.THIN, cra, sheet);
                    RegionUtil.setBorderRight(BorderStyle.THIN, cra, sheet);
                    RegionUtil.setBorderTop(BorderStyle.THIN, cra, sheet);
                    return;
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    直接复制当工具类就完事!


    三、完成其他配置,开始填充

    我们还要设置两个地址:模板的位置以及导出后文件的储存位置,以下是控制层完整代码

    @GetMapping(path = "/generateProjectWorkOrder")
        public JsonResp<String> generateProjectWorkOrder(@RequestParam("proId") String proId) {
            //设置模板位置以及导出后储存位置
            String templateFile = "D:/WorkProject/ExcelTemplate/模板.xls";
            String resultFile = "D:/WorkProject/ExcelTemplate/WorkOrderFile/" +"导出结果.xls";
            FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
            ExcelWriter excelWriter = EasyExcel.write(resultFile).withTemplate(templateFile).build();
            //使用让该组单元格格式不受影响的工具类
            WriteSheet sheet = EasyExcel.writerSheet(0).registerWriteHandler(new MyHandler()).build();
            //获取数据(分别是ABCD四组数据)
            HashMap<String, Object> workOrderFileData = service.selectWorkOrder(proId);
            ArrayList<SampleData> SampleDataList = service.selectSampleData(proId);
            ArrayList<AnalysisMethodData> analysisMethodDataList = service.selectAnalysisMethodData(proId);
            HashMap<String, String> reviewerData = service.selectReviewerData(proId);
            //单组填充(A区域)
            excelWriter.fill(workOrderFileData, sheet);
            //多个列表填充(B、C区域)
            // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹
            excelWriter.fill(new FillWrapper("data1", SampleDataList), fillConfig, sheet);
            excelWriter.fill(new FillWrapper("data2", analysisMethodDataList), fillConfig, sheet);
            //单组填充(D区域)
            excelWriter.fill(reviewerData, sheet);
            //关闭流
            excelWriter.finish();
            //返回数据
            JsonResp<String> objectJsonResp = new JsonResp<>();
            //返回路径
            resultFile = "lims/downloadGenerateExcel?path=" + resultFile;
            objectJsonResp.setData(resultFile);
            return objectJsonResp;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    数据获取的方式按照自己的格式来即可,若完成以上编写,可以实现本地的导出与下载,若需要部署在服务器上,则还需要从服务器下载到本地


    三、服务器项目导出后下载到本地

    若把项目部署在服务器上,使用该功能首先是下载到服务器上,然后再从服务器上下载到本地电脑。
    我们可以使用HttpServletResponse response 来操作:
    代码如下:

        @GetMapping(path = "/downloadGenerateExcel")
        public void download(String path, HttpServletResponse response) {
            try {
                // path是指想要下载的文件的路径
                File file = new File(path);
                String filename = file.getName();
                // 将文件写入输入流
                FileInputStream fileInputStream = new FileInputStream(file);
                InputStream fis = new BufferedInputStream(fileInputStream);
                byte[] buffer = new byte[fis.available()];
                //noinspection ResultOfMethodCallIgnored
                fis.read(buffer);
                fis.close();
                response.reset();
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
                // 告知浏览器文件的大小
                response.addHeader("Content-Length", "" + file.length());
                OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
                response.setContentType("application/octet-stream");
                outputStream.write(buffer);
                outputStream.flush();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这个方法的参数就是上述方法最后的代码:

    //返回数据
    JsonResp<String> objectJsonResp = new JsonResp<>();
    //返回路径
    resultFile = "lims/downloadGenerateExcel?path=" + resultFile;
    objectJsonResp.setData(resultFile);
    return objectJsonResp;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也就是导出完成后返回的参数可以自动调用这个方法,把返回的参数自动传入该方法的Path,即可完成从服务器下载到本地。


    四、使用效果

    运行对应的接口,即可把数据导出并填充在指定的Excel模板内,并下载到指定位置,使用以上模板导出的结果如下:
    在这里插入图片描述
    以上均为模拟数据,大家根据实际情况来编写即可

    在最后附上EasyExcel最新的文档地址https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill


    在这里插入图片描述

  • 相关阅读:
    docker安装es与kibana
    799. 香槟塔
    Docker+Jmeter+InfluxDB+Grafana 搭建性能监控平台
    openstack nova 源码分析
    数据分析:从界定问题开始做数据分析?
    03-安装docker及使用docker安装其他软件(手动挂载数据卷)
    西电计科院微机原理与系统设计课程笔记(车向泉版)
    LeetCode | 只出现一次的值(python解法)
    【C++修炼之路】8. string类详解
    设备零部件更换ar远程指导系统加强培训效果
  • 原文地址:https://blog.csdn.net/m0_57310550/article/details/127805964