首页 > 其他分享 >使用EasyExcel实现无模板、全自定义Excel导出

使用EasyExcel实现无模板、全自定义Excel导出

时间:2023-09-12 16:32:01浏览次数:40  
标签:String 自定义 Excel EasyExcel List private import new

1 需求背景

最近公司需要做一个动态字段的Excel导出,大致的样式如下:

image-20230606104726048.png

实体类如下:

// 部门实体类
public class Department {
    private String companyName;
    private String name;
    private String fullName;
    private String leaderName;
    private String business;
    private Long count;
    private String location;
    private String status;
    private List<Staff> staffList;
}
//员工实体类
public class Staff {
    private Long id;
    private String name;
    private Long age;
    private String position;
    private BigDecimal salary;
    private String code;
}

具体要求如下:

  1. 标题部分需要合并单元格;
  2. 展示部门信息的表格需要横向展示数据,且用户可以选择导出哪些字段的数据;
  3. 展示员工信息部分的数据为一个表格,用户可以选择导出哪些字段的数据。

如图所示,Excel可以被分成三个部分:

企业微信截图_16860187599012.png

这三个部分都需要动态生成,理由如下:

  1. 标题部分需要展示公司信息以及导出的是什么Excel(例如销售单、物流单等等),后续可能需要展示更多的信息,也就是说行数是动态的。并且图中的第三个部分也就是列表数据部分字段的个数是动态的,也就是说标题部分的列数也是动态的;
  2. 公共部分同理,且公共部分的字段也需要可选择的导出;
  3. 列表数据部分的字段个数以及数据的条数都是动态的,那么行和列都是动态的;

综上,我们现在的问题如下:

  1. 三个部分的表格行、列都是动态的;
  2. 三个部分在一个Excel中导出,列宽都会互相影响,如何自适应列宽?
  3. 公共部分的表格并不是传统的竖向表格,是横向排列的。
  4. 如何对应字段名和在Excel中显示的字段名的关系?比如某个字段在代码中为name,但Excel中应该显示为姓名
  5. 如何确定字段之间在Excel导出时的顺序,譬如name age salary三个字段,我希望它们按照name-age-salary的顺序显示,如果用户只导出name和salary字段,那顺序也应该是name-salary而不是其他的。

2 抽象导出实体类

如之前所说,可以将Excel分为三个部分,那么此时就可以构建一个类用以支持Excel的动态导出,具体代码如下:

// Excel实体类
public class ExportCustomCommon {
    // 标题部分,应该有几行就有几条
    List<String> headerTable;
    // 公共部分
    ExcelCommonData commonTable;
    // 列表数据部分
    List<? extends ExcelListData> listTable;
    
   	 // 列表数据的实体类,没有任何属性,仅作为一个对象的静态类型,方便统一处理
	public static class ExcelListData{}

    // 公共数据的实体类,没有任何属性,仅作为一个对象的静态类型,方便统一处理
	public static class ExcelCommonData{}
}

3 EasyExcel实现

引入EasyExcel依赖,我在这里使用的版本是2.2.10 。

<dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
    	<version>2.2.10</version>
</dependency>

首先需要知道,EasyExcel中有一个@ExcelProperty的注解,代码如下:

package com.alibaba.excel.annotation;
import ......
public @interface ExcelProperty {
	// 显示在Excel中的字段名
    String[] value() default {""};

	// 排序,值越小,优先级越高
    int order() default Integer.MAX_VALUE;
    …………
}

其余还有一些属性,但在本案例中没有用到,就不多做介绍。如此,字段在Excel中展示的名称问题和字段排序的问题通过EasyExcel自带的注解就已经可以解决。

接下来就是将需要导出的实体类加上注解:

public class DepartmentExcel extends ExportCustomCommon.ExcelCommonData {
    @ExcelProperty(value = "公司名", order = 1)
    private String companyName;
    @ExcelProperty(value = "部门名", order = 2)
    private String name;
    @ExcelProperty(value = "部门全名", order = 3)
    private String fullName;
    @ExcelProperty(value = "部门领导名", order = 4)
    private String leaderName;
    @ExcelProperty(value = "业务类型", order = 5)
    private String business;
    @ExcelProperty(value = "人数", order = 6)
    private Long count;
    @ExcelProperty(value = "地址", order = 7)
    private String location;
    @ExcelProperty(value = "状态", order = 8)
    private String status;
}
public class StaffExcel extends ExportCustomCommon.ExcelListData {
    private Long id;
    @ExcelProperty(value = "姓名", order = 1)
    private String name;
    @ExcelProperty(value = "年龄", order = 2)
    private Long age;
    @ExcelProperty(value = "职位", order = 3)
    private String position;
    @ExcelProperty(value = "工资", order = 4)
    private BigDecimal salary;
    @ExcelProperty(value = "编码", order = 5)
    private String code;
}

需要注意:Department中的数据用于公共部分表格,所以需要继承ExcelCommonData;Staff中数据是列表数据的展示,所以继承ExcelListData。

接下来是数据的获取及导出,标题部分很好解决,有几条数据就写几行,然后合并单元格就行了;列表数据部分也好解决,只需要使用EasyExcel自带的动态导出即可;关键是公共部分,因为是横向排列,并且列数不定,所以需要我们自己来做实现,具体代码如下:

package com.kazusa.excel.demo;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;

import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Description EasyExcel导出工具类
 * @Author Kazusa
 */
public class EasyExcelUtil {

    public void export(OutputStream os, Map<String, Object> params, List<String> fields) {
        //获取标题部分数据
        List<String> headerTable = (List) params.get("headerTable");
        //获取公共部分数据
        ExportCustomCommon.ExcelCommonData commonTable = (ExportCustomCommon.ExcelCommonData) params.get("commonTable");
        //获取列表部分数据
        List<ExportCustomCommon.ExcelListData> listTable = (List) params.get("listTable");
        //获取列表部分数据的类对象
        Class<? extends ExportCustomCommon.ExcelListData> listDataClass = listTable.get(0).getClass();
        //构建EasyExcel Writer对象
        ExcelWriter writer = null;

        try {
            writer = EasyExcel.write(os, listDataClass)//指定写入的流,以及需要EasyExcel自带动态生成的类的类对象
                    .excelType(ExcelTypeEnum.XLSX)//指定Excel文件类型,如xlsx、xls等
                	.build();
            WriteSheet sheet = EasyExcel
                    .writerSheet("sheet1")//指定写入的sheet
                    .needHead(false)//是否需要head,也就是每一个字段对应的字段名,这里为不需要,我们需要EasyExcel去生成字段名的地方只有列表数据部分
                    .build();
            //使用一个计数器记录当前已经写了几个表格
            AtomicInteger tableNoCounting = new AtomicInteger(1);
            //需要知道列数的最大值是多少
            int maxColumn = fields.size();
            this.buildHeader(maxColumn, headerTable, sheet, writer, tableNoCounting);
            this.buildCommon(maxColumn, commonTable, sheet, writer, tableNoCounting);
            this.buildList(listTable, sheet, writer, tableNoCounting, fields);
        } finally {
            assert writer != null;
            // 关闭流
            writer.finish();
        }

    }

    /**
     * 构建标题
     */
    private void buildHeader(int maxColumn, List<String> headerList, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {
        //自定义到处样式
        WriteCellStyle cellStyle = new WriteCellStyle();
        //水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont writeFont = new WriteFont();
        //加粗
        writeFont.setBold(Boolean.TRUE);
        //字体大小
        writeFont.setFontHeightInPoints((short) 15);
        cellStyle.setWriteFont(writeFont);
        //遍历标题部分的List
        for (String header : headerList) {
            WriteTable table = EasyExcel
                    .writerTable(tableNoCounting.get())//指定写入表格的序号,EasyExcel会将多个表格按照序号从小到大、由上到下的排列
                    .needHead(Boolean.FALSE)//也不需要标题
                    .registerWriteHandler(
                            //合并标题的单元格
                            new OnceAbsoluteMergeStrategy(tableNoCounting.get() - 1, tableNoCounting.getAndIncrement() - 1, 0, maxColumn)
                    )
                    //将自定义样式应用与该表
                    .registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle))
                    .build();
            //在这里,由于EasyExcel使用List<List<String>>这样的数据来构建一个表,里面的List<String>表示一行数据。
            //所以我们这里一次只构建一行数据,一行数据中只有一个单元格的数据,一行数据就作为一个表格写入
            //故有几个标题就需要构建几次表格
            List<String> cellList = new ArrayList<>();
            cellList.add(header);
            //因为需要合并单元格到列数最大的单元格处,这里如果不添加空字符串,EasyExcel不会构建单元格,在合并单元格的时候就会报错
            for (int i = 0; i < maxColumn - 1; ++i) {
                cellList.add("");
            }
            List<List<String>> rowList = new ArrayList<>();
            rowList.add(cellList);
            //写入表格
            writer.write(rowList, sheet, table);
        }
    }

    /**
     * 属性实体类
     */
    private static class ExcelField {
        //属性名
        private String fieldName;
        //Excel中显示名
        private String showName;
        //排序
        private int order;
        //属性值
        private Object value;

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getShowName() {
            return showName;
        }

        public void setShowName(String showName) {
            this.showName = showName;
        }

        public int getOrder() {
            return order;
        }

        public void setOrder(int order) {
            this.order = order;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }

    /**
     * 构建公共部分
     */
    private void buildCommon(int maxColumn, ExportCustomCommon.ExcelCommonData commonTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {
        if (ObjectUtil.isNotEmpty(commonTable)) {
            //获取公共数据的类对象
            Class<?> commonDataClass = commonTable.getClass();
            //通过类对象获取该类中的所有属性
            List<Field> fields = this.getAllField(commonDataClass);
            List<ExcelField> fieldList = new ArrayList<>();
            try {
                for (Field field : fields) {
                    //如果在Maven打包时报错,Spring项目中可以替换为Spring中的BeanUtils.getPropertyDescriptor()
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), commonDataClass);
                    Assert.notNull(pd, Exception::new);
                    //反射获取读方法
                    Method readMethod = pd.getReadMethod();
                    //读到属性值
                    Object fieldValue = readMethod.invoke(commonTable);
                    //获取属性注解
                    ExcelProperty property = field.getAnnotation(ExcelProperty.class);
                    //获取Excel显示名称
                    String excelFieldName = property.value()[0];
                    //获取Excel中排序
                    int excelFieldOrder = property.order();
                    //构建对象
                    ExcelField excelField = new ExcelField();
                    excelField.setFieldName(field.getName());
                    excelField.setShowName(excelFieldName);
                    excelField.setOrder(excelFieldOrder);
                    excelField.setValue(fieldValue);
                    fieldList.add(excelField);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            //根据Order排序
            fieldList.sort(Comparator.comparingInt(ExcelField::getOrder));
            int count = fieldList.size();
            //计算一行显示属性的个数,除以3是因为一个属性需要属性名--属性值--空字符串三个单元格
            int lineCount = maxColumn / 3;
            //计算行数
            int rows = (count + lineCount - 1) / lineCount;
            List<Object> cellList = new ArrayList<>();
            List<List<Object>> rowList = new ArrayList<>();
            //自定义样式
            WriteCellStyle cellStyle = new WriteCellStyle();
            //水平靠左
            cellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            //遍历所有行,一行作为一个表
            for (int row = 0; row < rows; ++row) {
                WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement())
                        .needHead(Boolean.FALSE)
                        .registerWriteHandler(
                                new HorizontalCellStyleStrategy(cellStyle, cellStyle)
                        ).build();
                //构建List<List<String>>类型的数据给EasyExcel导出
                for (int i = 0; i < lineCount && row * lineCount + i < count; ++i) {
                    ExcelField field = fieldList.get(row * lineCount + i);
                    cellList.add(field.getShowName() + ":");
                    cellList.add(field.getValue());
                    cellList.add("");
                }
                
                rowList.add(cellList);
                //指定写入的sheet和table
                writer.write(rowList, sheet, table);
                cellList.clear();
                rowList.clear();
            }
        }
    }

    /**
     * 构建列表部分
     */
    private void buildList(List<ExportCustomCommon.ExcelListData> listTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting, List<String> fields) {
        //自定义样式
        WriteCellStyle headStyle = new WriteCellStyle();
        //设置header背景颜色为透明
        headStyle.setFillForegroundColor(IndexedColors.AUTOMATIC.getIndex());
        //水平居中
        headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //上下左右四个边框
        headStyle.setBorderBottom(BorderStyle.THIN);
        headStyle.setBorderTop(BorderStyle.THIN);
        headStyle.setBorderLeft(BorderStyle.THIN);
        headStyle.setBorderRight(BorderStyle.THIN);
        WriteFont writeFont = new WriteFont();
        //字体加粗
        writeFont.setBold(Boolean.TRUE);
        //字号
        writeFont.setFontHeightInPoints((short) 12);
        headStyle.setWriteFont(writeFont);
        WriteCellStyle contentStyle = new WriteCellStyle();
        //内容上下左右四个边框
        contentStyle.setBorderBottom(BorderStyle.THIN);
        contentStyle.setBorderTop(BorderStyle.THIN);
        contentStyle.setBorderLeft(BorderStyle.THIN);
        contentStyle.setBorderRight(BorderStyle.THIN);
        //水平居中
        contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement())
                .needHead(Boolean.TRUE)//需要Header
                .registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))//传入自定义样式
                .includeColumnFiledNames(fields)//选择需要哪些属性
                .build();
        writer.write(listTable, sheet, table);
    }

    //获取该类的所有属性,包括父类中不重名的属性
    private List<Field> getAllField(Class<?> clazz) {
        List<Field> resultList = new ArrayList<>();
        for (List<String> fieldNameList = new ArrayList<>(); 
             clazz != null && !clazz.getName().toLowerCase().equals(ExportCustomCommon.class.getName());
             clazz = clazz.getSuperclass()) {
            List<Field> subFields = Arrays.asList(clazz.getDeclaredFields());
            List<Field> list = subFields.stream()
                    .filter((f) -> !fieldNameList.contains(f.getName()))
                    .collect(Collectors.toList());
            List<String> nameList = list.stream().map(Field::getName).collect(Collectors.toList());
            resultList.addAll(list);
            fieldNameList.addAll(nameList);
        }
        return resultList;
    }
}

接下来构建测试类

package com.kazusa.excel.demo;

import java.io.IOException;
import java.math.BigDecimal;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Description Excel导出测试类
 * @Author kazusa
 */
public class TestMain {

    public static void main(String[] args) throws IOException {
        EasyExcelUtil excelUtil = new EasyExcelUtil();
        //构建标题
        List<String> header = Arrays.asList("标题1", "标题2");
        //构建公共部分
        DepartmentExcel department = new DepartmentExcel();
        department.setCompanyName("TestCompany");
        department.setName("Name");
        department.setFullName("FullName");
        department.setLeaderName("LeaderName");
        department.setBusiness("Business");
        department.setCount(1000L);
        department.setLocation("Location");
        department.setStatus("Status");
        //构建列表部分
        List<StaffExcel> staffs = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            StaffExcel staff = new StaffExcel();
            staff.setId((long) i);
            staff.setName("staff-name" + i);
            staff.setAge((long) (i + 20));
            staff.setPosition("position" + i);
            staff.setSalary(new BigDecimal(i));
            staff.setCode("code" + i);
            staff.setField1("field1" + i);
            staff.setField2("field2" + i);
            staff.setField3("field3" + i);
            staff.setField4("field4" + i);
            staffs.add(staff);
        }
        //构建属性
        List<String> fieldList = Arrays.asList("name", "age", "position", "salary", "code"
                , "field1", "field2", "field3", "field4");
        ExportCustomCommon common = new ExportCustomCommon();
        common.setCommonTable(department);
        common.setHeaderTable(header);
        common.setListTable(staffs);
        excelUtil.export(Files.newOutputStream(Paths.get("C:\\Users\\DELL\\Desktop\\test-excel.xlsx"))
                , common, fieldList);
    }
}

导出结果如下:

image-20230606133919798.png

可以看到有一些问题,就是我们之前说过的自适应列宽的问题,如果不能根据单元格内文本的长度自适应列宽的话,就会出现某些内容显示不全的问题。万幸EasyExcel已经提供了解决方案,我们在EasyExcelUtil的34、221、272行添加了三行代码使导出的Excel可以自适应列宽。

package com.kazusa.excel.demo;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;

import java.beans.PropertyDescriptor;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Description EasyExcel导出工具类
 * @Author Kazusa
 */
public class EasyExcelUtil {
	//在这里新增了一个ThreadLocal类变量,用以存储一个自适应列宽的策略
    private static final ThreadLocal<LongestMatchColumnWidthStyleStrategy> matchStrategy = new ThreadLocal();

    public void export(OutputStream os, ExportCustomCommon params, List<String> fields) {
        //获取标题部分数据
        List<String> headerTable = params.getHeaderTable();
        //获取公共部分数据
        ExportCustomCommon.ExcelCommonData commonTable = params.getCommonTable();
        //获取列表部分数据
        List<? extends ExportCustomCommon.ExcelListData> listTable = params.getListTable();
        //获取列表部分数据的类对象
        Class<? extends ExportCustomCommon.ExcelListData> listDataClass = listTable.get(0).getClass();
		//每次构建一个新的Excel文件时,新建一个自适应列宽策略对象,并存入ThreadLocal中
        LongestMatchColumnWidthStyleStrategy matchWidthStrategy = new LongestMatchColumnWidthStyleStrategy();
        matchStrategy.set(matchWidthStrategy);
        
        //构建EasyExcel Writer对象
        ExcelWriter writer = null;

        try {
            writer = EasyExcel.write(os, listDataClass)//指定写入的流,以及需要EasyExcel自带动态生成的类的类对象
                    .excelType(ExcelTypeEnum.XLSX).build();
            WriteSheet sheet = EasyExcel
                    .writerSheet("sheet1")//指定写入的sheet
                    .needHead(false)//是否需要head,也就是每一个字段对应的字段名,这里为不需要,我们需要EasyExcel去生成字段名的地方只有列表数据部分
                    .build();
            //使用一个计数器记录当前已经写了几个表格
            AtomicInteger tableNoCounting = new AtomicInteger(1);
            //需要知道列数的最大值是多少
            int maxColumn = fields.size();
            this.buildHeader(maxColumn, headerTable, sheet, writer, tableNoCounting);
            this.buildCommon(maxColumn, commonTable, sheet, writer, tableNoCounting);
            this.buildList(listTable, sheet, writer, tableNoCounting, fields);
        } finally {
            assert writer != null;
            // 关闭流
            writer.finish();
            matchStrategy.remove();
        }

    }

    /**
     * 构建标题
     */
    private void buildHeader(int maxColumn, List<String> headerList, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {
        //自定义到处样式
        WriteCellStyle cellStyle = new WriteCellStyle();
        //水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont writeFont = new WriteFont();
        //加粗
        writeFont.setBold(Boolean.TRUE);
        //字体大小
        writeFont.setFontHeightInPoints((short) 15);
        cellStyle.setWriteFont(writeFont);
        //遍历标题部分的List
        for (String header : headerList) {
            WriteTable table = EasyExcel
                    .writerTable(tableNoCounting.get())//指定写入表格的序号,EasyExcel会将多个表格按照序号从小到大、由上到下的排列
                    .needHead(Boolean.FALSE)//也不需要标题
                    .registerWriteHandler(
                            //合并标题的单元格
                            new OnceAbsoluteMergeStrategy(tableNoCounting.get() - 1, tableNoCounting.getAndIncrement() - 1, 0, maxColumn - 1)
                    )
                    //将自定义样式应用与该表
                    .registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle, cellStyle))
                    .build();
            //在这里,由于EasyExcel使用List<List<String>>这样的数据来构建一个表,里面的List<String>表示一行数据。
            //所以我们这里一次只构建一行数据,一行数据中只有一个单元格的数据,一行数据就作为一个表格写入
            //故有几个标题就需要构建几次表格
            List<String> cellList = new ArrayList<>();
            cellList.add(header);
            //因为需要合并单元格到列数最大的单元格处,这里如果不添加空字符串,EasyExcel不会构建单元格,在合并单元格的时候就会报错
            for (int i = 0; i < maxColumn - 1; ++i) {
                cellList.add("");
            }
            List<List<String>> rowList = new ArrayList<>();
            rowList.add(cellList);
            //写入表格
            writer.write(rowList, sheet, table);
        }
    }

    /**
     * 属性实体类
     */
    private static class ExcelField {
        //属性名
        private String fieldName;
        //Excel中显示名
        private String showName;
        //排序
        private int order;
        //属性值
        private Object value;

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getShowName() {
            return showName;
        }

        public void setShowName(String showName) {
            this.showName = showName;
        }

        public int getOrder() {
            return order;
        }

        public void setOrder(int order) {
            this.order = order;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }

    /**
     * 构建公共部分
     */
    private void buildCommon(int maxColumn, ExportCustomCommon.ExcelCommonData commonTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting) {
        if (ObjectUtil.isNotEmpty(commonTable)) {
            //获取公共数据的类对象
            Class<?> commonDataClass = commonTable.getClass();
            //通过类对象获取该类中的所有属性
            List<Field> fields = this.getAllField(commonDataClass);
            List<ExcelField> fieldList = new ArrayList<>();
            try {
                for (Field field : fields) {
                    //如果在Maven打包时报错,Spring项目中可以替换为Spring中的BeanUtils.getPropertyDescriptor()
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), commonDataClass);
                    Assert.notNull(pd, Exception::new);
                    //反射获取读方法
                    Method readMethod = pd.getReadMethod();
                    //读到属性值
                    Object fieldValue = readMethod.invoke(commonTable);
                    //获取属性注解
                    ExcelProperty property = field.getAnnotation(ExcelProperty.class);
                    //获取Excel显示名称
                    String excelFieldName = property.value()[0];
                    //获取Excel中排序
                    int excelFieldOrder = property.order();
                    //构建对象
                    ExcelField excelField = new ExcelField();
                    excelField.setFieldName(field.getName());
                    excelField.setShowName(excelFieldName);
                    excelField.setOrder(excelFieldOrder);
                    excelField.setValue(fieldValue);
                    fieldList.add(excelField);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            //根据Order排序
            fieldList.sort(Comparator.comparingInt(ExcelField::getOrder));
            int count = fieldList.size();
            //计算一行显示属性的个数,除以3是因为一个属性需要属性名--属性值--空字符串三个单元格
            int lineCount = maxColumn / 3;
            //计算行数
            int rows = (count + lineCount - 1) / lineCount;
            List<Object> cellList = new ArrayList<>();
            List<List<Object>> rowList = new ArrayList<>();
            //自定义样式
            WriteCellStyle cellStyle = new WriteCellStyle();
            //水平靠左
            cellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            //遍历所有行,一行作为一个表
            for (int row = 0; row < rows; ++row) {
                WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement())
                        .needHead(Boolean.FALSE)
                        .registerWriteHandler(
                                new HorizontalCellStyleStrategy(cellStyle, cellStyle)
                        )
                    	//添加自适应列宽策略
                        .registerWriteHandler(matchStrategy.get())
                        .build();
                //构建List<List<String>>类型的数据给EasyExcel导出
                for (int i = 0; i < lineCount && row * lineCount + i < count; ++i) {
                    ExcelField field = fieldList.get(row * lineCount + i);
                    cellList.add(field.getShowName() + ":");
                    cellList.add(field.getValue());
                    cellList.add("");
                }

                rowList.add(cellList);
                //指定写入的sheet和table
                writer.write(rowList, sheet, table);
                cellList.clear();
                rowList.clear();
            }
        }
    }

    /**
     * 构建列表部分
     */
    private void buildList(List<? extends ExportCustomCommon.ExcelListData> listTable, WriteSheet sheet, ExcelWriter writer, AtomicInteger tableNoCounting, List<String> fields) {
        //自定义样式
        WriteCellStyle headStyle = new WriteCellStyle();
        //设置header背景颜色为透明
        headStyle.setFillForegroundColor(IndexedColors.AUTOMATIC.getIndex());
        //水平居中
        headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //上下左右四个边框
        headStyle.setBorderBottom(BorderStyle.THIN);
        headStyle.setBorderTop(BorderStyle.THIN);
        headStyle.setBorderLeft(BorderStyle.THIN);
        headStyle.setBorderRight(BorderStyle.THIN);
        WriteFont writeFont = new WriteFont();
        //字体加粗
        writeFont.setBold(Boolean.TRUE);
        //字号
        writeFont.setFontHeightInPoints((short) 12);
        headStyle.setWriteFont(writeFont);
        WriteCellStyle contentStyle = new WriteCellStyle();
        //内容上下左右四个边框
        contentStyle.setBorderBottom(BorderStyle.THIN);
        contentStyle.setBorderTop(BorderStyle.THIN);
        contentStyle.setBorderLeft(BorderStyle.THIN);
        contentStyle.setBorderRight(BorderStyle.THIN);
        //水平居中
        contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteTable table = EasyExcel.writerTable(tableNoCounting.getAndIncrement())
                .needHead(Boolean.TRUE)//需要Header
            	//添加自适应列宽策略
                .registerWriteHandler(matchStrategy.get())
                .registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))//传入自定义样式
                .includeColumnFiledNames(fields)//选择需要哪些属性
                .build();
        writer.write(listTable, sheet, table);
    }

    //获取该类的所有属性,包括父类中不重名的属性
    private List<Field> getAllField(Class<?> clazz) {
        List<Field> resultList = new ArrayList<>();
        for (List<String> fieldNameList = new ArrayList<>();
             clazz != null && !clazz.getName().toLowerCase().equals(ExportCustomCommon.class.getName());
             clazz = clazz.getSuperclass()) {
            List<Field> subFields = Arrays.asList(clazz.getDeclaredFields());
            List<Field> list = subFields.stream()
                    .filter((f) -> !fieldNameList.contains(f.getName()))
                    .collect(Collectors.toList());
            List<String> nameList = list.stream().map(Field::getName).collect(Collectors.toList());
            resultList.addAll(list);
            fieldNameList.addAll(nameList);
        }
        return resultList;
    }
}

看完自适应列宽部分代码的小伙伴应该会有一个疑问,为什么需要使用一个线程本地变量来存储这个策略对象?

就像我们之前说过的该代码导出Excel表格使用了多个Table组合成一个Excel的形式,那么每个table中的每一个单元格都会影响跟它同列但是属于其他Table的列宽,查看LongestMatchColumnWidthStyleStrategy的源码可以看到,其内部有一个cache Map,作用就是存储该列的最大长度,从而保证该列上的每一个单元格中的内容都可以显示完全,所以在这里我们需要保证一个Excel文件导出过程中使用的LongestMatchColumnWidthStyleStrategy对象为同一个对象。当然不一定使用线程本地变量来存储,也可以使用其他的方法。

package com.alibaba.excel.write.style.column;

import ............

public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {

    private static final int MAX_COLUMN_WIDTH = 255;

    //这里就是存储某一列最大列宽的Map,如果构建同一个Excel时使用了不同的Map,最终的结果会出现错误
    private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(8);

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head,
        Integer relativeRowIndex, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (!needSetWidth) {
            return;
        }
        Map<Integer, Integer> maxColumnWidthMap = cache.get(writeSheetHolder.getSheetNo());
        if (maxColumnWidthMap == null) {
            maxColumnWidthMap = new HashMap<Integer, Integer>(16);
            cache.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
        }
        Integer columnWidth = dataLength(cellDataList, cell, isHead);
        if (columnWidth < 0) {
            return;
        }
        if (columnWidth > MAX_COLUMN_WIDTH) {
            columnWidth = MAX_COLUMN_WIDTH;
        }
        Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
        if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
            maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
        }
    }

    private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        }
        CellData cellData = cellDataList.get(0);
        CellDataTypeEnum type = cellData.getType();
        if (type == null) {
            return -1;
        }
        switch (type) {
            case STRING:
                return cellData.getStringValue().getBytes().length;
            case BOOLEAN:
                return cellData.getBooleanValue().toString().getBytes().length;
            case NUMBER:
                return cellData.getNumberValue().toString().getBytes().length;
            default:
                return -1;
        }
    }
}

最后导出的结果如图:

image-20230606135154929.png

最后,本代码只实现了列表部分可以选择字段导出,在公共部分没有办法选择指定字段导出,但是由于整个Excel都是动态生成的,在构建公共部分表格时只需要把公共部分的字段自己做一个筛选即可。

标签:String,自定义,Excel,EasyExcel,List,private,import,new
From: https://blog.51cto.com/u_16259135/7446364

相关文章

  • kubernetes部署mongoDB 单机版 自定义配置文件、密码、日志路径等
    来源:https://aijishu.com/a/1060000000097166官方镜像地址: https://hub.docker.com/_/mong...docker版的mongo移除了默认的/etc/mongo.conf,修改了db数据存储路径为/data/db.创建configmap配置,注意不能加fork=true,否则Pod会变成Completed。apiVersion:v1kind:ConfigMap......
  • Excel
     =RANDBETWEEN(1,100)&INDEX($G$1:$G$4,RANDBETWEEN(1,4))&RANDBETWEEN(1,100)&"="在上一步公式运算后连接一个1到100的随机数,并且再连接一个“=”符号。公式完成后,点选此单元格,将鼠标置于单元格的右下角,待指针变成十字时,按住鼠标左键拖动填充需求单元格,注意不要超过辅助栏哦......
  • jwt自定义表签发、jwt多方式登录(auth的user表)
    jwt自定义表签发继承AbstractUser,直接使用自动签发token纯自己写的用户表,需要自己签发关于签发:1、通过user生成payload,jwt提供的方法,字段必须是username,传入user,返回payload2、生成token,jwt提供的方法,把payload放入token自定义表大致流程:先创建表需要什么建什......
  • drf-jwt自定义表签发、多方式登录
    一、jwt自定义表签发自定义表签发,用的是自己定义的表1.models.py:-注意点:因为视图中使用了drf-jwt的自动签发,所以用户名必须为usernamefromdjango.dbimportmodels#自定义签发用的是自定义的user表#注意点:使用drf-jwt的自动签发,用户名必须是usernameclassUser(......
  • vue中使用xlsx插件前端读取解析excel文件
    问题描述工作中一般都是后端去解析excel数据,前端使用上传组件去将excel组件丢给后端,后端使用一些插件去解析excel(比如hutool工具类)不过有些情况下,前端也需要去做一些excel的解析,比如产品经理说,在上传excel文件之前,要做一个excel的图表化预览审核查看啥的,没问题的话,再丢给后端去......
  • nopi 2.6.1 读word docx,写Excel xsls 源代码例子
    ///<summary>///获取.docx文件内容,使用NPOI.XWPF插件解析///</summary>///<paramname="strFilePath">文件路径</param>///<returns></returns>publicstringGetDocxContent(stri......
  • 【心得】TP6,使用phpspreadsheet库进行EXCEL的数据导入导出
    在日常开发中,我们会遇到大批量的数据导出以及导入,之前的PHP旧库现在已经停更了,如下提示:composerrequirephpoffice/phpexcelPackagephpoffice/phpexcelisabandoned,youshouldavoidusingit.Usephpoffice/phpspreadsheetinstead.phpoffice/phpexcel包已废弃,应避免......
  • jwt自定义表签发、jwt多方式登录(auth的user表)
    jwt自定义表签发models.pyfromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportAbstractUser#继承AbstractUser直接使用自动签发token#纯自己写的用户表,需要自己签发classUser(models.Model):username=models.CharField(max_length=32)......
  • jwt自定义表签发, jwt 多方式登录(auth的user表)
    1jwt自定义表签发1.1models.pyfromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportAbstractUser#继承AbstractUser直接使用自动签发token#纯自己写的用户表,需要自己签发classUser(models.Model):username=models.CharField(max_leng......
  • openpyxl识别excel中的图片并保存
    fromopenpyxlimportload_workbookworkbook=load_workbook(r"C:\Users\ben_ma\Desktop\新建XLSX工作表.xlsx")num=1forsheet_nameinworkbook.sheetnames:worksheet=workbook[sheet_name]forimageinworksheet._images:img_da......