总的来说,easypoi基于Apache poi二次封装的开源二方包,用起来也比较简单,也提供了注解方式导出,并且在顺序、层级、颜色宽度、单元格合并等样式处理上,提供了丰富的注解属性和接口,推荐使用;
功能简介
easypoi是为了让开发者快速的实现excel、word、pdf的导入导出,基于Apache poi基础上的一个工具包;功能如下:
(1)基于注解的导入导出,可以灵活定义的表头字段,修改注解就可以修改Excel;
(2)支持常用的样式自定义;
(3)支持一对多的导出/导入;
(4)支持模板的导出,一些常见的标签,自定义标签;
(5)支持HTML/Excel转换;
(6)支持word、图片、excel的导出;
常用注解
因为本篇介绍的是使用easypoi注解方式的示例,先介绍下常用的注解;
1. @ExcelTarget
@ExcelTarget注解作用于最外层的对象,可以这么理解——这个类与一个excel对应(类属性对应excel的列);在使用easypoi的API导出excel生成Workbook对象时,作为参数传入,如下:
/**
* @description 导出实体类
*/
@Data
@ExcelTarget("auditFlowExport")
public class AuditFlowExport {
...
}
// 调用easypoi的API生成excel对象
final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);
2. @Excel
@Excel 注解是作用到Filed上面,是对Excel一列的一个描述,这个注解是必须要的注解,使用示例如下:
/**
* 一级审批人,excel列名:一级审批人、顺序6、宽度20、分组"审批人信息"
*/
@Excel(name = "一级审批人", orderNum = "6", width = 20, groupName = "审批人信息")
private String firstAuditor;
3. @ExcelEntity
@ExcelEntity注解表示一个继续深入导出的实体,是作用一个类型为实体的属性上面;
4. @ExcelCollection
@ExcelCollection注解表示一个集合,主要针对一对多的导出,作用在类型是List的属性上面;比如一个工单对应多个审批人,审批人就可以用集合表示如下:
/**
* 审核人信息
*/
@ExcelCollection(name = "审核人信息", orderNum = "6")
private List<StepAndAuditorExport> stepAndAuditorList;
Easypoi的使用示例
需求背景
将工单记录以Excel格式导出给审计人员,每条记录包括工单的基本信息;由于不同工单的审核层级不同,因此将审核人信息合并展示;
以下是接入及实现的步骤:
1. 引入依赖
当前工程为SpringBoot项目,引入以下依赖;
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
需要注意的是由于easypoi的依赖内部依赖原生的poi,所以引入了easypoi的依赖之后,需要把原生的poi的依赖删掉,不然可能遇到类冲突;
2. 定义导出数据对应的类
通过注解方式定义字段的excel列名、宽度、字体等;因为里面存在一对多(List类型给的属性),因此其他字段的注解属性加了needMerge = true,表示合并单元格;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author Akira
* @description 导出实体类
*/
@Data
@ExcelTarget("auditFlowExport")
public class AuditFlowExport {
/**
* 工单id
*/
@Excel(name = "审批单号", orderNum = "1", needMerge = true)
private Long flowId;
/**
* 工单标题
*/
@Excel(name = "流程名称", orderNum = "2", width = 50, needMerge = true)
private String title;
/**
* 发起人
*/
@Excel(name = "发起人", orderNum = "3", width = 18, needMerge = true)
private String submitorName;
/**
* 工单创建时间 格式yyyy-MM-dd HH:mm:ss
*/
@Excel(name = "发起时间", orderNum = "4", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 25, needMerge = true)
private Date createTime;
/**
* 业务模块编码
*/
@Excel(name = "归属项目", orderNum = "5", width = 25, needMerge = true)
private String secondSubModuleCodeName;
/**
* 审核人信息 因为存在一对多,因此其他列属性定义needMerge = true 即合并单元格
*/
@ExcelCollection(name = "审核人信息", orderNum = "6")
private List<StepAndAuditorExport> stepAndAuditorList;
/**
* 详情信息
*/
@Excel(name = "详情信息", orderNum = "7", width = 120, needMerge = true)
private String displayText;
@Data
public static class StepAndAuditorExport {
@Excel(name = "审核层级", orderNum = "1", width = 10)
private int step;
@Excel(name = "审核人", orderNum = "2", width = 18)
private String auditor;
}
}
3. 定义导出格式
调用导出方法时,其中一个参数就是样式;easypoi已经默认了一套样式,有自定义样式需求时,支持实现原默认样式类来扩展样式;下面的样式比较简单,仅将表头的字体设置大一号且加粗;
/**
* @author Akira
* @description 简单样式 仅统一字体
*/
public class ExcelSimpleStyleUtil extends ExcelExportStylerDefaultImpl {
public ExcelSimpleStyleUtil(Workbook workbook) {
super(workbook);
}
/**
* 这里设置表头的格式,最上面的一行
* @see ExportParams#title
*/
@Override
public CellStyle getHeaderStyle(short color) {
CellStyle cellStyle = super.getHeaderStyle(color);
cellStyle.setFont(getFont(workbook, 11, true));
cellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.GREY_50_PERCENT.getIndex());
return cellStyle;
}
/**
* 列标题
*/
@Override
public CellStyle getTitleStyle(short color) {
CellStyle cellStyle = super.getTitleStyle(color);
// 仅将表头的字体设置大一号且加粗
cellStyle.setFont(getFont(workbook, 11, true));
cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex());
return cellStyle;
}
/*以下都是行样式,交替*/
/**
* 行样式
*/
@Override
public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
CellStyle cellStyle = super.stringSeptailStyle(workbook, isWarp);
cellStyle.setFont(getFont(workbook, 10, false));
return cellStyle;
}
/**
* 这里设置循环行,没有样式的一行
*/
@Override
public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
CellStyle cellStyle = super.stringNoneStyle(workbook, isWarp);
cellStyle.setFont(getFont(workbook, 10, false));
return cellStyle;
}
/**
* 字体样式
*
* @param size 字体大小
* @param isBold 是否加粗
* @return
*/
private Font getFont(Workbook workbook, int size, boolean isBold) {
Font font = workbook.createFont();
// 字体样式
font.setFontName("微软雅黑");
// 是否加粗
font.setBold(isBold);
// 字体大小
font.setFontHeightInPoints((short) size);
return font;
}
}
4. 导出方法
其核心就是调用easypoi封装好的ExcelExportUtil#exportExcel方法,返回Workbook对象,然后向response里面写字节流;
/**
* 导出
*
* @param response
* @param pageQuery
*/
public void export(HttpServletResponse response, AuditFlowPageQuery query) {
// 查询结果并转换成导出类型AuditFlowExport
final List<AuditFlowExport> exportList = buildExportList(query);
// 不需要标题栏和二级标题 这里仅定义sheet的名称
final ExportParams exportParams = new ExportParams(null, null, "导出数据");
// 设置导出样式
exportParams.setStyle(ExcelStyleUtil.class);
// easypoi的核心方法 将java的List写成excel的每一行数据
final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);
// 导出文件
try {
String fileName = new String("记录导出结果.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
OutputStream outputStream = response.getOutputStream();
response.flushBuffer();
sheets.write(outputStream);
// 写完数据关闭流
outputStream.close();
log.warn("export success. [pageQuery={} total={}] ", JSON.toJSONString(pageQuery), exportList.size());
} catch (IOException e) {
log.error("export error! [pageQuery={}] e:{}", JSON.toJSONString(pageQuery), e);
}
}
}
5. 接口定义
一般的导出接口controller写法,默认GET方式,比较简单;
/**
* 我的可读工单导出
*/
@RequestMapping(value = {"/audit/flow/export"})
public DefaultResponseDTO<Boolean> export(AuditFlowPageQuery pageQuery, HttpServletResponse response) {
try {
userAuditFlowService.export(response, pageQuery);
return DefaultResponseDTO.success(Boolean.TRUE);
} catch (Exception e) {
log.error("audit flow export error", e);
return DefaultResponseDTO.fail(ResultCodeEnum.SERVER_BUSYNESS, "请稍后重试");
}
}
场景和代码都比较简单,如果想使用更加复杂的excel相关API的用法,可以参考下面的文章;
补充:同一个excel导出多个sheet
关于在同一个excel文件中导出多个sheet的方法,在网上搜了一圈,没有写的很清楚的,要么是复制粘贴的帖子,要么是把自己的代码原封不动复制上去,能不能编译通过都另说;自己通过查看easypoi的API,看到有一条这样的方法,如下:
// cn.afterturn.easypoi.excel.ExcelExportUtil#exportExcel
/**
* 根据Map创建对应的Excel(一个excel 创建多个sheet)
*
* @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
* Collection 数据
* @return
*/
public static Workbook exportExcel(List<Map<String, Object>> list, ExcelType type) {
Workbook workbook = getWorkbook(type, 0);
for (Map<String, Object> map : list) {
ExcelExportService service = new ExcelExportService();
service.createSheet(workbook, (ExportParams) map.get("title"),
(Class<?>) map.get("entity"), (Collection<?>) map.get("data"));
}
return workbook;
}
注意,这个方法仅支持导出xls类型的excel,也就是第二个参数必须为ExcelType.HSSF,如果改成ExcelType.XSSF,执行时会报错,原因具体不清楚,以验证过;一般来说单sheet行数小于65535,差不多够用了,否则就去操作原生的Apache poi吧;
注意:以上的描述有误,感谢评论区小伙伴的提醒,此方法在使用时,方法参数的第二位ExcelType,需要和导出配置ExportParams对象中指定的类型一致,否则会报错;
示例代码如下:
@Override
public void exportMultiSheetWorkbook(List<List<MyExport>> exportDataSet, HttpServletResponse response) {
// 多个sheet配置参数
final List<Map<String, Object>> sheetsList = Lists.newArrayList();
exportDataSet.forEach(exportList -> {
final String sheetName = "sheet名称";
Map<String, Object> exportMap = Maps.newHashMap();
final ExportParams exportParams = new ExportParams(null, sheetName, ExcelType.HSSF);
exportParams.setStyle(ExcelSimpleStyleUtil.class);
// 以下3个参数为API中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
exportMap.put("title", exportParams);
exportMap.put("entity", MyExport.class);
exportMap.put("data", exportList);
// 加入多sheet配置列表
sheetsList.add(exportMap);
});
// 导出文件
try {
// 核心方法:导出含多个sheet的excel文件 【注意,该方法第二个参数必须与上述的ExportParams对象指定的导出类型一致,默认ExcelType.HSSF格式,否则执行此方法时会报错!!!】
final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
String fileName = new String("文件名.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
OutputStream outputStream = response.getOutputStream();
response.flushBuffer();
workbook.write(outputStream);
// 写完数据关闭流
outputStream.close();
log.warn("导出成功");
} catch (IOException e) {
log.error("导出异常 e:{}", e);
}
}
补充:同一个excel导出多个sheet
实际工程中,可能会遇到"模板导出"的场景,如固定的表标题、表头,仅需要在固定的格式下面新增列去填充数据;例如:
这时候,可以使用easypoi的另一个API,带TemplateExportParams参数的方法;
其步骤是:
准备excel模板,在指定的单元格填充好easypoi支持的表达式;
再根据模板excel文件生成TemplateExportParams对象;
调用API,传入上面的TemplateExportParams对象和数据对象集合,返回excel对象;
示例代码:
@Override
public Workbook export(UnitInfoQuery unitInfoQuery) {
TemplateExportParams params = new TemplateExportParams("doc/导出模板.xls");
List<UnitInfoDO> unitInfoDOList = unitInfoDAO.selectByCondition(unitInfoQuery);
List<ExcelUnitInfoVO> units = unitInfoDOList.stream().map(beanConvertMapper::convert2ExcelUnitInfoVO).collect(Collectors.toList());
Map<String, Object> map = new HashMap<>();
// 字段名与导出excel模板表达式对应
map.put("units", units);
return ExcelExportUtil.exportExcel(params, map);
}