一、异步导出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. 腾讯云对象存储COS获Veritas认证,数据安全能力再升级

    近日获悉,腾讯云对象存储 COS 正式通过 Veritas 备份软件标准化测试,为数据安全再添新助力. Veritas 对 COS 的支持已经从底层打通,目前 Veritas 的 NetBackup ...

  2. vue3 + vite 报错处理

    TypeError: vite.defineConfig is not a function 执行命令:npm install @vitejs/plugin-vue -D  config里面配置代码: ...

  3. 中电金信:ChatGPT一夜爆火,知识图谱何以应战?

              随着ChatGPT的爆火出圈     人工智能再次迎来发展小高潮      那么作为此前搜索领域的主流技术          知识图谱前路又将如何呢?   事实上,ChatGPT也 ...

  4. 【前端】2024年 前端Base64编码的中文处理问题

    window.btoa() 遇到中文要出问题 localStorage.setItem("token",window.btoa(unescape(encodeURIComponen ...

  5. Qt编写视频监控系统77-Onvif组件支持非正常时间的设备

    一.前言 在经历了大量的现场设备测试,至少几十种厂家.几百种设备,遇见过奇奇怪怪的问题,一个个想方设法解决,发现有个问题是在下发鉴权的时候,需要带上设备的时间,而不是发送端的时间,如果带的不是设备上的 ...

  6. Ubuntu 装卸Opencv

    buntu中卸载opencv的方法: 1.打开ubuntu: 2.找到当初安装opencv的build目录,进入该build目录执行卸载操作: 3.通过rm命令清理/usr中所有opencv相关项即可 ...

  7. k8s集群部署mysql完整过程记录

    挂载MySQL数据卷 在k8s集群中挂载MySQL数据卷 需要安装一个NFS. 在主节点安装NFS yum install -y nfs-utils rpcbind 在主节点创建目录 mkdir -p ...

  8. 20. C++快速入门--并发基础

    参考:<Professional c++>,<并发编程实战> 1 基本概念 1.1 竞争 原子性 "原子"(atomic)操作是指一种不可分割的操作, 即在 ...

  9. Redis组件的特性,实现一个分布式限流

    分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即 ...

  10. Java线程的安全问题

    当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作.Java采取的办法是sync ...