Java POI dynamically selects the exported field column

Business scenario: the export button of a page, because there are many fields, and the customer wants to export different fields for easy observation every time, so it is necessary to dynamically select the Excel export column

The method used in this article is used 自定义注解 + POI to implement it.
Let’s not talk much, let’s see the effect first. If it doesn’t meet your effect, just delete it.

1. Display of renderings

1.1 postman analog front-end parameter transfer

The following field values ​​are passed to represent the columns that need to be exported
insert image description here

1.2 Exported excel renderings

insert image description here

Let's briefly talk about the code idea and process:
1. The fields that need to be exported in the front-end parameter transfer, and the excel headers corresponding to the fields
2. The fields in the first step of the loop, draw the cells of each row according to the found data set
3. The key Step 1: Find the corresponding relationship between the front-end transmission and participation in our data set (the list of data entities found from mysql), and draw each cell according to this step.
In this article, the reflection of the entity class is used to obtain the corresponding relationship. For details, please refer to the code at "ExportExcel.getValues(Object rowData, String[] exportFieldArr)", the core code!

2. Code display

2.1. Entity class

2.1.1 dto for receiving front end


/**
 * <p>@Description: 用于接收前端传参的dto</p >
 * <p>@param </p >
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DynamicExportRequest {
    
    

    //@ApiModelProperty("前端选择的需要导出的字段")
    private GoodsExportBean goodsExportBean;
    // 分页参数
    private int pageIndex;
    private int pageSize;
}

2.1.2 mysql entity class


package com.lzq.learn.test.动态选择列导出excel;

import com.lzq.learn.anno.DynamicExport;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * <p>@Description: 实体类</p >
 * <p>@date 18:58 18:58</p >
 * @author 吴巴格
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {
    
    

    private Integer id;

    private String name;

    private BigDecimal price;

    private String type;

    private BigDecimal quantity;

    private LocalDateTime createTime;

}

2.1.3 dto used to receive front-end fields that need to be exported

package com.lzq.learn.test.动态选择列导出excel.byExportBean;

import com.lzq.learn.anno.DynamicExport;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * <p>@Description: 用于接收前端哪些需要导出字段的dto</p >
 * <p>@date 9:28 9:28</p >
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GoodsExportBean {
    
    


    @DynamicExport(sort = 1, name = "id")
    private String id;

    @DynamicExport(sort = 2, name = "姓名")
    private String name;

    @DynamicExport(sort = 3, name = "价格")
    private String price;

    @DynamicExport(sort = 4, name = "类型")
    private String type;

    @DynamicExport(sort = 5, name = "库存")
    private String quantity;

    @DynamicExport(sort = 6, name = "创建时间")
    private String createTime;
}

2.1.4 Optional field entities for the front end

package com.lzq.learn.test.动态选择列导出excel;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * <p>@Description: 用于返回给前端的可选择导出列 </p >
 * <p>@date 14:23 14:23</p >
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicField {
    
    

    //("字段英文名")
    private String englishFiledName;

    //("excel显示表头中文名")
    private String chineseTitleName;

    public static DynamicField build(String englishFiledName, String chineseTitleName) {
    
    
        DynamicField dynamicField = new DynamicField();
        dynamicField.setEnglishFiledName(englishFiledName);
        dynamicField.setChineseTitleName(chineseTitleName);
        return dynamicField;
    }
}

2.2 Tool classes and annotations

2.2.1 Custom annotations

package com.lzq.learn.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * <p>@Description: 动态导出字段, 将可选字段加上该注解</p >
 * <p>@date 10:26 10:26</p >
 */
@Target({
    
    TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface DynamicExport {
    
    

    int sort() default 0;

    String name() default "";
}

2.2.2 Dynamic field column tool class

package com.lzq.learn.test.动态选择列导出excel;

import com.lzq.learn.anno.DynamicExport;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>@Description: 可选择动态字段列工具类</p >
 * <p>@date 14:26 14:26</p >
 */
public class DynamicFieldUtil {
    
    

    /**
     * <p>@Description: 带有 @DynamicExport 注解的实体类,利用反射返回可选择动态列</p >
     * <p>@param [object]</p >
     * <p>@return java.util.List<com.hjza.environment.response.export.DynamicField></p >
     * <p>@throws </p >
     */
    public static <T> List<DynamicField> exportPageListDynamicFieldList(Class<T> objectClass){
    
    
        Field[] declaredFields = objectClass.getDeclaredFields();
        List<DynamicField> dynamicFieldList = new ArrayList<>();
        for (Field declaredField : declaredFields) {
    
    
            declaredField.setAccessible(true);
            DynamicExport dynamicExportAnnotation = declaredField.getAnnotation(DynamicExport.class);
            String chineseTitle = dynamicExportAnnotation.name();
            dynamicFieldList.add(DynamicField.build(declaredField.getName(), chineseTitle));
        }
        return dynamicFieldList;
    }

}

2.2.3 Core POI tools

package com.lzq.learn.test.动态选择列导出excel.byExportBean;

import cn.hutool.core.util.StrUtil;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
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.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * <p>@Description: 将 Apache POI 的一些常用api进行封装</p >
 */
public class ExportExcel {
    
    



    /**
     * <p>@Description: 根据 所选动态列,以及数据集进行导出excel</p >
     * <p>@param [exportBeanFrom, dataList, fileName]</p >
     * <p>@return void</p >
     * <p>@throws </p >
     */
    public static <T> void exportByTitleAndData(Object exportBeanFrom, List<T> dataList, String fileName) {
    
    
        // 处理参数: 需要导出的英文字段名 exportFieldArr, 中文表头 title
        Map<String, Object> beanExportFieldMap = ExportExcelSortUtil.filterAndSort(exportBeanFrom);
        Map<String, String[]> fieldAndTitleMap = ExportExcelSortUtil.getExportFieldAndExportTitle(beanExportFieldMap);
        String[] title = ExportExcelSortUtil.getExportTitleArr(fieldAndTitleMap);
        String[] exportFieldArr = ExportExcelSortUtil.getExportFieldArr(fieldAndTitleMap);
        // 根据参数 生成工作簿,并写入文件流
        if (title.length > 0 && exportFieldArr.length > 0) {
    
    
            Workbook workbook = ExportExcel.getWorkbook(dataList, exportFieldArr, title);
            ExportExcel.writeToResponse(workbook, fileName);
        }
    }

    /**
     * <p>@Description: 将文件流写会回 response 中</p >
     * <p>@param [workbook, fileName]</p >
     * <p>@return void</p >
     * <p>@throws </p >
     */
    public static void writeToResponse(Workbook workbook, String fileName) {
    
    
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        OutputStream outputStream = null;
        try {
    
    
            outputStream = response.getOutputStream();
            workbook.write(outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * <p>@Description: 根据 数据集、导出列、表头汉字 创建工作簿</p >
     * <p>@param [dataSet 数据集, exportFieldArr 需要导出的字段列, titles 导出列对应的中文表头]</p >
     * <p>@return org.apache.poi.xssf.usermodel.XSSFWorkbook</p >
     * <p>@date 15:19 15:19</p >
     */
    public static <T> XSSFWorkbook getWorkbook(Collection<T> dataSet, String[] exportFieldArr, String[] titles) {
    
    
        // 校验变量和预期输出excel列数是否相同
        if (exportFieldArr.length != titles.length) {
    
    
            return null;
        }
        // 存储每一行的数据
        List<String[]> list = new ArrayList<>();
        for (Object obj : dataSet) {
    
    
            // 获取到每一行的属性值数组
            list.add(getValues(obj, exportFieldArr));
        }
        return getWorkbook(titles, list);
    }

    public static XSSFWorkbook getWorkbook(String[] titles, List<String[]> list) {
    
    
        // 定义表头
        String[] title = titles;
        // 创建excel工作簿
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建工作表sheet
        XSSFSheet sheet = workbook.createSheet();
        // 创建第一行
        XSSFRow row = sheet.createRow(0);
        XSSFCell cell = null;
        // 插入第一行数据的表头
        row.setHeight((short) (24 * 20));
        CellStyle headerCommonStyle = getHeaderCommonStyle(workbook);
        for (int i = 0; i < title.length; i++) {
    
    
            cell = row.createCell(i);
            cell.setCellValue(title[i]);
            cell.setCellStyle(headerCommonStyle);
        }
        // 数据行渲染
        CellStyle bodyStyle = getBodyStyle(workbook);
        int idx = 1;
        for (String[] strings : list) {
    
    
            XSSFRow nrow = sheet.createRow(idx++);
            XSSFCell ncell = null;
            for (int i = 0; i < strings.length; i++) {
    
    
                ncell = nrow.createCell(i);
                ncell.setCellValue(strings[i]);
                ncell.setCellStyle(bodyStyle);
            }
        }
        // 设置固定列宽
        setColumnWidth(titles, sheet);
        return workbook;
    }

    // 设置固定列宽
    public static void setColumnWidth(String[] titles, Sheet sheet) {
    
    
        for (int i = 0; i < titles.length; i++) {
    
    
            sheet.setColumnWidth(i, 20 * 256);
        }
    }

    private static CellStyle getHeaderCommonStyle(Workbook workbook) {
    
    
        CellStyle header = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(Boolean.TRUE);
        font.setFontHeightInPoints((short) 14);
        font.setFontName("宋体");
        header.setFont(font);
        header.setBorderTop(BorderStyle.THIN);
        header.setBorderLeft(BorderStyle.THIN);
        header.setBorderBottom(BorderStyle.THIN);
        header.setBorderRight(BorderStyle.THIN);
        header.setAlignment(HorizontalAlignment.CENTER);
        header.setVerticalAlignment(VerticalAlignment.CENTER);
        header.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//        header.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        return header;
    }
    private static CellStyle getBodyStyle(Workbook workbook) {
    
    
        CellStyle body = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("宋体");
        body.setFont(font);
        body.setWrapText(Boolean.TRUE);
        body.setBorderTop(BorderStyle.THIN);
        body.setBorderLeft(BorderStyle.THIN);
        body.setBorderBottom(BorderStyle.THIN);
        body.setBorderRight(BorderStyle.THIN);
        body.setAlignment(HorizontalAlignment.LEFT);
        body.setVerticalAlignment(VerticalAlignment.CENTER);
        body.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        body.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        return body;
    }

    /**
     * <p>@Description: object就是每一行的数据</p >
     * <p>@param [rowData, exportFieldArr]</p >
     * <p>@return java.lang.String[]</p >
     * <p>@throws </p >
     */
    public static String[] getValues(Object rowData, String[] exportFieldArr) {
    
    
        String[] values = new String[exportFieldArr.length];
        try {
    
    
            for (int i = 0; i < exportFieldArr.length; i++) {
    
    

                Field field = null;

                try {
    
    
                    field = rowData.getClass().getDeclaredField(exportFieldArr[i]);
                } catch (Exception e) {
    
    
                    field = rowData.getClass().getField(exportFieldArr[i]);
                }
                // 设置访问权限为true
                field.setAccessible(true);
                values[i] = setCellValue(field, rowData);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return values;
    }

    public static String setCellValue(Field field, Object data) {
    
    
        Class<?> fieldType = field.getType();
        String result = "";
        try {
    
    
            Method method = data.getClass().getMethod(getMethodNameByCamel("get", field.getName()));
            Object fieldValue = method.invoke(data);
            if (fieldType == String.class) {
    
    
                result = (method.invoke(data) == null ? null : method.invoke(data).toString());
            } else if (fieldType == Short.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == Integer.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == Long.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == Float.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == Double.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == BigDecimal.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == Boolean.class) {
    
    
                result = returnStringFromNumber(fieldValue);
            } else if (fieldType == LocalDate.class) {
    
    
                String pattern = "yyyy-MM-dd";
                LocalDate date = method.invoke(data) == null ? null : (LocalDate) method.invoke(data);
                if (date != null) {
    
    
                    result = (date.format(DateTimeFormatter.ofPattern(pattern)));
                }
            } else if (fieldType == LocalDateTime.class) {
    
    
                String pattern = "yyyy-MM-dd HH:mm:ss";
                LocalDateTime date = method.invoke(data) == null ? null : (LocalDateTime) method.invoke(data);
                if (date != null) {
    
    
                    result = (date.format(DateTimeFormatter.ofPattern(pattern)));
                }
            } else {
    
    
                result = (method.invoke(data) == null ? null : method.invoke(data).toString());
            }
            return result;
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
        return result;
    }

    // 将数字转换为 保留两位小数的字符串
    public static String returnStringFromNumber(Object data) {
    
    
        if (data == null || StrUtil.isBlank(data.toString())) {
    
    
            return "";
        }
        Double aDouble = Double.valueOf(data.toString());
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        String result = decimalFormat.format(aDouble);
        return result;
    }


    /**
     * <p>@Description: 拼接前缀以及方法名,驼峰形式</p >
     * <p>@param [prefix, fieldName]</p >
     * <p>@return java.lang.String</p >
     * <p>@throws </p >
     */
    private static String getMethodNameByCamel(String prefix, String fieldName) {
    
    
        StringBuilder builder = new StringBuilder()
                .append(prefix)
                .append(fieldName.substring(0, 1).toUpperCase())
                .append(fieldName.substring(1));
        return builder.toString();
    }
}

2.3 Business call layer

2.3.1 controller



import com.lzq.learn.domain.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author 吴巴哥
 */
@RestController
@RequestMapping("/dynamic")
public class TestDynamicExcelController {
    
    


    @Autowired
    private DynamicExportService dynamicExportService;

    // 方式一:通过注解的形式,动态导出excel
    @PostMapping("/exportByFields")
    public void exportByFields(@RequestBody DynamicExportRequest  dynamicExportRequest) {
    
    
        dynamicExportService.exportByFields(dynamicExportRequest , "1.xlsx");
    }


    //("返回给前端的导出Excel的动态列")
    @GetMapping("/getDynamicFields")
    public CommonResult getDynamicFields() {
    
    
        List<DynamicField> dynamicFieldList = dynamicExportService.getDynamicFields();
        return CommonResult.success(dynamicFieldList, "获取成功");
    }

}

2.3.2 service code


import com.lzq.learn.test.动态选择列导出excel.byExportBean.ExportExcel;
import com.lzq.learn.test.动态选择列导出excel.byExportBean.GoodsExportBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class DynamicExportService {
    
    



    // 模拟从 mysql 获取数据
    public List getDataList() {
    
    
        ArrayList<Goods> goodsList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            BigDecimal quantity = BigDecimal.TEN.add(BigDecimal.valueOf(i));
            Goods goods = new Goods(i, "商品"+i, BigDecimal.valueOf(i), "类型"+i, quantity, LocalDateTime.now());
            goodsList.add(goods);
        }
        return goodsList;
    }



    public void exportStaticsByReflect(DynamicExportRequest dynamicExportRequest, String fileName) {
    
    

    }

    public void exportByFields(DynamicExportRequest dynamicExportRequest, String fileName) {
    
    
        List<Goods> dataList = this.getDataList();
        GoodsExportBean goodsExportBean = dynamicExportRequest.getGoodsExportBean();
        ExportExcel.exportByTitleAndData(goodsExportBean, dataList, fileName);
    }

    public List<DynamicField> getDynamicFields() {
    
    
        List<DynamicField> dynamicFieldList = DynamicFieldUtil.exportPageListDynamicFieldList(GoodsExportBean.class);
        return dynamicFieldList;
    }
}

3. Example of postman interface call

3.1 Get optional export field columns

Interface address: localhost:8089/dynamic/getDynamicFields
The return value of the interface is as follows

{
    
    
	"code": 200,
	"message": "获取成功",
	"data": [
		{
    
    
			"englishFiledName": "id",
			"chineseTitleName": "id"
		},
		{
    
    
			"englishFiledName": "name",
			"chineseTitleName": "姓名"
		},
		{
    
    
			"englishFiledName": "price",
			"chineseTitleName": "价格"
		},
		{
    
    
			"englishFiledName": "type",
			"chineseTitleName": "类型"
		},
		{
    
    
			"englishFiledName": "quantity",
			"chineseTitleName": "库存"
		},
		{
    
    
			"englishFiledName": "createTime",
			"chineseTitleName": "创建时间"
		}
	]
}

3.2 Export excel after selecting fields

Interface address: localhost:8089/dynamic/exportByFields
post request, the parameter passing format is as follows

{
    
    
    "goodsExportBean":{
    
    
        //"id":"主键",
        "name":"商品名称",
        "price":"价格",
        //"type":"类型",
        "quantity":"库存哦"
        //"createTime":"上架时间"
    }
}

Finally, where the text is not thoughtful, I hope you guys can actively provide comments, I will accept it with humility and correct it in time!

Guess you like

Origin blog.csdn.net/lzq2357639195/article/details/131644196