功能描述
数据集即查询出来的List
1.效果截图
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 extends ExportDict>[] 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 extends ExportDict>[] 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的实现。