2022通用poi导出excel,5.x版本

功能描述

数据集即查询出来的List,泛型必须是对象List<对象类>,而不是List>
①支持多级标题,支持相邻标题名称相同自动合并。
②支持导出Excel自定义顺序
③支持字段类型为日期,大字段,BigDecimal自定义处理,例如日期格式化等。
④支持数据与枚举映射,场景:数据库存储值为数字,展示需要中文
⑤支持多个不同数据集导出到同一excel文件中
⑥支持若干自定义单元格宽度,序号等,详情实现见注解类【ExportField.java】

1.效果截图

普通导出

普通多级标题String[][] title1 = {{"用户导出"}, {"测试", "测试","数据", "0000", "0000"}, {"分隔符"},{"用户名", "昵称","性别", "价格", "数字"}};
增强导出

增强导出

2.实现思路设计

常见的导出,有含有标题的,不含有标题的,多级标题的,标题希望合并的
因此采用二维数组进行标题设计,并且对内容进行计算合并
作者太懒,暂不想写,先即粘即用。

3.引入依赖

            
                org.apache.poi
                poi
                5.1.0
            

            
                org.apache.poi
                poi-ooxml
                5.1.0
            
工程路径自行调整

4.核心代码

①接口:对于外部调用,只需要数据集和对应的字段名

package com.erhya.admin.service.poi;

import java.util.List;

/**
 * 抽象接口,为扩展poi和jxl等其他导出技术
 */
public interface ExcelTemplate {

    /**
     * 抽象模板方法
     * @param exportList 导出的数据集合
     * @param showFiles 导出字段属性名
     */
    void execute(List exportList, String... showFiles);

}

②接口:数据集中的数字转换为字典值(例如:1对应展示男,0对应展示女)

package com.erhya.admin.service.poi;

/**
 * 导出功能的枚举字典
 */
public interface ExportDict {

    /**
     * 根据key找到值
     * @param key key==字段值
     */
    String getVal(String key);

}

注解类

package com.erhya.admin.service.poi.annotation;

import com.erhya.admin.service.poi.ExportDict;

import java.lang.annotation.*;


/**
 * 导出信息配置注解
 * @author 李林
 * @date 2022-01-22
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExportField {

    /**
     * 是否为序号字段
     * 为了应对导出需要从1开始计数,默认不需要
     */
    boolean isNo() default false;

    /**
     * 暂时没用,作为标记
     * 单元格顶部标题,当只需要一层标题的时候,仅加入此项配置即可,就不需要传二维数组的标题了
     */
    String fieldName() default "";

    /**
     * 默认单元格宽度,256为一个字节宽度
     */
    int width() default 18;

    /**
     * 当字段为日期类型时,希望的转换格式
     */
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";

    /** 数据字典 */
    Class[] dict() default {};

    int scale() default 2;

}

抽象类(反射获取属性值)

package com.erhya.admin.service.poi;

import com.erhya.admin.service.poi.annotation.ExportField;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Date;

/**
 * 抽象层-公共方法区
 *
 * @param  数据泛型
 */
public abstract class AbstractExcelTemplate implements ExcelTemplate {

    private Integer scale = 2;

    /**
     * 接口默认实现方法,获取反射类字段
     *
     * @param fieldName 字段名
     * @param clazz     反射类
     * @return 字段信息
     */
    protected final Field getField(String fieldName, Class clazz) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return field;
    }

    /**
     * 接口默认实现方法,获取反射类字段值
     * @return 字段值
     */
    protected final String getFieldVal(Field field, Object obj, Integer counter) {
        String fieldType = field.getGenericType().getTypeName();
        ExportField fieldAnnotation = field.getAnnotation(ExportField.class);
        if (fieldAnnotation != null) {
            if (fieldAnnotation.isNo()) {
                return String.valueOf(counter);
            }
        }

        String fieldVal = null;
        try {
            Object o = field.get(obj);
            if (o != null) {
                if (StringUtils.isNotEmpty(fieldType)) {
                  // 预留特殊字段处理,从注解类中获取属性
                    switch (fieldType) {
                        case "java.util.Date":
                            fieldVal = DateFormatUtils.format((Date) o, fieldAnnotation == null ? "yyyy-MM-dd HH:mm:ss" : fieldAnnotation.dateFormat());
                            break;
                        case "java.sql.Clob":

                            break;
                        case "java.math.BigDecimal":
                            fieldVal = ((BigDecimal) o).setScale(fieldAnnotation == null ? 2 : fieldAnnotation.scale(), BigDecimal.ROUND_CEILING).toPlainString();
                            break;
                        default:
                            fieldVal = String.valueOf(o);
                    }
                }
            }

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        Class[] dict = fieldAnnotation.dict();
        if (dict.length > 0) {
            fieldVal = getDictVal(dict[0], fieldVal);
        }

        if (StringUtils.isEmpty(fieldVal)) {
            fieldVal = "";
        }
        return fieldVal;
    }

    /**
     * 反射-从枚举类中获取对应的字典值
     *
     * @param emum 枚举类
     * @param key  字典(字段)值
     * @return val
     */
    public static  String getDictVal(Class emum, String key) {
        String invoke = null;
        try {
            // getVal为接口类:InterfaceEnum中的方法
            Method method = emum.getDeclaredMethod("getVal", String.class);
            T[] enumConstants = emum.getEnumConstants();
            Object mevoke = method.invoke(enumConstants[0], key);
            if (method != null) {
                invoke = mevoke.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return invoke;
    }
}

POI实现

package com.erhya.admin.service.poi;

import com.erhya.admin.service.poi.annotation.ExportField;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基于POI实现的模板方法
 * 使用方法: PoiExcelTemplate<对象> poi = PoiExcelTemplate<对象>(若干参数);
 *           poi.execute(参数);
 * @param  数据泛型
 * @author lilin  模板模式 + 泛型 + 反射
 */
public class PoiExcelTemplate extends AbstractExcelTemplate implements ExecuteChain {

    /** excel的文档对象 */
    private HSSFWorkbook workbook;
    /** excel的表单 */
    private HSSFSheet sheet;
    /** excel的表单名称 */
    private String sheetName;

    /** 标题 */
    private String[][] title;
    /** 标题中最长的一行 */
    private Integer titleMaxLen;
    /** 标题栏的合并列 */
    private List shellMerge;
    /** 默认单元格宽度 */
    private static final Integer DEFAULT_WIDTH = 18;

    /** 导出文件名 */
    private String fileName;
    /** 标题栏 样式 */
    private CellStyle titleStyle;
    /** 数据栏 样式 */
    private CellStyle dataStyle;

    /** 列表数据 */
    private List data;
    /** 列表数据展示字段 */
    private String[] showFiles;
    /** 当前数据行(标题行数+已写入数据行) */
    private Integer startIndex;
    /** 序号计数器 */
    private AtomicInteger center;

    private HttpServletResponse response;

    public PoiExcelTemplate(String fileName, String[][] title, String sheetName, HttpServletResponse response) {
        this.fileName = fileName;
        this.title = title;
        this.sheetName = sheetName;
        this.response = response;
    }

    /**
     * 初始化方法
     * @param workbook 工作簿对象
     */
    private void init(HSSFWorkbook workbook){

        if (workbook == null){
            workbook = new HSSFWorkbook();
        }

        this.workbook = workbook;
        this.titleStyle = buildTitleStyle();

        if (StringUtils.isEmpty(sheetName)){
            sheetName = "Sheet";
        }

        this.sheet = workbook.createSheet(sheetName);

        this.shellMerge = new ArrayList<>();
        this.center = new AtomicInteger(0);
        this.startIndex = this.title == null ? 0 : this.title.length;
        this.dataStyle = buildColumStyle();
    }

    @Override
    public void execute(List list, String... showFiles){
        init(workbook);
        titleInit();
        this.dataStyle = buildColumStyle();
        buildList(list, showFiles);
        exportList();
    }

    @Override
    public void unExecute(List list, String... showFiles){
        this.data = list;
        this.showFiles = showFiles;
    }

    @Override
    public void china(String fileName, Boolean flag){
        if (StringUtils.isNotEmpty(fileName)){
            this.fileName = fileName;
        }
        init(workbook);
        titleInit();
        this.dataStyle = buildColumStyle();
        buildList(data, showFiles);
        if (!flag){
            exportList();
        }
    }

    private void titleInit(){
        if (this.title == null || this.title.length == 0){
            return;
        }

        if (titleMaxLen == null){
            titleMaxLen = 0;
        }

        // 计算最长的一行
        for (String[] t1 : title) {
            int length = t1.length;
            titleMaxLen = Math.max(length, titleMaxLen);
        }

        initMergeTitle();

        for (CellRangeAddress cellAddresses : shellMerge) {
            sheet.addMergedRegion(cellAddresses);
        }

    }

    private void initMergeTitle(){
        String tag = null;
        for (int i = 0; i < title.length; i++) {
            HSSFRow row = sheet.createRow(i);
            String[] t2 = title[i];
            if (t2.length == 1){
                HSSFCell cell = row.createCell(0);
                cell.setCellStyle(this.titleStyle);
                cell.setCellType(CellType.STRING);
                cell.setCellValue(t2[0]);
                shellMerge.add(new CellRangeAddress(i, i, 0, titleMaxLen-1));
            } else {
                Integer mergeCell = 0;
                for (int j = 0; j < t2.length; j++) {
                    HSSFCell cell = row.createCell(j);
                    String cell2 = t2[j];
                    if (tag == null || j == 0){
                        tag = cell2;
                    }

                    cell.setCellStyle(this.titleStyle);
                    cell.setCellType(CellType.STRING);
                    cell.setCellValue(cell2);

                    if (tag.equals(cell2)){
                        mergeCell++;
                    } else {
                        if (mergeCell > 1){
                            shellMerge.add(new CellRangeAddress(i, i, j-mergeCell, j-1));
                        }
                        tag = cell2;
                        mergeCell = 1;
                    }

                    // 最后一行合并方式和其他不一样
                    if (j == t2.length-1 && tag.equals(cell2)){
                        if (mergeCell > 1){
                            shellMerge.add(new CellRangeAddress(i, i, j-mergeCell+1, j));
                        }
                    }
                }
            }
        }
    }

    protected void buildList(List list, String... showFiles){
        for (T t : list) {
            Class clazz = t.getClass();
            HSSFRow row = this.sheet.createRow(startIndex);
            int i = 0;
            int num =  this.center.addAndGet(1);

            for (String showFile : showFiles) {
                int width = DEFAULT_WIDTH;
                Field field = getField(showFile, clazz);
                String fieldVal = getFieldVal(field, t, num);

                ExportField fieldAnnotation = field.getAnnotation(ExportField.class);
                if (fieldAnnotation != null){
                    width = fieldAnnotation.width();
                }

                HSSFCell cell = row.createCell(i);
                this.sheet.setColumnWidth(i, width * 256);
                cell.setCellStyle(dataStyle);
                cell.setCellType(CellType.STRING);
                cell.setCellValue(fieldVal);
                i++;
            }
            startIndex++;
        }
    }

    /**
     * 可重写的标题样式
     */
    protected HSSFCellStyle buildTitleStyle(){
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直对齐
        cellStyle.setBorderBottom(BorderStyle.THIN); //下边框
        cellStyle.setBorderLeft(BorderStyle.THIN); //左边框
        cellStyle.setBorderRight(BorderStyle.THIN); //右边框
        cellStyle.setBorderTop(BorderStyle.THIN); //上边
        cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); //背景色
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        HSSFFont headerFont = (HSSFFont) workbook.createFont(); // 创建字体样式
        headerFont.setBold(true); //字体加粗
        headerFont.setFontName("仿宋"); // 设置字体类型
        headerFont.setFontHeightInPoints((short) 12); // 设置字体大小
        cellStyle.setFont(headerFont); // 为标题样式设置字体样式
        return cellStyle;
    }

    /**
     * 可重写的单元格样式
     */
    protected HSSFCellStyle buildColumStyle(){
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.LEFT);//水平居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直对齐
        cellStyle.setBorderBottom(BorderStyle.THIN); //下边框
        cellStyle.setBorderLeft(BorderStyle.THIN); //左边框
        cellStyle.setBorderRight(BorderStyle.THIN); //右边框
        cellStyle.setBorderTop(BorderStyle.THIN); //上边
        HSSFFont headerFont = (HSSFFont) workbook.createFont(); // 创建字体样式
        headerFont.setBold(false); //字体加粗
        headerFont.setFontName("仿宋"); // 设置字体类型
        headerFont.setFontHeightInPoints((short) 11); // 设置字体大小
        cellStyle.setFont(headerFont); // 为标题样式设置字体样式
        return cellStyle;
    }

    private void exportList(){
        response.reset();
        response.setContentType("text/html;charset=utf-8");
        try {
            response.setHeader("Content-disposition", "attachment; filename="+ new String((fileName).getBytes("gbk"),"iso8859-1") +".xls");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        BufferedOutputStream bufferedOutPut = null;
        ServletOutputStream output = null;
        try {
            output = response.getOutputStream();
            bufferedOutPut = new BufferedOutputStream(output);
            bufferedOutPut.flush();
            workbook.write(bufferedOutPut);
        } catch (IOException e){
            e.printStackTrace();
        } finally{
            if (bufferedOutPut != null){
                try {
                    bufferedOutPut.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (output != null){
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public HSSFWorkbook getWorkbook() {
        return workbook;
    }

    public void setWorkbook(HSSFWorkbook workbook) {
        this.workbook = workbook;
    }
}

以下为增强处理,可不引入(多个数据集导出到同一Excel文件中)

③接口:导出增强

package com.erhya.admin.service.poi;

import java.util.List;

/**
 * Excel增强处理-链式处理
 * 责任链模式
 * @param  泛型
 */
public interface ExecuteChain {

    /**
     * 设置数据集和展示字段
     * @param list 数据集
     * @param showFiles 展示字段
     */
    void unExecute(List list, String... showFiles);

    /**
     * 执行链方法
     * @param fileName 文件名
     * @param flag 标识:是否含有下一个链
     */
    void china(String fileName, Boolean flag);

}

package com.erhya.admin.service.poi.chain;

/**
 * 导出链
 */
public class ExcelExecuteChain {

    /** 文件名 */
    private String fileName;
    /** 头节点 */
    private ExcelChina head;
    /** 下一节点 */
    private ExcelChina next;

    public ExcelExecuteChain(String fileName) {
        this.fileName = fileName;
    }

    /**
     * 添加链条
     * @param excelChain 链条对象
     */
    public void addChain(ExcelChina excelChain){

        excelChain.setSuccessor(null);

        // 首次设置头节点
        if (head == null){
            this.head = excelChain;
            this.next = excelChain;
            return;
        }

        this.next.setSuccessor(excelChain);
        this.next = excelChain;
    }

    /**
     * 执行链条
     */
    public void executeChain(){
        if (this.head != null){
            try {
                this.head.execute(fileName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

package com.erhya.admin.service.poi.chain;

import lombok.Data;

@Data
public abstract class ExcelChina {

    protected ExcelChina successor;
    protected Object workBook;

    public abstract void execute(String fileName) throws Exception;
}
package com.erhya.admin.service.poi.chain.poi;

import com.erhya.admin.service.poi.PoiExcelTemplate;
import com.erhya.admin.service.poi.chain.ExcelChina;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

public class PoiNewSheetExcelExecute extends ExcelChina {

    private PoiExcelTemplate excelTemplate;

    public PoiNewSheetExcelExecute(PoiExcelTemplate excelTemplate) {
        this.excelTemplate = excelTemplate;
    }

    @Override
    public void execute(String fileName) throws Exception{
        HSSFWorkbook workbook = excelTemplate.getWorkbook();
        if (workbook == null && super.workBook == null){
            workbook = new HSSFWorkbook();
            super.workBook = workbook;
        }

        excelTemplate.setWorkbook((HSSFWorkbook) workBook);

        if (successor != null ){

            excelTemplate.setWorkbook((HSSFWorkbook) workBook);
            excelTemplate.china(fileName, true);

            successor.setWorkBook(super.workBook);
            successor.execute(fileName);
            return;
        }

        excelTemplate.china(fileName, false);

    }

}


使用方法

普通导出
1.返回数据集合List
2. PoiExcelTemplate template = new PoiExcelTemplate<>("文件名", 二维数据标题, "sheet名", response);
3.调用方法;template.execute(users, "展示属性1", "展示属性2", "展示属性3"...);


示例

新建测试类

package com.erhya.admin.dto;

import com.erhya.admin.service.poi.annotation.ExportField;
import com.erhya.admin.service.poi.enums.ExportEnum;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @Author: lilin
 * @Date: 2022/4/16
 */
@Data
public class User {

    @ExportField(width = 18)
    private String userName;

    @ExportField(width = 18)
    private String nick;

    @ExportField(width = 18, dict = ExportEnum.class)
    private String sex;

    @ExportField(width = 18)
    private BigDecimal doub;

    @ExportField(width = 22)
    private BigDecimal font;
}

package com.erhya.admin.dto;

import com.erhya.admin.service.poi.annotation.ExportField;
import lombok.Data;

/**
 * @Author: lilin
 * @Date: 2022/4/16
 */
@Data
public class Car {

    @ExportField(isNo = true, width = 60)
    private String no;

    @ExportField(width = 18)
    private String name;

    @ExportField(width = 30)
    private String price;

}
package com.erhya.admin.service.poi.enums;

import com.erhya.admin.service.poi.ExportDict;

public enum ExportEnum implements ExportDict {
    A("1", "男的"),
    C("0", "女的");

    private String key;
    private String value;

    ExportEnum(String key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public String getVal(String key) {
        ExportEnum[] values = ExportEnum.values();
        for (ExportEnum value : values) {
            if (value.key.equals(key)) {
                return value.value;
            }
        }
        return "其他";
    }
}

①常用版(查询数据,导出)

@GetMapping("/poi2")
    public void poi(HttpServletResponse response) {
        List users = new ArrayList<>(3);
        User user1 = new User();
        User user2 = new User();
        User user3 = new User();

        user1.setUserName("小明");
        user2.setUserName("小红");
        user3.setUserName("小华");

        user1.setSex("1");
        user2.setSex("0");
        user3.setSex("奥利给");

        user1.setNick("小明");
        user2.setNick("小红");
        user3.setNick("小华");

        user1.setDoub(new BigDecimal("999999999.25"));
        user2.setDoub(new BigDecimal("5566998899.55"));
        user3.setDoub(new BigDecimal("100.10"));

        users.add(user1);
        users.add(user2);
        users.add(user3);

        String[][] title1 = {{"用户导出"}, {"用户名", "昵称","性别", "价格", "数字"}};

        PoiExcelTemplate template = new PoiExcelTemplate<>("文件名", title1, "shell", response);
        template.execute(users, "userName", "nick", "sex", "doub", "font");

    }

②增强版(多数据集导出到同一Excel)

@GetMapping("/poi")
    public void poi(HttpServletResponse response) {
        List users = new ArrayList<>(3);
        User user1 = new User();
        User user2 = new User();
        User user3 = new User();

        user1.setUserName("小明");
        user2.setUserName("小红");
        user3.setUserName("小华");

        user1.setSex("男");
        user2.setSex("女");
        user3.setSex("女3");

        user1.setNick("小明");
        user2.setNick("小红");
        user3.setNick("小华");

        user1.setDoub(new BigDecimal(999999999.25));
        user2.setDoub(new BigDecimal(5566998899.55));
        user3.setDoub(new BigDecimal(100.10));
//        user3.setFont(new BigDecimal(987654321000L));

        users.add(user1);
        users.add(user2);
        users.add(user3);

        List cars = new ArrayList<>();

        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();
        Car car4 = new Car();

        car1.setName("兰博基尼");
        car2.setName("奥迪");
        car3.setName("宇通客车");
        car4.setName("玛莎拉蒂");

        car1.setPrice("5000");
        car2.setPrice("6000");
        car3.setPrice("1000");
        car4.setPrice("2000");

        cars.add(car1);
        cars.add(car2);
        cars.add(car3);
        cars.add(car4);

        ExcelExecuteChain chain = new ExcelExecuteChain("小米");

        String[][] title1 = {{"用户导出"}, {"用户名", "昵称","性别", "价格", "数字"}};
        String[][] title2 = {{"小汽车导出"}, {"名称", "价格"}};

        PoiExcelTemplate template = new PoiExcelTemplate<>("文件名", title1, "shell", response);
        template.unExecute(users, "userName", "nick", "sex", "doub", "font");

        PoiExcelTemplate template2 = new PoiExcelTemplate<>("文件名", title2, "shell2", response);
        template2.unExecute(cars,"price","price");

        PoiExcelTemplate template3 = new PoiExcelTemplate<>("文件名", title2, "shell3", response);
        template3.unExecute(cars,"name","price");

        PoiNewSheetExcelExecute chainA = new PoiNewSheetExcelExecute<>(template);
        chain.addChain(chainA);

        PoiNewSheetExcelExecute chainB = new PoiNewSheetExcelExecute<>(template2);
        chain.addChain(chainB);

        PoiNewSheetExcelExecute chainC = new PoiNewSheetExcelExecute<>(template3);
        chain.addChain(chainC);

        chain.executeChain();

    }

PS:由于有些类或抽象结构是后加的,所以结构上可能有不合理之处,勿喷。

嫌复制粘贴麻烦的,联系作者提供完整版压缩包,希望实现JXL的,也可联系。本文没有放JXL的实现。

你可能感兴趣的:(2022通用poi导出excel,5.x版本)