整合EasyExcel实现灵活的导入导出java

打印 上一主题 下一主题

主题 576|帖子 576|积分 1728


  • 引入pom依靠

  1.          <dependency>
  2.             <groupId>com.alibaba</groupId>
  3.             <artifactId>easyexcel</artifactId>
  4.         </dependency>
复制代码

  • 实现功能
    1. 结合Vue前端,实现浏览器页面直接导出日志文件
    2. 实现文件的灵活导入
    复制代码
文件导出
3. 实体类
  1. 实体类里有自定义转换器:用于Java类型数据和Excel类型数据的转换,非常使用。结合注解,可以非常方便的进行Excel文件导出。
复制代码
  1. /**
  2. * <p>
  3. * 操作日志信息
  4. * </p>
  5. *
  6. * home.php?mod=space&uid=686208 horse
  7. * home.php?mod=space&uid=441028 2020-09-08
  8. * 注意: 实体类中如果使用@Accessory(chain=true),那么导入的数据无法填充到实例中,导出数据不受影响
  9. */
  10. @Data
  11. @EqualsAndHashCode(callSuper = false)
  12. @TableName("tb_operational_log")
  13. @ApiModel(value = "OperationalLog对象", description = "操作日志信息")
  14. public class OperationalLog implements Serializable {
  15.     private static final long serialVersionUID = 1L;
  16.     @ExcelProperty({"操作日志", "日志ID"})
  17.     @ApiModelProperty(value = "日志ID")
  18.     @TableId(value = "id", type = IdType.ASSIGN_ID)
  19.     private String id;
  20.     @ExcelProperty({"操作日志", "操作类型"})
  21.     @ApiModelProperty(value = "操作类型")
  22.     private String operType;
  23.     @ExcelProperty({"操作日志", "操作描述"})
  24.     @ApiModelProperty(value = "操作描述")
  25.     private String operDesc;
  26.     @ExcelProperty({"操作日志", "操作员ID"})
  27.     @ApiModelProperty(value = "操作员ID")
  28.     private String operUserId;
  29.     @ExcelProperty({"操作日志", "操作员名称"})
  30.     @ApiModelProperty(value = "操作员名称")
  31.     private String operUserName;
  32.     @ExcelProperty({"操作日志", "操作方法"})
  33.     @ApiModelProperty(value = "操作方法")
  34.     private String operMethod;
  35.     @ExcelProperty({"操作日志", "请求方法"})
  36.     @ApiModelProperty(value = "请求方法")
  37.     private String operRequWay;
  38.     @ExcelProperty(value = {"操作日志", "请求耗时:单位-ms"}, converter = CustomRequestTimeConverter.class)
  39.     @ApiModelProperty(value = "请求耗时:单位-ms")
  40.     private Long operRequTime;
  41.     @ExcelProperty({"操作日志", "请求参数"})
  42.     @ApiModelProperty(value = "请求参数")
  43.     private String operRequParams;
  44.     @ExcelProperty({"操作日志", "请求Body"})
  45.     @ApiModelProperty(value = "请求Body")
  46.     private String operRequBody;
  47.     @ExcelProperty({"操作日志", "请求IP"})
  48.     @ApiModelProperty(value = "请求IP")
  49.     private String operRequIp;
  50.     @ExcelProperty({"操作日志", "请求URL"})
  51.     @ApiModelProperty(value = "请求URL")
  52.     private String operRequUrl;
  53.     @ExcelProperty(value = {"操作日志", "日志标识"}, converter = CustomLogFlagConverter.class)
  54.     @ApiModelProperty(value = "日志标识: 1-admin,0-portal")
  55.     private Boolean logFlag;
  56.     @ExcelProperty({"操作日志", "操作状态"})
  57.     @ApiModelProperty(value = "操作状态:1-成功,0-失败")
  58.     @TableField(value = "is_success")
  59.     private Boolean success;
  60.     @ExcelIgnore
  61.     @ApiModelProperty(value = "逻辑删除 1-未删除, 0-删除")
  62.     @TableField(value = "is_deleted")
  63.     @TableLogic(value = "1", delval = "0")
  64.     private Boolean deleted;
  65.     @ExcelProperty(value = {"操作日志", "创建时间"}, converter = CustomTimeFormatConverter.class)
  66.     @ApiModelProperty(value = "创建时间")
  67.     private Date gmtCreate;
  68. }
复制代码

  • 接口和具体实现
    4.1 接口
  1.     @OperatingLog(operType = BlogConstants.EXPORT, operDesc = "导出操作日志,写出到响应流中")
  2.     @ApiOperation(value = "导出操作日志", hidden = true)
  3.     @PostMapping("/oper/export")
  4.     public void operLogExport(@RequestBody List<String> logIds, HttpServletResponse response) {
  5.         operationalLogService.operLogExport(logIds, response);
  6.     }
复制代码
4.2 具体实现
  1.     自定义导出策略HorizontalCellStyleStrategy
  2.     自定义导出拦截器CellWriteHandler,更加精确的自定义导出策略
复制代码
  1.     /**
  2.      * 导出操作日志(可以考虑分页导出)
  3.      *
  4.      * @param logIds
  5.      * @param response
  6.      */
  7.     @Override
  8.     public void operLogExport(List<String> logIds, HttpServletResponse response) {
  9.         OutputStream outputStream = null;
  10.         try {
  11.             List<OperationalLog> operationalLogs;
  12.             LambdaQueryWrapper<OperationalLog> queryWrapper = new LambdaQueryWrapper<OperationalLog>()
  13.                     .orderByDesc(OperationalLog::getGmtCreate);
  14.             // 如果logIds不为null,按照id查询信息,否则查询全部
  15.             if (!CollectionUtils.isEmpty(logIds)) {
  16.                 operationalLogs = this.listByIds(logIds);
  17.             } else {
  18.                 operationalLogs = this.list(queryWrapper);
  19.             }
  20.             outputStream = response.getOutputStream();
  21.             // 获取单元格样式
  22.             HorizontalCellStyleStrategy strategy = MyCellStyleStrategy.getHorizontalCellStyleStrategy();
  23.             // 写入响应输出流数据
  24.             EasyExcel.write(outputStream, OperationalLog.class).excelType(ExcelTypeEnum.XLSX).sheet("操作信息日志")
  25.                     // .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自适应列宽(不是很适应,效果并不佳)
  26.                     .registerWriteHandler(strategy) // 注册上面设置的格式策略
  27.                     .registerWriteHandler(new CustomCellWriteHandler()) // 设置自定义格式策略
  28.                     .doWrite(operationalLogs);
  29.         } catch (Exception e) {
  30.             log.error(ExceptionUtils.getMessage(e));
  31.             throw new BlogException(ResultCodeEnum.EXCEL_DATA_EXPORT_ERROR);
  32.         } finally {
  33.             IoUtil.close(outputStream);
  34.         }
  35.     }
复制代码
自界说导出策略简单如下:
  1. /**
  2. * @author Mr.Horse
  3. * @version 1.0
  4. * @description: 单元格样式策略
  5. * @date 2021/4/30 8:43
  6. */
  7. public class MyCellStyleStrategy {
  8.     /**
  9.      * 设置单元格样式(仅用于测试)
  10.      *
  11.      * @return 样式策略
  12.      */
  13.     public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
  14.         // 表头策略
  15.         WriteCellStyle headerCellStyle = new WriteCellStyle();
  16.         // 表头水平对齐居中
  17.         headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
  18.         // 背景色
  19.         headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
  20.         WriteFont headerFont = new WriteFont();
  21.         headerFont.setFontHeightInPoints((short) 14);
  22.         headerCellStyle.setWriteFont(headerFont);
  23.         // 自动换行
  24.         headerCellStyle.setWrapped(Boolean.FALSE);
  25.         // 内容策略
  26.         WriteCellStyle contentCellStyle = new WriteCellStyle();
  27.         // 设置数据允许的数据格式,这里49代表所有可以都允许设置
  28.         contentCellStyle.setDataFormat((short) 49);
  29.         // 设置背景色: 需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
  30.         contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
  31.         contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
  32.         // 设置内容靠左对齐
  33.         contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
  34.         // 设置字体
  35.         WriteFont contentFont = new WriteFont();
  36.         contentFont.setFontHeightInPoints((short) 12);
  37.         contentCellStyle.setWriteFont(contentFont);
  38.         // 设置自动换行
  39.         contentCellStyle.setWrapped(Boolean.FALSE);
  40.         // 设置边框样式和颜色
  41.         contentCellStyle.setBorderLeft(MEDIUM);
  42.         contentCellStyle.setBorderTop(MEDIUM);
  43.         contentCellStyle.setBorderRight(MEDIUM);
  44.         contentCellStyle.setBorderBottom(MEDIUM);
  45.         contentCellStyle.setTopBorderColor(IndexedColors.RED.getIndex());
  46.         contentCellStyle.setBottomBorderColor(IndexedColors.GREEN.getIndex());
  47.         contentCellStyle.setLeftBorderColor(IndexedColors.YELLOW.getIndex());
  48.         contentCellStyle.setRightBorderColor(IndexedColors.ORANGE.getIndex());
  49.         // 将格式加入单元格样式策略
  50.         return new HorizontalCellStyleStrategy(headerCellStyle, contentCellStyle);
  51.     }
  52. }
复制代码
自界说导出拦截器简单如下:
  1. /**
  2. * @author Mr.Horse
  3. * @version 1.0
  4. * @description 实现CellWriteHandler接口, 实现对单元格样式的精确控制
  5. * @date 2021/4/29 21:11
  6. */
  7. public class CustomCellWriteHandler implements CellWriteHandler {
  8.     private static Logger logger = LoggerFactory.getLogger(CustomCellWriteHandler.class);
  9.     @Override
  10.     public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
  11.                                  Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
  12.     }
  13.     /**
  14.      * 单元格创建之后(没有写入值)
  15.      *
  16.      * @param writeSheetHolder
  17.      * @param writeTableHolder
  18.      * @param cell
  19.      * @param head
  20.      * @param relativeRowIndex
  21.      * @param isHead
  22.      */
  23.     @Override
  24.     public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
  25.                                 Head head, Integer relativeRowIndex, Boolean isHead) {
  26.     }
  27.     @Override
  28.     public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
  29.                                        CellData cellData, Cell cell, Head head, Integer relativeRowIndex,
  30.                                        Boolean isHead) {
  31.     }
  32.     /**
  33.      * 单元格处理后(已写入值): 设置第一行第一列的头超链接到EasyExcel的官网(本系统的导出的excel 0,1两行都是头,所以只设置第一行的超链接)
  34.      * 这里再进行拦截的单元格样式设置的话,前面该样式将全部失效
  35.      *
  36.      * @param writeSheetHolder
  37.      * @param writeTableHolder
  38.      * @param cellDataList
  39.      * @param cell
  40.      * @param head
  41.      * @param relativeRowIndex
  42.      * @param isHead
  43.      */
  44.     @Override
  45.     public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
  46.                                  List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex,
  47.                                  Boolean isHead) {
  48.         // 设置超链接
  49.         if (isHead && cell.getRowIndex() == 0 && cell.getColumnIndex() == 0) {
  50.             logger.info(" ==> 第{}行,第{}列超链接设置完成", cell.getRowIndex(), cell.getColumnIndex());
  51.             CreationHelper helper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
  52.             Hyperlink hyperlink = helper.createHyperlink(HyperlinkType.URL);
  53.             hyperlink.setAddress("https://github.com/alibaba/easyexcel");
  54.             cell.setHyperlink(hyperlink);
  55.         }
  56.         // 精确设置单元格格式
  57.         boolean bool = isHead && cell.getRowIndex() == 1 &&
  58.                 (cell.getStringCellValue().equals("请求参数") || cell.getStringCellValue().equals("请求Body"));
  59.         if (bool) {
  60.             logger.info("第{}行,第{}列单元格样式设置完成。", cell.getRowIndex(), cell.getColumnIndex());
  61.             // 获取工作簿
  62.             Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
  63.             CellStyle cellStyle = workbook.createCellStyle();
  64.             Font cellFont = workbook.createFont();
  65.             cellFont.setBold(Boolean.TRUE);
  66.             cellFont.setFontHeightInPoints((short) 14);
  67.             cellFont.setColor(IndexedColors.SEA_GREEN.getIndex());
  68.             cellStyle.setFont(cellFont);
  69.             cell.setCellStyle(cellStyle);
  70.         }
  71.     }
  72. }
复制代码
4.3 前端请求
  1. 前端在基于Vue+Element的基础上实现了点击导出按钮,在浏览器页面进行下载。
复制代码
  1. // 批量导出
  2.     batchExport() {
  3.       // 遍历获取id集合列表
  4.       const logIds = []
  5.       this.multipleSelection.forEach(item => {
  6.         logIds.push(item.id)
  7.       })
  8.        // 请求后端接口
  9.       axios({
  10.         url: this.BASE_API + '/admin/blog/log/oper/export',
  11.         method: 'post',
  12.         data: logIds,
  13.         responseType: 'arraybuffer',
  14.         headers: { 'token': getToken() }
  15.       }).then(response => {
  16.         // type类型可以设置为文本类型,这里是新版excel类型
  17.         const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' })
  18.         const pdfUrl = window.URL.createObjectURL(blob)
  19.         const fileName = 'HorseBlog操作日志' // 下载文件的名字
  20.         // 对于<a>标签,只有 Firefox 和 Chrome(内核)支持 download 属性
  21.         if ('download' in document.createElement('a')) {
  22.           const link = document.createElement('a')
  23.           link.href = pdfUrl
  24.           link.setAttribute('download', fileName)
  25.           document.body.appendChild(link)
  26.           link.click()
  27.           window.URL.revokeObjectURL(pdfUrl) // 释放URL 对象
  28.         } else {
  29.           // IE 浏览器兼容方法
  30.           window.navigator.msSaveBlob(blob, fileName)
  31.         }
  32.       })
  33.     }
复制代码
测试结果:还行,基本实现了页面下载的功能
Excel文件导入
5. 文件读取配置
  1. 本配置基于泛型的方式编写,可扩展性较强。
复制代码
  1. /**
  2. * @author Mr.Horse
  3. * @version 1.0
  4. * @description: EasyExcel文件读取配置(不能让spring管理)
  5. * @date 2021/4/27 13:24
  6. */
  7. public class MyExcelImportConfig<T> extends AnalysisEventListener<T> {
  8.     private static Logger logger = LoggerFactory.getLogger(MyExcelImportConfig.class);
  9.     /**
  10.      * 每次读取的最大数据条数
  11.      */
  12.     private static final int MAX_BATCH_COUNT = 10;
  13.     /**
  14.      * 泛型bean属性
  15.      */
  16.     private T dynamicService;
  17.     /**
  18.      * 可接收任何参数的泛型List集合
  19.      */
  20.     List<T> list = new ArrayList<>();
  21.     /**
  22.      * 构造函数注入bean(根据传入的bean动态注入)
  23.      *
  24.      * @param dynamicService
  25.      */
  26.     public MyExcelImportConfig(T dynamicService) {
  27.         this.dynamicService = dynamicService;
  28.     }
  29.     /**
  30.      * 解析每条数据都进行调用
  31.      *
  32.      * @param data
  33.      * @param context
  34.      */
  35.     @Override
  36.     public void invoke(T data, AnalysisContext context) {
  37.         logger.info(" ==> 解析一条数据: {}", JacksonUtils.objToString(data));
  38.         list.add(data);
  39.         if (list.size() > MAX_BATCH_COUNT) {
  40.             // 保存数据
  41.             saveData();
  42.             // 清空list
  43.             list.clear();
  44.         }
  45.     }
  46.     /**
  47.      * 所有数据解析完成后,会来调用一次
  48.      * 作用: 避免最后集合中小于 MAX_BATCH_COUNT 条的数据没有被保存
  49.      *
  50.      * @param context
  51.      */
  52.     @Override
  53.     public void doAfterAllAnalysed(AnalysisContext context) {
  54.         saveData();
  55.         logger.info(" ==> 数据解析完成 <==");
  56.     }
  57.     /**
  58.      * 保存数据: 正式应该插入数据库,这里用于测试
  59.      */
  60.     private void saveData() {
  61.         logger.info(" ==> 数据保存开始: {}", list.size());
  62.         list.forEach(System.out::println);
  63.         logger.info(" ==> 数据保存结束 <==");
  64.     }
  65.     /**
  66.      * 在转换异常 获取其他异常下会调用本接口。我们如果捕捉并手动抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
  67.      *
  68.      * @param exception
  69.      * @param context
  70.      * @throws Exception
  71.      */
  72.     @Override
  73.     public void onException(Exception exception, AnalysisContext context) throws Exception {
  74.         logger.error(" ==> 数据解析失败,但是继续读取下一行:{}", exception.getMessage());
  75.         //  如果是某一个单元格的转换异常 能获取到具体行号
  76.         if (exception instanceof ExcelDataConvertException) {
  77.             ExcelDataConvertException convertException = (ExcelDataConvertException) exception;
  78.             logger.error("第{}行,第{}列数据解析异常", convertException.getRowIndex(), convertException.getColumnIndex());
  79.         }
  80.     }
  81. }
复制代码

  • 读取测试
  1.     @ApiOperation(value = "数据导入测试", notes = "操作日志导入测试[OperationalLog]", hidden = true)
  2.     @PostMapping("/import")
  3.     public R excelImport(@RequestParam("file") MultipartFile file) throws IOException {
  4.         EasyExcel.read(file.getInputStream(), OperationalLog.class, new MyExcelImportConfig<>(operationalLogService))
  5.                 .sheet().doRead();
  6.         return R.ok().message("文件导入成功");
  7.     }
复制代码

  • 附上自界说属性转换器
    转换器的属性内容转换,需要根据自己的实际业务需求而定,这里仅作为简单示例
  1. /**
  2. * @author Mr.Horse
  3. * @version 1.0
  4. * @description: 自定义excel转换器: 将操作日志的请求耗时加上单位 "ms"
  5. * @date 2021/4/27 10:25
  6. */
  7. public class CustomRequestTimeConverter implements Converter<Long> {
  8.     /**
  9.      * 读取数据时: 属性对应的java数据类型
  10.      *
  11.      * @return
  12.      */
  13.     @Override
  14.     public Class<Long> supportJavaTypeKey() {
  15.         return Long.class;
  16.     }
  17.     /**
  18.      * 写入数据时: excel内部的数据类型,因为请求耗时是long类型,对应excel是NUMBER类型,但是加上"ms后对应的是STRING类型"
  19.      *
  20.      * @return
  21.      */
  22.     @Override
  23.     public CellDataTypeEnum supportExcelTypeKey() {
  24.         return CellDataTypeEnum.STRING;
  25.     }
  26.     /**
  27.      * 读取回调
  28.      *
  29.      * @param cellData
  30.      * @param contentProperty
  31.      * @param globalConfiguration
  32.      * @return
  33.      * @throws Exception
  34.      */
  35.     @Override
  36.     public Long convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
  37.         // 截取字符串: "ms",转换为long类型
  38.         String value = cellData.getStringValue();
  39.         return Long.valueOf(value.substring(0, value.length() - 2));
  40.     }
  41.     @Override
  42.     public CellData<Long> convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
  43.         // 添加字符串: "ms"
  44.         return new CellData<>(String.valueOf(value).concat("ms"));
  45.     }
  46. }
复制代码
格式化时间
  1. /**
  2. * @author Mr.Horse
  3. * @version 1.0
  4. * @description: {description}
  5. * @date 2021/4/27 14:01
  6. */
  7. public class CustomTimeFormatConverter implements Converter<Date> {
  8.     @Override
  9.     public Class<Date> supportJavaTypeKey() {
  10.         return Date.class;
  11.     }
  12.     @Override
  13.     public CellDataTypeEnum supportExcelTypeKey() {
  14.         return CellDataTypeEnum.STRING;
  15.     }
  16.     @Override
  17.     public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
  18.         String value = cellData.getStringValue();
  19.         return DateUtil.parse(value, DatePattern.NORM_DATETIME_PATTERN);
  20.     }
  21.     @Override
  22.     public CellData<Date> convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
  23.         return new CellData<>(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN));
  24.     }
  25. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表