• 数据导出组件


    组件编码 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 特性

    二、使用指南

    使用该组件导出数据主要会用到三个注解:@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标注,该注解可配置列标题、显示顺序等。

    /**
     * 列标题 首先根据多语言取,如果多语言为空则取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。

    /**
     * 将该注解加在请求数据的接口上:<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 导出数据

    导出数据需要发起两次请求:

    三、异步导出

    使用指南

    1. 在前端异步导出组件上选择【异步】,点击确认,如果导出任务成功执行则会返回异步导出任务的编号
    2. 复制返回的异步导出任务编号,到【文件管理】->【异步导出文件】页面查询该任务状态
    3. 任务状态有三种【正在进行】、【已取消】、【已结束】,【已结束】任务可能为异常结束,异常信息会在展示在【异常信息】一栏,也可能为成功执行完成,会在操作栏看到【下载】按钮,点击下载文件。

    注意事项

    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,这样才能找到该填充器。