• SpringBoot+EasyExcel导入导出【加水印】



    本篇文章内容是针对于接口开发~~

    《POI方式导出导入的参考链接》


    背景

    老项目主要采用的POI框架来进行Excel数据的导入和导出,但经常会出现OOM的情况,导致整个服务不可用。后续逐步转移到EasyExcel。

    一、EasyExcel介绍

    EasyExcel是阿里巴巴开源poi插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。

    主要解决方式:通过解压文件的方式加载,一行一行的加载,并且抛弃样式字体等不重要的数据,降低内存的占用。

    EasyExcel优势:

    • 注解式自定义操作。
    • 输入输出简单,提供输入输出过程的接口
    • 支持一定程度的单元格合并等灵活化操作

    官方文档地址:https://easyexcel.opensource.alibaba.com/

    其他详细介绍参考官网

    二、案例配置

    2.1 依赖

    
    <dependency>
        <groupId>org.apache.poigroupId>
        <artifactId>ooxml-schemasartifactId>
        <version>1.0version>
    dependency>
    
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>2.2.11version>
    dependency>
    
    
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.4.0version>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
    
    • 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
    • 32

    其他所需依赖自行添加

    2.2 导入导出的实体类

    @Data
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public class Member {
        /**
         * EasyExcel使用:导出时忽略该字段
         */
        @ExcelIgnore
        private Integer id;
    
        @ExcelProperty("用户名")
        @ColumnWidth(20)
        private String username;
    
        /**
         * EasyExcel使用:日期的格式化
         */
        @ColumnWidth(20)
        @ExcelProperty("出生日期")
        @DateTimeFormat("yyyy-MM-dd")
        private Date birthday;
    
        /**
         * EasyExcel使用:自定义转换器
         */
        @ColumnWidth(10)
        @ExcelProperty(value = "性别", converter = GenderConverter.class)
        private Integer gender;
    }
    
    • 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

    常用注解:

    @ExcelProperty(index = 0, value = “姓名”) :

    • 用于设置Excel表头,其中index用户表头的编号,从0开始;value为表头对应的内容。

    @ExcelProperty(value = “性别”, converter = GenderConverter.class)

    • 自定义转换器

    @DateTimeFormat(“yyyy-MM-dd”)

    • 用于日期的格式化。

    @ContentStyle(dataFormat = 2)

    • 保留两位小数

    排除指定列的三种方式:

    • 方式1:在类上添加 @ExcelIgnoreUnannotated
    • 方式2:指定字段加@ExcelIgnore注解
    • 方式3:EasyExcel.write(fileName, UserData.class).sheet(“学生信息表”)
      .excludeColumnFiledNames(Arrays.asList(“remark”)).doWrite(getData());
      这种方法的好处是:同一Excel可以在调用方法时排除不同的数据列。

    2.3 自定义性别转换器

    自定义内容转换器,类似枚举的实现,将“男”、“女”转换成“0”、“1”的数值。

    package com.daniel.utils;
    
    import com.alibaba.excel.converters.Converter;
    import com.alibaba.excel.enums.CellDataTypeEnum;
    import com.alibaba.excel.metadata.CellData;
    import com.alibaba.excel.metadata.GlobalConfiguration;
    import com.alibaba.excel.metadata.property.ExcelContentProperty;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 10:25
     * @description
     */
    
    public class GenderConverter implements Converter<Integer> {
    
        private static final String MAN = "男";
        private static final String WOMAN = "女";
    
    
        @Override
        public Class<?> supportJavaTypeKey() {
            // 实体类中对象属性类型
            return Integer.class;
        }
    
        @Override
        public CellDataTypeEnum supportExcelTypeKey() {
            // Excel中对应的CellData属性类型
            return CellDataTypeEnum.STRING;
        }
    
        @Override
        public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty,
                                         GlobalConfiguration globalConfiguration) {
            // 从Cell中读取数据
            String gender = cellData.getStringValue();
            // 判断Excel中的值,将其转换为预期的数值
            if (MAN.equals(gender)) {
                return 0;
            } else if (WOMAN.equals(gender)) {
                return 1;
            }
            return null;
        }
    
        @Override
        public CellData<?> convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty,
                                              GlobalConfiguration globalConfiguration) {
            // 判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象
            if (integer == null) {
                return new CellData<>("");
            } else if (integer == 0) {
                return new CellData<>(MAN);
            } else if (integer == 1) {
                return new CellData<>(WOMAN);
            }
            return new CellData<>("");
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    三、导入导出工具类

    3.1 监听类

    package com.daniel.utils;
    
    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 11:04
     * @description 解析监听器
     *   每解析一行会回调invoke()方法。
     *   整个excel解析结束会执行doAfterAllAnalysed()方法
     *
     *   没有考虑合并单元格的情况
     */
    
    public class ExcelListener extends AnalysisEventListener<Object> {
    
        //定义一个保存Excel所有记录的集合
        private List<Object> datas = new ArrayList<>();
    
        public List<Object> getDatas() {
            return datas;
        }
    
        public void setDatas(List<Object> datas) {
            this.datas = datas;
        }
    
        /**
         * 逐行解析
         * object : 当前行的数据
         * 这个每一条数据解析都会来调用
         * 我们将每一条数据都保存到list集合中
         * @param object    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
         * @param analysisContext
         */
        @Override
        public void invoke(Object object, AnalysisContext analysisContext) {
            System.out.println("读取object=" + object);
            //当前行
            // context.getCurrentRowNum()
            //数据存储到list,供批量处理,或后续自己业务逻辑处理。
            if (object != null) {
                datas.add(object);
    //            handleBusinessLogic();
            }
    
              /*
            如数据过大,可以进行定量分批处理
            if(datas.size() >= 200){
                handleBusinessLogic();
                datas.clear();
            }
             */
        }
    
        /**
         * 读取表头内容
         * @param headMap 表头
         * @param analysisContext
         */
        @Override
        public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext analysisContext) {
            System.out.println("表头" + headMap);
        }
    
        /**
         * 解析完所有数据后会调用该方法
         * @param analysisContext
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            //解析结束销毁不用的资源,非必要语句,查看导入的数据
            System.out.println("读取Excel完毕" + datas.size());
        }
    
        //根据业务自行实现该方法,例如将解析好的dataList存储到数据库中
        private void handleBusinessLogic() {}
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    3.2 ExcelUtils工具类

    package com.daniel.utils;
    
    import cn.hutool.core.convert.Convert;
    import com.alibaba.excel.EasyExcel;
    import com.alibaba.excel.ExcelReader;
    import com.alibaba.excel.read.metadata.ReadSheet;
    import com.alibaba.excel.write.metadata.style.WriteCellStyle;
    import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
    import org.apache.poi.ss.usermodel.HorizontalAlignment;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 9:47
     * @description
     */
    public class ExcelUtils {
    
        private static final Logger log = LoggerFactory.getLogger(ExcelUtils.class);
    
        /**
         * 读取Excel(多个sheet可以用同一个实体类解析)
         * @param excelInputStream
         * @param fileName
         * @param clazz
         * @param 
         * @return
         */
        public static <T> List<T> readExcel(InputStream excelInputStream, String fileName, Class<T> clazz) {
            ExcelListener excelListener = new ExcelListener();
            ExcelReader excelReader = getReader(excelInputStream, fileName,clazz, excelListener);
            if (excelReader == null) {
                return new ArrayList<>();
            }
            List<ReadSheet> readSheetList = excelReader.excelExecutor().sheetList();
            for (ReadSheet readSheet : readSheetList) {
                excelReader.read(readSheet);
            }
            excelReader.finish();
            return Convert.toList(clazz, excelListener.getDatas());
        }
    
        /**
         * 导出数据
         *
         * @param head      类名
         * @param excelname excel名字
         * @param data      数据
         */
        public static void getExcelimporttemplate(Class head, String excelname, List data) {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = requestAttributes.getResponse();
            // 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            //response.setContentType("application/vnd.ms-excel");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
    
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            try {
                //表格头部样式
                WriteCellStyle headWriteCellStyle = new WriteCellStyle();
                //设置背景颜色
                //headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
                //设置头字体
                //WriteFont headWriteFont = new WriteFont();
                //headWriteFont.setFontHeightInPoints((short)13);
                //headWriteFont.setBold(true);
                //headWriteCellStyle.setWriteFont(headWriteFont);
                //设置头居中
                headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    
                //表格内容样式
                WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
                //设置 水平居中
                contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    
                HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
                String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
                response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
                EasyExcel.write(response.getOutputStream(), head).registerWriteHandler(horizontalCellStyleStrategy).sheet("Sheet1").doWrite(data);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 导出数据
         *
         * @param head      类名
         * @param excelname excel名字
         * @param data      数据
         */
        public static void excelExport(Class head, String excelname, List data) {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = requestAttributes.getResponse();
            // 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            //response.setContentType("application/vnd.ms-excel");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            try {
                String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
                response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
                EasyExcel.write(response.getOutputStream(), head).sheet("Sheet1").doWrite(data);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        /**
         * 导出数据二级表头
         * (合并第一行单元格,第二行开始是业务字段)
         * @param bigTitle  合并单元格的名字
         * @param head      类名
         * @param excelname 导出的excel名字
         * @param data      数据
         * @param header    导出的字段列名
         */
        public static void getBigTitleExcel(String bigTitle, Class head, String excelname, List data, List<String> header) {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = requestAttributes.getResponse();
            // 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            //response.setContentType("application/vnd.ms-excel");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            //response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            response.setCharacterEncoding("utf-8");
    
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            try {
                //表格头部样式
                WriteCellStyle headWriteCellStyle = new WriteCellStyle();
                //设置背景颜色
                //headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
                //设置头字体
                //WriteFont headWriteFont = new WriteFont();
                //headWriteFont.setFontHeightInPoints((short)13);
                //headWriteFont.setBold(true);
                //headWriteCellStyle.setWriteFont(headWriteFont);
                //设置头居中
                headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    
                //表格内容样式
                WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
                //设置 水平居中
                contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    
                HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
                String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
                response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
                EasyExcel.write(response.getOutputStream())
                    .head(getExcelHeader(header, bigTitle))
                    .inMemory(true)
                    .registerWriteHandler(new WaterMarkHandler("wwwwwwwwwwwwwwww"))
                    .registerWriteHandler(horizontalCellStyleStrategy).sheet("Sheet1")
                    .doWrite(data);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        private static List<List<String>> getExcelHeader(List<String> header, String bigTitle) {
            List<List<String>> head = new ArrayList<>();
            List<String> head0 = null;
            for (String h : header) {
                head0 = new ArrayList<>();
                head0.add(bigTitle);
                head0.add(h);
                head.add(head0);
            }
            return head;
        }
        
        /**
         * 返回ExcelReader
         *
         * @param excel         文件
         * @param clazz         实体类
         * @param excelListener
         */
        private static <T> ExcelReader getReader(InputStream inputStream, String filename, Class<T> clazz, ExcelListener excelListener) {
            try {
                if (filename == null ||
                    (!filename.toLowerCase().endsWith(".xls") && !filename.toLowerCase().endsWith(".xlsx"))) {
                    return null;
                }
                ExcelReader excelReader = EasyExcel.read(inputStream, clazz, excelListener).build();
                inputStream.close();
                return excelReader;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210

    3.3 导入操作

    package com.daniel.demo;
    
    import com.alibaba.excel.EasyExcel;
    import com.daniel.entity.Member;
    import com.daniel.utils.ExcelListener;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.IOException;
    import java.util.List;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 15:39
     * @description easyexcel 几种读取文件的方式
     *      方式一:同步读取,将解析结果返回,比如返回List,业务再进行相应的数据集中处理
     *      方式二:对照doReadSync()方法的是最后调用doRead()方法,不进行结果返回,而是在MemberImportExcelListener中进行一条条数据的处理;
     */
    
    @Controller
    @RequestMapping("read")
    public class ReadController {
    
        /**
         * 1. 从Excel导入内容
         * 基于同步获取结果列表的形式进行导入
         */
        @PostMapping("/import1")
        @ResponseBody
        public void importMemberList(@RequestPart("file") MultipartFile file) throws IOException {
            List<Member> list = EasyExcel.read(file.getInputStream())
                .head(Member.class)
                .sheet()
                .doReadSync();
            //打印读取的每条数据
            for (Member member : list) {
                System.out.println(member);
            }
        }
    
        /**
         * 2. 基于Listener方式从Excel导入会员列表
         */
        @RequestMapping(value = "/import2", method = RequestMethod.POST)
        @ResponseBody
        public void importMemberList2(@RequestPart("file") MultipartFile file) throws IOException {
            // 此处示例为方式二,读取监听器
            EasyExcel.read(file.getInputStream(), Member.class, new ExcelListener()).sheet().doRead();
        }
    
    	  /**
         *  基于ExcelUtils工具类导入
         */
        @PostMapping(value = "/import3")
        @ResponseBody
        public List<Member> read(MultipartFile excel) throws IOException {
            return ExcelUtils.readExcel(excel.getInputStream(), excel.getOriginalFilename(), Member.class);
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    3.4 导出操作

    业务逻辑实现

    为方便验证功能,DAO层的逻辑便不再实现,可以直接通过Service层来封装数据,或者使用本类最底部的data()方法模拟数据(代码中两个方法都有,注意区分使用),先来看导出功能的业务类实现。

    public interface MemberService {
        /**
         * 获取所有的成员信息
         * @return 成员信息列表
         */
        List<Member> getAllMember();
    }
    
    @Service("memberService")
    public class MemberServiceImpl implements MemberService {
        @Override
        public List<Member> getAllMember() {
            // 这里构造一些测试数据,具体业务场景可从数据库等其他地方获取
            List<Member> list = new ArrayList<>();
            Member member = new Member();
            member.setUsername("张三");
            member.setBirthday(getDate(1990, 10, 11));
            member.setGender(0);
            list.add(member);
    
            Member member1 = new Member();
            member1.setUsername("王红");
            member1.setBirthday(getDate(1999, 3, 29));
            member1.setGender(1);
            list.add(member1);
    
            Member member2 = new Member();
            member2.setUsername("李四");
            member2.setBirthday(getDate(2000, 2, 9));
            member2.setGender(0);
            list.add(member2);
    
            return list;
        }
    
        private Date getDate(int year, int month, int day) {
            Calendar calendar = Calendar.getInstance();
            calendar.set(year, month, day);
            return calendar.getTime();
        }
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 15:44
     * @description
     */
    
    @Controller
    @RequestMapping("write")
    public class WriteController {
    
        /**
         * 普通导出方式,不使用工具类
         */
        @PostMapping("/export1")
        public void exportMembers1(HttpServletResponse response,HttpServletRequest request) throws IOException {
        	//从数据库获取数据
            //List members = memberService.getAllMember();
            try {
                String filenames = "demo";
                String userAgent = request.getHeader("User-Agent");
                if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
                    filenames = URLEncoder.encode(filenames, "UTF-8");
                } else {
                    filenames = new String(filenames.getBytes("UTF-8"), "ISO-8859-1");
                }
                response.setContentType("application/vnd.ms-exce");
                response.setCharacterEncoding("utf-8");
                response.addHeader("Content-Disposition", "attachment;filename=" + filenames + ".xlsx");
                //从数据库获取数据,更换data()即可
                EasyExcel.write(response.getOutputStream(), Member.class).sheet("sheet").doWrite(data());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 基于策略及拦截器导出(带样式)
         */
        @RequestMapping("/export2")
        public void exportMembers2(HttpServletResponse response, HttpServletRequest request) throws IOException {
    //        List members = memberService.getAllMember();
            try {
                String filenames = "demo2";
                String userAgent = request.getHeader("User-Agent");
                if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
                    filenames = URLEncoder.encode(filenames, "UTF-8");
                } else {
                    filenames = new String(filenames.getBytes("UTF-8"), "ISO-8859-1");
                }
                response.setContentType("application/vnd.ms-exce");
                response.setCharacterEncoding("utf-8");
                response.addHeader("Content-Disposition", "attachment;filename=" + filenames + ".xlsx");
                EasyExcel.write(response.getOutputStream(), Member.class)
                    .sheet("sheet")
                    // 注册通用格式策略
                    .registerWriteHandler(CommonCellStyleStrategy.getHorizontalCellStyleStrategy())
                    // 设置自定义格式策略
    //                .registerWriteHandler(new CustomCellWriteHandler())
                    .doWrite(data());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 封装工具类导出方式
         */
        @PostMapping("/export3")
        @ResponseStatus(HttpStatus.CREATED)
        public void inputExcel(@RequestBody MultipartFile file) {
    
            //工具类方法1:
    //        ExcelUtils.getExcelimporttemplate(Member.class,"test1",data());
    
            //工具类方法2:
    //        ExcelUtils.excelExport(Member.class,"test1",data());
    
            //工具类方法3:
            List<String> list = new ArrayList<>();
            //列名
            list.add("111");
            list.add("222");
            list.add("333");
            ExcelUtils.getBigTitleExcel("测试big",Member.class,"test3",data(),list);
        }
    
        //辅助方法
        private List<Member> data() {
            List<Member> list = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                Member data = new Member();
                data.setUsername("Helen" + i);
                data.setBirthday(new Date());
                if (i%2 == 0) {
                    data.setGender(1);
                }else {
                    data.setGender(0);
                }
                list.add(data);
            }
            return list;
        }
    
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    小知识:

    • 03版本的文件是.xls,最大支持6万多行数据,文件里可以带宏
      算上标题,做多可写65536行,超出会报错:java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
    • 07版本的文件分了好多种,都能支持104万行,其中.xlsx是最常用的后缀,里面不能保存宏,这样可以提高安全性,不容易被宏病毒感染

    3.5 注册通用格式策略(参考封装)

    在EasyExcel执行write方法之后,获得ExcelWriterBuilder类,通过该类的registerWriteHandler方法可以设置一些处理策略。

    package com.daniel.utils;
    
    import com.alibaba.excel.write.metadata.style.WriteCellStyle;
    import com.alibaba.excel.write.metadata.style.WriteFont;
    import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
    import org.apache.poi.ss.usermodel.BorderStyle;
    import org.apache.poi.ss.usermodel.FillPatternType;
    import org.apache.poi.ss.usermodel.HorizontalAlignment;
    import org.apache.poi.ss.usermodel.IndexedColors;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 10:42
     * @description 通用的格式策略工具类,设置excel基础格式
     */
    
    public class CommonCellStyleStrategy {
        /**
         * 设置单元格样式(仅用于示例)
         *    其他业务需求样式参考封装
         * @return 样式策略
         */
        public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
            // 表头策略
            WriteCellStyle headerCellStyle = new WriteCellStyle();
            // 表头水平对齐居中
            headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            // 背景色
            headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
            WriteFont headerFont = new WriteFont();
            headerFont.setFontHeightInPoints((short) 15);
            headerCellStyle.setWriteFont(headerFont);
            // 自动换行
            headerCellStyle.setWrapped(Boolean.FALSE);
    
            // 内容策略
            WriteCellStyle contentCellStyle = new WriteCellStyle();
            // 设置数据允许的数据格式,这里49代表所有可以都允许设置
            contentCellStyle.setDataFormat((short) 49);
            // 设置背景色: 需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
            contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
            contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
            // 设置内容靠左对齐
            contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            // 设置字体
            WriteFont contentFont = new WriteFont();
            contentFont.setFontHeightInPoints((short) 12);
            contentCellStyle.setWriteFont(contentFont);
            // 设置自动换行
            contentCellStyle.setWrapped(Boolean.FALSE);
            // 设置边框样式和颜色
            contentCellStyle.setBorderLeft(BorderStyle.MEDIUM);
            contentCellStyle.setBorderTop(BorderStyle.MEDIUM);
            contentCellStyle.setBorderRight(BorderStyle.MEDIUM);
            contentCellStyle.setBorderBottom(BorderStyle.MEDIUM);
            contentCellStyle.setTopBorderColor(IndexedColors.RED.getIndex());
            contentCellStyle.setBottomBorderColor(IndexedColors.RED.getIndex());
            contentCellStyle.setLeftBorderColor(IndexedColors.RED.getIndex());
            contentCellStyle.setRightBorderColor(IndexedColors.RED.getIndex());
    
            // 将格式加入单元格样式策略
            return new HorizontalCellStyleStrategy(headerCellStyle, contentCellStyle);
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    该类中示例设置了Excel的基础格式。

    再来实现一个精细化控制单元格内容CellWriteHandler的实现类

    3.6 设置自定义格式策略(参考封装)

    package com.daniel.utils;
    
    import com.alibaba.excel.metadata.CellData;
    import com.alibaba.excel.metadata.Head;
    import com.alibaba.excel.write.handler.CellWriteHandler;
    import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
    import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
    import org.apache.poi.common.usermodel.HyperlinkType;
    import org.apache.poi.ss.usermodel.*;
    
    import java.util.List;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0016 10:44
     * @description 实现CellWriteHandler接口, 实现对单元格样式的精确控制
     */
    public class CustomCellWriteHandler implements CellWriteHandler {
    
        /**
         * 创建单元格之前的操作
         */
        @Override
        public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                     Head head, Integer integer, Integer integer1, Boolean aBoolean) {
        }
    
        /**
         * 创建单元格之后的操作
         */
        @Override
        public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
                                    Head head, Integer integer, Boolean aBoolean) {
    
        }
    
        /**
         * 单元格内容转换之后的操作
         */
        @Override
        public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                           CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
    
    	}
    
        /**
         * 单元格处理后(已写入值)的操作
         */
        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                     List<CellData> list, Cell cell, Head head, Integer integer, Boolean isHead) {
    
            // 设置超链接
            if (isHead && cell.getRowIndex() == 0 && cell.getColumnIndex() == 0) {
                CreationHelper helper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
                Hyperlink hyperlink = helper.createHyperlink(HyperlinkType.URL);
                hyperlink.setAddress("https://blog.csdn.net/qq1808814025?spm=1000.2115.3001.5343");
                cell.setHyperlink(hyperlink);
            }
    
            // 精确设置单元格格式
            boolean bool = isHead && cell.getRowIndex() == 1;
            if (bool) {
                // 获取工作簿
                Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
                CellStyle cellStyle = workbook.createCellStyle();
    
                Font cellFont = workbook.createFont();
                cellFont.setBold(Boolean.TRUE);
                cellFont.setFontHeightInPoints((short) 14);
                cellFont.setColor(IndexedColors.SEA_GREEN.getIndex());
                cellStyle.setFont(cellFont);
                cell.setCellStyle(cellStyle);
            }
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    四、导出的excel加水印

    4.1 水印工具类

    package com.daniel.utils;
    
    import com.alibaba.excel.write.handler.SheetWriteHandler;
    import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
    import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
    import lombok.RequiredArgsConstructor;
    import lombok.SneakyThrows;
    import org.apache.poi.ss.usermodel.*;
    import org.apache.poi.xssf.usermodel.XSSFRelation;
    import org.apache.poi.xssf.usermodel.XSSFSheet;
    import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    
    import javax.imageio.ImageIO;
    import java.awt.*;
    import java.awt.Color;
    import java.awt.Font;
    import java.awt.font.FontRenderContext;
    import java.awt.geom.Rectangle2D;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * @author daniel
     * @createTime 2022/9/16 0030 16:37
     * @description 
     * 		excel添加水印,只支持XSSFWorkbook,其余类别:SXSSFWorkbook、SXSSFWorkbook请另寻他法
     *  
     * easyExcel使用时需要设置inMemory(true),否者默认使用的是SXSSFWorkbook,会报错!
     *
     *  打印水印
     */
    @RequiredArgsConstructor
    public class WaterMarkHandler implements SheetWriteHandler {
    
        private final String WATER_MARK;
    
        public static ByteArrayOutputStream createWaterMark(String content) throws IOException {
            int width = 200;
            int height = 150;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 获取bufferedImage对象
            String fontType = "微软雅黑";
            int fontStyle = Font.BOLD;
            int fontSize = 20;
            Font font = new Font(fontType, fontStyle, fontSize);
            Graphics2D g2d = image.createGraphics(); // 获取Graphics2d对象
            image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
            g2d.dispose();
            g2d = image.createGraphics();
            g2d.setColor(new Color(0, 0, 0, 20)); //设置字体颜色和透明度,最后一个参数为透明度
            g2d.setStroke(new BasicStroke(1)); // 设置字体
            g2d.setFont(font); // 设置字体类型  加粗 大小
            g2d.rotate(-0.5, (double) image.getWidth() / 2, (double) image.getHeight() / 2);//设置倾斜度
            FontRenderContext context = g2d.getFontRenderContext();
            Rectangle2D bounds = font.getStringBounds(content, context);
            double x = (width - bounds.getWidth()) / 2;
            double y = (height - bounds.getHeight()) / 2;
            double ascent = -bounds.getY();
            double baseY = y + ascent;
            // 写入水印文字原定高度过小,所以累计写水印,增加高度
            g2d.drawString(content, (int) x, (int) baseY);
            // 设置透明度
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            // 释放对象
            g2d.dispose();
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(image, "png", os);
            return os;
        }
    
    
        /**
         * 为Excel打上水印工具函数
         *
         * @param sheet excel sheet
         * @param bytes 水印图片字节数组
         */
        public static void putWaterRemarkToExcel(XSSFSheet sheet, byte[] bytes) {
            //add relation from sheet to the picture data
            XSSFWorkbook workbook = sheet.getWorkbook();
            int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
            String rID = sheet.addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(pictureIdx))
                .getRelationship().getId();
            //设置水印图片
            sheet.getCTWorksheet().addNewPicture().setId(rID);
        }
    
        @Override
        public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        }
    
        @SneakyThrows
        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            try (ByteArrayOutputStream waterMark = createWaterMark(WATER_MARK)) {
                XSSFSheet sheet = (XSSFSheet) writeSheetHolder.getSheet();
                putWaterRemarkToExcel(sheet, waterMark.toByteArray());
            }
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    4.2 使用方式

    使用方式参考ExcelUtils工具类的 【导出数据二级表头(getBigTitleExcel)】 方法
    在这里插入图片描述此处导出的水印不可打印,只是背景图格式!!!

    以上就是EasyExcel的基础使用过程,欢迎点赞关注交流。

  • 相关阅读:
    安卓毕业设计选题基于Uniapp实现的鲜花购物商城
    容器化基础设施的威胁和风险不会更少
    广西柳州机械异形零部件三维扫描3D抄数全尺寸测绘建模-CASAIM中科广电
    Github每日精选(第19期):PHP 图像处理Intervention Image
    @Valid与@Validated区别和详细使用及参数注解校验大全
    QQ文件传输协议研究
    C++中set/multiset容器(黑马程序员)
    Spark读取Excel文件
    疯狂java 1.7垃圾回收机制
    jenkins声明式流水线pipline深入学习
  • 原文地址:https://blog.csdn.net/qq1808814025/article/details/126902866