一、异步导出Excel文件

1、设计思想

用户无需在当前页面等待导出结果,点击导出按钮后服务端即可返回前端提示用户导出处理中请到下载中心查看结果。

具体业务文件导出实现由后台异步处理导出文件到腾讯COS存储(有效期七天,到期自动删除)。

用户统一在下载中心菜单栏页面中查看导出任务结果并下载文件。

2、技术组件

① EasyExcel   文档地址:https://www.yuque.com/easyexcel/doc

② Redisson延迟队列或xxl-job定时任务    (定时更新文件状态为已过期)

③ 腾讯COS对象存储

3、具体实现

① 导出文件记录表

下载中心就是从这里查数据下载文件

export_record

② 导出状态枚举 ExportStateEnum

public enum ExportStateEnum {

 FAILED(1,"失败"),
SUCCESS(2,"成功"),
GOING(3,"进行中"),
EXPIRED(4,"已过期"),
; private Integer value;
private String msg;
}

③ 异步导出工具类 (PS:有待优化)

@Slf4j
@Component
public class AsyncExcelUtil {
@Autowired
ExportRecordFeignClient exportRecordFeignClient; @Autowired
COSClient cosClient;
@Autowired
ThreadPoolTaskExecutor taskExecutor; Long recordId; public ResponseData asyncExport(UserInfo userInfo, String fileName, Runnable r) {
//1、数据库初始化操作记录
ResponseData<Long> initResult = this.exportRecordInit(userInfo, fileName);
if (Objects.nonNull(initResult) && Objects.equals(initResult.getCode(), ResponseEnum.SUCCESS.getCode())) {
this.recordId = initResult.getData();
taskExecutor.execute(r);
return ResponseData.success("操作成功");
}
return ResponseData.fail("操作失败");
} /**
* 查询当前用户下导出文件记录数据
*
* @param entity
*/
public ResponseData<List<ExportRecordEntity>> queryExportRecordList(ExportRecordEntity entity) {
return exportRecordFeignClient.queryExportRecordList(entity);
} /**
* 初始化导入导出记录表
*
* @param userInfo
* @param fileName
*/
public ResponseData<Long> exportRecordInit(UserInfo userInfo, String fileName) {
//1、数据库初始化操作记录
ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
exportRecordEntity.setTenantId(Long.parseLong(userInfo.getUniversityId()));
exportRecordEntity.setOpType(1);
exportRecordEntity.setProgress(30);
exportRecordEntity.setIsSuccess(2);
exportRecordEntity.setExportFileName(fileName);
exportRecordEntity.setCreatedId(Long.parseLong(userInfo.getEmployeeId()));
exportRecordEntity.setCreatedName(userInfo.getUserName());
exportRecordEntity.setCreatedTime(LocalDateTime.now());
exportRecordEntity.setUpdatedTime(exportRecordEntity.getCreatedTime());
return exportRecordFeignClient.exportInit(exportRecordEntity);
} /**
* 数据整理完毕更新进度
*
* @param recordId
*/
public boolean exportDataComplete(Long recordId) {
ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
exportRecordEntity.setId(recordId);
exportRecordEntity.setProgress(80);
exportRecordEntity.setUpdatedTime(LocalDateTime.now());
return exportRecordFeignClient.exportDataComplete(exportRecordEntity);
} /**
* 导出excel文件上传到腾讯COS并更新导出操作结果
*
* @param data 数据列表
* @param fileNm 文件名
* @param sheetNm excel文件sheet名称
* @param <T>
*/
public <T> boolean exportToCos(List<T> data, String fileNm, String sheetNm) {
Either<String, String> exportResult = asyncExport1(recordId, data, fileNm, sheetNm);
ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
exportRecordEntity.setId(recordId);
exportRecordEntity.setUpdatedTime(LocalDateTime.now());
if (exportResult.isLeft()) {
exportRecordEntity.setIsSuccess(0);
exportRecordEntity.setFailReason(exportResult.getLeft());
} else {
exportRecordEntity.setProgress(100);
exportRecordEntity.setIsSuccess(1);
exportRecordEntity.setExportFileUrl(exportResult.get());
}
return exportRecordFeignClient.exportSaveResult(exportRecordEntity);
} /**
* 导出excel文件上传到腾讯COS
*
* @param data 数据列表
* @param fileNm 文件名
* @param sheetNm excel文件sheet名称
* @param <T>
*/
public <T> Either<String, String> asyncExport1(Long recordId, List<T> data, String fileNm, String sheetNm) {
if (Objects.isNull(data) || CollectionUtils.isEmpty(data)) {
return Either.left("数据为空");
} else {
this.exportDataComplete(recordId);
}
String filePath = "";
try {
//导出操作
String basePath = ResourceUtils.getURL("classpath:").getPath() + "static/";
// 建立新的文件
File fileExist = new File(basePath);
// 文件夹不存在,则新建
if (!fileExist.exists()) {
fileExist.mkdirs();
}
String fileName = fileNm + "-" + System.currentTimeMillis() + ".xlsx";
filePath = basePath + fileName;
EasyExcel.write(filePath, data.get(0).getClass()).sheet(sheetNm).doWrite(data);
// 指定要上传的文件
File localFile = new File(filePath);
// 指定文件将要存放的存储桶
String bucketName = Constants.DOCUMENT_BUCKET;
// 指定文件上传到 COS 上的路径,即对象键。例如对象键为folder/picture.jpg,则表示将文件 picture.jpg 上传到 folder 路径下
String key = "temp/asyncexcel/" + fileName;
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, localFile);
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
return Either.right(cosClient.getObjectUrl(bucketName, key).toString());
} catch (Exception e) {
log.error("异步导出excel异常:", e);
return Either.left(e.getMessage());
} finally {
removeTempFile(filePath);
}
} /***************************** private私有方法 *********************************/
private static void removeTempFile(String filePath) {
File delFile = new File(filePath);
   if (delFile.exists()) {
delFile.delete();
  }
} }

4、AsyncExcelUtil工具类具体使用示例

    @PostMapping(value = "/testAsyncExport", produces = {"application/json"})
public ResponseData testAsyncExport(@RequestBody CommonRequestParam param) {
UserInfo userInfo = param.getUserInfo();
String fileName = "异步导出测试文件";
UserInfoParam userParam = new UserInfoParam();
userParam.setUserName(userInfo.getUserName());
userParam.setUserId(userInfo.getUserId());
userParam.setModule("各自业务模块名称,如:直播数据");
return asyncExcelUtil.asyncExport(userParam, fileName, () -> {
//模拟封装得到要导出的数据
List<ExportVo> retVo = new ArrayList<>();
for (int i = 0; i < 7000; i++) {
ExportVo vo = new ExportVo();
vo.setModule("商城");
vo.setName("张三" + i);
vo.setUserDept("部门" + i);
vo.setWatchTotal("20");
retVo.add(vo);
}
asyncExcelUtil.exportToCos(userParam, retVo, fileName, "直播测试数据");
});
}

参数说明

① userParam 记录操作人信息

② fileName 文件名称

③ 行为参数化 不需要每个业务再创建对应的实现类,Lamda表达式代替内名内部类实现灵活。Runnable 接收传入线程池异步执行。

5、优化点:子线程异常处理

由于上面的实现如果异步子线程中发生异常是会直接退出的,无法记录任何日志,如果让业务方自己添加try catch模块有可能会造成疏漏而且也不方便。

优化方案:为线程设置“未捕获异常处理器”UncaughtExceptionHandler

在子线程中多添加一行固定代码设置当前线程的异常处理器:

Thread.currentThread().setUncaughtExceptionHandler(new CustomThreadExceptionHandler());
public class CustomThreadExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t,Throwable e) {
//处理 记录到库数据获取异常
}
}

6、异步导入Excel方案

实现思路整合和异步导出一致,在下载中心列表中区分导入和导出的操作,并且导入操作须记录能够直接跳转到对应业务菜单页面去,能够下载导入错误数据的excel文件。

异步导入导出Excel方案的更多相关文章

  1. C#中缓存的使用 ajax请求基于restFul的WebApi(post、get、delete、put) 让 .NET 更方便的导入导出 Excel .net core api +swagger(一个简单的入门demo 使用codefirst+mysql) C# 位运算详解 c# 交错数组 c# 数组协变 C# 添加Excel表单控件(Form Controls) C#串口通信程序

    C#中缓存的使用   缓存的概念及优缺点在这里就不多做介绍,主要介绍一下使用的方法. 1.在ASP.NET中页面缓存的使用方法简单,只需要在aspx页的顶部加上一句声明即可:  <%@ Outp ...

  2. uniapp导入导出Excel

    众所周知,uniapp作为跨端利器,有诸多限制,其中之一便是vue页面不支持传统网页的dom.bom.blob等对象. 所以,百度上那些所谓的导入导出excel的方法,大部分都用不了,比如xlsx那个 ...

  3. ASP.NET Core 导入导出Excel xlsx 文件

    ASP.NET Core 使用EPPlus.Core导入导出Excel xlsx 文件,EPPlus.Core支持Excel 2007/2010 xlsx文件导入导出,可以运行在Windows, Li ...

  4. thinkphp导入导出excel表单数据

    在PHP项目经常要导入导出Excel表单. 先去下载PHPExcel类库文件,放到相应位置. 我在thinkphp框架中的位置为ThinkPHP/Library/Org/Util/ 导入 在页面上传e ...

  5. 导入导出Excel工具类ExcelUtil

    前言 前段时间做的分布式集成平台项目中,许多模块都用到了导入导出Excel的功能,于是决定封装一个ExcelUtil类,专门用来处理Excel的导入和导出 本项目的持久化层用的是JPA(底层用hibe ...

  6. php中导入导出excel的原理

    在php中我们要经常导入导出excel文件,方便后台管理.那么php导入和导出excel的原理到底是什么呢?excel分为两大版本excel2007(后缀.xlsx).excel2003(后缀.xls ...

  7. NPOI导入导出EXCEL通用类,供参考,可直接使用在WinForm项目中

    以下是NPOI导入导出EXCEL通用类,是在别人的代码上进行优化的,兼容xls与xlsx文件格式,供参考,可直接使用在WinForm项目中,由于XSSFWorkbook类型的Write方法限制,Wri ...

  8. .NET导入导出Excel

    若是开发后台系统,ASP.NET MVC中总是涉及了很多导入导出Excel的问题,有的时候处理起来比较烦 如果能使用以下代码解决,就完美了 public class ReportModel { [Ex ...

  9. Java利用POI导入导出Excel中的数据

         首先谈一下今天发生的一件开心的事,本着一颗android的心我被分配到了PB组,身在曹营心在汉啊!好吧,今天要记录和分享的是Java利用POI导入导出Excel中的数据.下面POI包的下载地 ...

  10. .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)

    .Net MVC  导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构)   public cl ...

随机推荐

  1. Vuex 面试题(2023-09-13更新)

    谈谈你对 Vuex 的理解 什么是 Vuex? vuex 是 Vue 应用程序开发的状态管理插件,它采用集中式存储,管理应用的所有组件的状态 Vuex 解决了什么问题? 多个组件依赖于同一状态时,多层 ...

  2. 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-8- 元素高级定位技巧(详细教程)

    1.简介 随着网页的复杂性和动态性的增加,自动化测试变得越来越重要.Playwright作为一款强大的无头浏览器测试库,提供了多种元素定位方式,使得我们能够轻松地对网页进行自动化操作.在基础的定位方式 ...

  3. 我的世界服务端插件安装 AuthMe用户登录插件安装

    Minecraft服务端插件安装-AuthMe用户登录插件安装 需要准备AuthMe插件 AuthMe-5.6.0.jar用户登录插件 例如使用/register <密码> <确认密 ...

  4. betterZip解压后压缩包会删除 zip文件解压后原压缩文件能不能删掉

    https://www.betterzipcn.com/faq/better-ydeq.html 品牌型号:MacBook Book Air 系统:MacOS Mojave 10.14 软件版本:Be ...

  5. Qt程序员必看/关于Qt收费的官方答复

    一.答复说明 Qt软件从诞生之日就是GPL/LGPL开源授权和商业授权并存的,开源不代表免费而是为了共享.关于您的问题,我做大致的回复. Qt商用版本的模块是否都是LGPL协议,所有模块是否存在GPL ...

  6. FFmpeg命令行选项

    如下内容取自官网文档"Documentation-ffmpeg"和"Documentation-ffmpeg-all" 1 帮助信息 如下选项适用于 ff 系列 ...

  7. 记一次简单的存储过程和Pivot行转列

    首先我很讨厌写存储过程,其次我很讨厌 没办法,主要是需要进行 行转列,项目经理说可以用Pivot.我不是很精通sql,但是我会百度呀~ pivot需要有确定的列名.那我这个项目里面没办法确定,最后问了 ...

  8. Linux查找JDK的 实际位置

    检查JDK是否安装 java -version 查找java命令的位置 which java 查找列出该链接所指向的原始文件或目录 ls -l /usr/bin/java 查找目录 ls -l /et ...

  9. C Primer Plus 第6版 第八章 编程练习参考答案

    编译环境VS Code+WSL GCC 源码请到文末下载 . 我给第一题写了Linux shell脚本,感兴趣的同学可以尝试修改并运行一下. /*第1题************************ ...

  10. Github配置SSH避免远程提交重复输入用户名密码

    一. 前言 Logon failed, use ctrl+c to cancel basic credential prompt. 是不是提交会遇到上述错误,每次远程提交至Github需要重新输入用户 ...