数据导出组件
组件编码
hzero-starter-export
一、简介
1.1 概述
hzero-starter-export
导出组件基于 apache poi 4.0.0
开发,使用 SXSSF
支持大数据量导出,导出格式为*.xlsx
。在项目中只需依赖该jar包,再结合三个注解即可完成数据的导出。对于导出Excel样式,提供了两种默认实现,同时支持自定义导出样式。
1.2 组件坐标:
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-starter-export</artifactId>
<version>${hzero.starter.version}</version>
</dependency>
1.3 特性
- 支持大数据量导出,不存在内存溢出
- 支持可选择列导出
- 支持头行结构导出、头行打平导出
- 支持自定义Excel导出样式
- 支持自定义单元格格式
- 支持一个DTO中的列分组
- 支持数据自定义渲染
- 使用及其方便简单
二、使用指南
使用该组件导出数据主要会用到三个注解:@ExcelSheet
、@ExcelColumn
、@ExcelExport
。
2.1 @ExcelSheet
在导出的DTO类上,使用@ExcelSheet
标注导出的Sheet,头行结构中,行上也需要使用该注解标注。在@ExcelSheet
中,可配置导出Sheet的标题,分页查询大小等,基本不需配置,使用默认的即可。
/**
* Sheet(Excel)标题 首先根据多语言取,如果多语言为空则取title,title为空则取类名
*/
@AliasFor("zh")
String title() default "";
/**
* 中文标题
*/
@AliasFor("title")
String zh() default "";
/**
* 英文标题
*/
String en() default "";
/**
* 多语言KEY 根据 key & code 获取多语言
* @see #promptCode
* @see #title
*/
String promptKey() default "";
/**
* 多语言CODE 根据 key & code 获取多语言
* @see #promptKey
* @see #title
*/
String promptCode() default "";
/**
* 行偏移量 从第几行开始显示数据 大于等于0
*/
int rowOffset() default 0;
/**
* 占位符,偏移的列可使用占位符显示
*/
String placeholder() default "*****";
/**
* 列偏移量 从第几列开始显示数据 大于等于0
*/
int colOffset() default 0;
/**
* 分页大小 每次查询的数量
*/
int pageSize() default 5000;
2.2 @ExcelColumn
在导出DTO类中,在需要作为导出列的字段上,使用@ExcelColumn
标注,该注解可配置列标题、显示顺序等。
- 在头行结构多Sheet导出中,如果某列需要显示到子Sheet中,可配置
showInChildren=true
; - 如果某个字段是List子列表,需要配置
child=true
。 - 对于一个DTO,想要分组导出不同字段的组合,可设置
groups
属性,通过定义不同的接口来分组,同时需要在@ExcelExport
配置对应的groups
标识。 - 导出的Cell格式可通过
pattern
设置,在BaseConstants.Pattern
里定义了一些基础的日期、数字、金额的格式。 - 如果想要对某个字段自定义显示样式,可设置
renders
属性,需实现ValueRenderer
接口。
/**
* 列标题 首先根据多语言取,如果多语言为空则取title,title为空则取类名
*/
@AliasFor("zh")
String title() default "";
/**
* 中文标题
*/
@AliasFor("title")
String zh() default "";
/**
* 英文标题
*/
String en() default "";
/**
* 多语言KEY 根据 key & code 获取多语言
* @see #promptCode
* @see #title
*/
String promptKey() default "";
/**
* 多语言CODE 根据 key & code 获取多语言
* @see #promptKey
* @see #title
*/
String promptCode() default "";
/**
* 在子列表中显示该列
*/
boolean showInChildren() default false;
/**
* 列顺序
*/
int order() default 1;
/**
* Cell 格式,参考 {@link BaseConstants.Pattern}
*/
String pattern() default "";
/**
* 是否子节点
*/
boolean child() default false;
/**
* 列宽度
*/
String width() default "3000";
/**
* 是否可编辑
*/
boolean editable() default true;
/**
* 分组标识
*/
Class<?>[] groups() default {};
/**
* 数据渲染器,根据需求自行实现渲染器,设置Cell数据时会通过该渲染器来渲染数据和类型。
*
* <pre> example:
* public static class ExampleRenderer implements ValueRenderer {
*
* public Object render(Object value, Object data) {
* ExampleDTO dto = (ExampleDTO) data;
* return "template name = " + dto.name;
* }
* }
* </pre>
*/
Class<? extends ValueRenderer>[] renders() default {};
Example:
@ExcelSheet(zh = "收货记录", en = "Receiving record")
public class ReveRecodeDTO {
@ExcelColumn(zh = "事务编号", en = "trxNum", showInChildren=true)
private String trxNum;
@ExcelColumn(zh = "客户", en = "companyName", groups = {Group2.class})
private String companyName;
@ExcelColumn(zh = "物品编码", en = "itemCode", order = 4, groups = {Group1.class})
private String itemCode;
@ExcelColumn(zh = "物品名称", en = "itemName", order = 3, groups = {Group1.class})
private String itemName;
@ExcelColumn(zh = "日期", en = "trxDate", pattern = BaseConstants.Pattern.DATE)
private Date trxDate;
@ExcelColumn(zh = "数量", en = "quantity", groups = {Group2.class})
private BigDecimal quantity;
@ExcelColumn(zh = "金额", en = "netAmount", pattern = BaseConstants.Pattern.TB_ONE_DECIMAL)
private BigDecimal netAmount;
@ExcelColumn(zh = "原因", en = "moveReason")
private String moveReason;
@ExcelColumn(zh = "接收人", en = "receiptPerson")
private String receiptPerson;
@ExcelColumn(zh = "备注", en = "remark", renders = RemarkValueRenderer.class)
private String remark;
@ExcelColumn(zh = "详情列表", en = "detailsList", child = true)
List<RecordLineDTO> detailsList;
public interface Group1 {}
public interface Group2 {}
public class RemarkValueRenderer implements ValueRenderer {
@Override
public Object render(Object value, Object data) {
RecordLineDTO dto = (RecordLineDTO) data;
return "显示备注:" + dto.remark;
}
}
// getter/setter
}
2.3 @ExcelExport
在导出接口上,使用@ExcelExport
标注,注解需配置导出的DTO。
- 对于只有行的数据,一般可以直接使用已有的List、Page方法即可。
- 接口方法必须有
HttpServletResponse
和ExportParam
参数,HttpServletResponse用于输出Excel,ExportParam用于封装参数。 - 在ExportParam中,通过
exportType=COLUMN
请求导出列;通过exportType=DATA
导出Excel;通过fillerType
指定导出样式; - 对于头行结构,选择了行后,会将行字段名称放入
selection
中,在查询头行数据时,即可按需查询行数据。 - 接口方法如果有
PageRequest
参数,将会分批次查询数据填充到Sheet中,如果没有,则默认一次性查询所有数据。 - 导出时,所有的查询已经控制在一个事务内,不会出现幻读的问题。
/**
* 将该注解加在请求数据的接口上:<br/>
*
* 接口方法必须带有 {@link HttpServletResponse} 参数,将通过 {@link HttpServletResponse#getWriter()} 返回数据 <br/>
* 接口方法必须带有 {@link ExportParam} 参数:
* <ul>
* <li>通过 {@link ExportParam#fillerType} 指定导出方式</li>
* <li>通过 {@link ExportParam#exportType} 指定导出类型</li>
* <ul>
* <li>{@link ExportType#COLUMN} 查询导出的列</li>
* <li>{@link ExportType#DATA} 导出数据</li>
* <li>{@link ExportType#TEMPLATE} 导出模板</li>
* </ul>
* <li>通过 {@link ExportParam#ids} 传入选择导出的列</li>
* </ul>
* 接口方法最好带有分页参数 {@link PageRequest},支持分页查询数据,从而避免大数据量导致内存溢出 <br/>
*
* @author bojiangzhou 2018/07/25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelExport {
/**
* 导出对象
*/
Class<?> value();
/**
* 分组标识
*/
Class<?>[] groups() default {};
}
Example:
@GetMapping("/export")
@ExcelExport(ReveRecodeDTO.class)
public ResponseEntity export(ReveRecodeDTO record, ExportParam exportParam, HttpServletResponse response, PageRequest pageRequest) {
List<ReveRecodeDTO> list = repository.export(record, exportParam, pageRequest);
return Results.success(list);
}
2.4 导出数据
导出数据需要发起两次请求:
- 第一次请求可导出的列:host/v1/events/export?
exportType=COLUMN
- 第二次传入列ID导出数据:host/v1/events/export?
exportType=DATA
&fillerType=multi-sheet&ids=1&ids=2&ids=3&ids=4&ids=5&ids=6&ids=7……- 单 sheet 页导出:fillerType=single-sheet
- 多 sheet 页导出:fillerType=multi-sheet
三、异步导出
使用指南
- 在前端异步导出组件上选择【异步】,点击确认,如果导出任务成功执行则会返回异步导出任务的编号
- 复制返回的异步导出任务编号,到【文件管理】->【异步导出文件】页面查询该任务状态
- 任务状态有三种【正在进行】、【已取消】、【已结束】,【已结束】任务可能为异常结束,异常信息会在展示在【异常信息】一栏,也可能为成功执行完成,会在操作栏看到【下载】按钮,点击下载文件。
注意事项
- 目前异步导出支持默认使用hzero-file文件服务存储异步导出文件,文件大小受限,最大为40MB。
- 异步导出组件支持扩展,可自定义实现,将文件存储到自己部署的文件服务器。
- 支持中断异步导出任务,需要开启management端口并开放async-export-endpoint
management:
server:
port: 8080
endpoints:
web:
exposure:
include: async-export-endpoint #or '*'
- 默认关闭异步导出,开启需要增加配置。
hzero:
export:
enable-async: true
#异步线程池默认配置如下,如无需修改,可省略以下配置
core-pool-size: 3
maximum-pool-size: 10
keep-alive-time: 0
queue-size: 2147483645
async-thread-name: async-export-executor
四、定制化开发
一般来说,预定义的导出样式可能不满足需求,可以自行开发导出的方式。预定义两种导出方式,单Sheet页导出和多Sheet页导出。
单Sheet页: 头行打平的方式在一个Sheet页中显示
多Sheet页:头行分开Sheet页导出
如果需要自定义导出方式,可实现 ExcelFiller
抽象类(Excel填充器),该抽象类有两个抽象方法:
/**
* @return 填充器类型名称
*/
public abstract String getFillerType();
/**
* 填充数据
*
* @param workbook 工作簿
* @param root 导出列
* @param data 数据
*/
protected abstract void fillData(SXSSFWorkbook workbook, ExportColumn root, List<?> data);
开发完成后,需要将自定义的填充器注册为Spring的bean,这样才能找到该填充器。