java实战--异步Excel下载
2024-01-23 本文已影响0人
Geroge1226
1、说明
实际业务中,系统会涉及Excel
报表下载共功能,当业务数据体量过大,为避免用户点击下载后长时间等待,可设计成异步Excel
(生成Excel
和下载Excel
两步)下载,用户点击下载任务触达后,可进行其他业务操作,稍后在业务报表模块中查看生成的Excel
信息,在点击已生成完成的的Excel文件链接
下载即可。
2、 设计方案
2.1 业务交互流程
(1)用户点击业务下载按钮
(2)系统收到下载请求,后台异步去生成
Excel文件
,弹框反馈用户下载请求已触达,稍后去报表模块
查看报表image.png
(3)报表中心看到后台生成好的Excel报表名称及时间
image.png
2.2 设计示意图
image.png【说明】
a.用户点击下载Excel
b.异步生成Excel文件,文件上传到文件存储OSS服务器,返回文件地址
c.文件名称及地址保存数据库
d.用户在报表中心展示文件地址超链接。下载从OSS云服务器下载(可CDN加速)
3、实现
3.1 数据库表设计
- 导出文件表
point_export_file
CREATE TABLE `point_export_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`zone_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '导出文件所属区部',
`type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '文件类型',
`file_name` varchar(256) DEFAULT '' COMMENT '文件名称',
`file_url` varchar(256) DEFAULT '' COMMENT '文件url',
`operator_id` bigint(20) DEFAULT NULL COMMENT '操作人id',
`operator_name` varchar(256) NOT NULL DEFAULT 'admin' COMMENT '用户名称',
`down_count` smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '下载次数',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最近更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='导出文件列表';
3.2 程序代码实现
(1)controller类
,接收业务导出请求,触发异步导出任务。
@Qualifier("excelThreadPool")
@Autowired
private ExecutorService executorService;
@ApiOperation("导出积分明细")
@GetMapping(value = "/api/point/export")
public Response<Boolean> exportConsumeRecord(PointExportCriteria criteria) {
// 触发异步事件
Future<?> future = executorService.submit( () -> {
final String fileUrl = pointService.getPointRecordFileUrl(criteria);} );
return Response.ok(true);
}
(2)配置类config,配置异步线程池。
@Bean("excelThreadPool")
public ExecutorService buildExcelThreadPool() {
int cpuNum = Runtime.getRuntime().availableProcessors();
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1000);
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("excel-pool-%d").build();
return new ThreadPoolExecutor( cpuNum * 2 + 1, 5 * cpuNum,
2, TimeUnit.MINUTES, workQueue, threadFactory);
}
(3)核心异步业务实现类,读取业务数据生成Excel文件,并触发上传文件。
public String getPointRecordFileUrl(PointExportCriteria criteria){
// todo 1查询数据
List<Point> pointList = new ArrayList();
int pageNo = 1;
criteria.setPageNo(pageNo);
criteria.setPageSize(PAGE_SIZE);
String fileName = "自定义文件名头-" + DateTime.now().toString("yyyyMMddHHmmss") + ".xlsx";
String filePath = FILE_PATH +fileName;
int total = PAGE_SIZE;
// todo 2.使用alibaba Excel去写文档
ExcelWriter excelWriter = null;
try {
// 这里 需要指定写用哪个class去写
excelWriter = EasyExcel.write(filePath, Point.class).build();
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("电子券使用记录").build();
while (true) {
criteria.setPageNo(pageNo);
Paging<Point> paging = point.pagingMapper(criteria);
if (paging.getData().isEmpty()) {
log.warn("this paging now do not has coupon consume record,criteria {},pageNo {}", criteria, pageNo);
break;
}
List<Point> pointList = paging.getData();
total= pointList.size();
exportScopeDtoList.addAll(pointList);
excelWriter.write(exportScopeDtoList, writeSheet);
exportScopeDtoList.clear();
pageNo++;
if (total < PAGE_SIZE) {
break;
}
}
} catch (Exception e) {
log.error("export failed,cause:{} ", Throwables.getStackTraceAsString(e));
throw new JsonResponseException(500,"point.export.fail");
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
// 获取下载链接
String fileUrl = getFileUrl(filePath, fileName, currentUser,zoneIds);
log.info("导出积分 done criteria: {},cost: {} ms",criteria,stopwatch.elapsed(TimeUnit.MILLISECONDS));
return fileUrl;
}
(4)文件上传,OSS文件存储,返回文件地址,并保存到导出文件列库表。
public String getFileUrl(String filePath, String fileName, BaseUser currentUser, List<String> zoneIds) {
File file = new File(filePath);
// 上传云服务器
String fileUrl = ossBlobClient.doUpload(file);
PointExportFile exportFile = new PointExportFile();
exportFile.setFileUrl(fileUrl);
exportFile.setType(0);
exportFile.setFileName(fileName);
exportFile.setOperatorId(currentUser.getId());
exportFile.setOperatorName(currentUser.getName());
if (!CollectionUtils.isEmpty(zoneIds)) {
exportFile.setZoneId(zoneIds.get(0));
}
exportFile.setDownCount(0);
Response<Long> longResponse = pointExportFileMapper.create(exportFile);
if (!longResponse.isSuccess()) {
log.error("create export file record fail,cause {}",longResponse.getError());
throw new JsonResponseException(longResponse.getError());
}
return fileUrl;
}
(5)文件列表查询,返回文件链接地址,名称信息。前端通过Excel
链接去完成下载
操作。
@ApiOperation("文件导出分页查询")
@RequestMapping(value = "/export/paging", method = RequestMethod.GET)
public Response<Paging<PointExportFile>> paging(
@RequestParam(required = true,defaultValue = "1")Integer pageNo,
@RequestParam(required = true,defaultValue = "10")Integer pageSize){
if (log.isDebugEnabled()) {
log.debug("start paging export file ,pageNo: {},pageSize: {}", pageNo,pageSize);
}
ExportFileCriteria exportFileCriteria = new ExportFileCriteria();
exportFileCriteria.setPageNo(pageNo);
exportFileCriteria.setPageSize(pageSize);
exportFileCriteria.setUserZoneIds(ManageZoneUtil.queryUserManageZone());
Response<Paging<ExportFile>> resp = exportFileReadService.paging(exportFileCriteria);
if(!resp.isSuccess()){
log.error("failed to paging export file cause: {}",
resp.getError());
throw new JsonResponseException(resp.getError());
}
return resp;
}