异步导入导出Excel方案
一、异步导出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方案的更多相关文章
- 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 ...
- uniapp导入导出Excel
众所周知,uniapp作为跨端利器,有诸多限制,其中之一便是vue页面不支持传统网页的dom.bom.blob等对象. 所以,百度上那些所谓的导入导出excel的方法,大部分都用不了,比如xlsx那个 ...
- ASP.NET Core 导入导出Excel xlsx 文件
ASP.NET Core 使用EPPlus.Core导入导出Excel xlsx 文件,EPPlus.Core支持Excel 2007/2010 xlsx文件导入导出,可以运行在Windows, Li ...
- thinkphp导入导出excel表单数据
在PHP项目经常要导入导出Excel表单. 先去下载PHPExcel类库文件,放到相应位置. 我在thinkphp框架中的位置为ThinkPHP/Library/Org/Util/ 导入 在页面上传e ...
- 导入导出Excel工具类ExcelUtil
前言 前段时间做的分布式集成平台项目中,许多模块都用到了导入导出Excel的功能,于是决定封装一个ExcelUtil类,专门用来处理Excel的导入和导出 本项目的持久化层用的是JPA(底层用hibe ...
- php中导入导出excel的原理
在php中我们要经常导入导出excel文件,方便后台管理.那么php导入和导出excel的原理到底是什么呢?excel分为两大版本excel2007(后缀.xlsx).excel2003(后缀.xls ...
- NPOI导入导出EXCEL通用类,供参考,可直接使用在WinForm项目中
以下是NPOI导入导出EXCEL通用类,是在别人的代码上进行优化的,兼容xls与xlsx文件格式,供参考,可直接使用在WinForm项目中,由于XSSFWorkbook类型的Write方法限制,Wri ...
- .NET导入导出Excel
若是开发后台系统,ASP.NET MVC中总是涉及了很多导入导出Excel的问题,有的时候处理起来比较烦 如果能使用以下代码解决,就完美了 public class ReportModel { [Ex ...
- Java利用POI导入导出Excel中的数据
首先谈一下今天发生的一件开心的事,本着一颗android的心我被分配到了PB组,身在曹营心在汉啊!好吧,今天要记录和分享的是Java利用POI导入导出Excel中的数据.下面POI包的下载地 ...
- .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)
.Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构) public cl ...
随机推荐
- Avalonia 国际化之路:Resx 资源文件的深度应用与探索
在当今全球化的软件开发浪潮中,应用的国际化(i18n)与本地化(L10n)显得尤为重要.Avalonia UI 作为一款强大的跨平台 UI 框架,为开发者提供了多种实现国际化的途径.其中,使用传统的 ...
- shell 获取进程号
# Shell最后运行的后台PID(后台运行的最后一个进程的进程ID号) $! # Shell本身的PID(即脚本运行的当前进程ID号 $$
- rsync+ssh同步备份文件
定期对web代码或重要的文件做同步异地服务器备份,防止服务器故障严重磁盘损坏时文件丢失的问题. 备份服务器:192.168.200.134 目标服务器:192.168.201.65 rsync同步命令 ...
- com.sun.xml.internal.messaging.saaj.util 不存在
maven 编译时报错:程序包com.sun.xml.internal.messaging.saaj.util不存在需要添加 <compilerArguments> <verbose ...
- Qt编写的项目作品23-推流综合应用示例
一.功能特点 支持各种本地音视频文件和网络音视频文件,格式包括mp3.aac.wav.wma.mp4.mkv.rmvb.wmv.mpg.flv.asf等. 支持各种网络音视频流,网络摄像头,协议包括r ...
- 痞子衡嵌入式:MCUXpresso for VS Code开发环境搭建及SDK工程导入
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MCUXpresso for VS Code开发环境搭建及SDK工程导入. MCUXpresso IDE(包括其前身 LPCXpress ...
- [转]使用navicat将excel文件导入mysql数据库
excel: 注: 1.mysql里建立一张跟excel一样的表结构的表(包含id) 2.excel最好没有任何格式,只是纯值,不然会出现导入不了的错误 ----------------------- ...
- new idea
如何我希望将url链接作为大语言模型的输入,同时通过大模型的能力来学习与认识url网页链接中的文本.图片.语音等元素,应该怎么做? 要将URL链接作为输入来学习与识别URL中的文本.图片.语音等元素, ...
- WPF 背景阴影窗体
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/200 ...
- 《Linux shell 脚本攻略》第1章——读书笔记
目录 文件描述符及重定向 函数和参数 迭代器 算术比较 文件系统相关测试 字符串进行比较 文件描述符及重定向 echo "This is a sample text 1" > ...