系统的数据导出是一个重要的功能,而且对于excel类型的数据导出需求尤其多,如果系统的数据量不是很大,则无关紧要。但是系统的数据量如果非常巨大,对于导出数据来说就异常困难。
数据很少的情况下,进行数据导出,可以完全交给前端去做。前端人员可以通过接口获取的数据生成excel进行导出。数据量很少或者只允许用户每次导出很少的数据时,这种做法快速有效,成本很低,而且服务端只会承载数据查询的压力,考虑到数据库主从,这个压力就更小了。
同样,服务端也可以生成excel,通过流的形式进行输出,但是这种做法将压力交给了后端。
对于经常需要进行导出的系统来说,一般会搭建一个文件导出服务,这个服务专门为文件导出进行服务。
导出服务,对外提供导出的任务接口,不同业务只需要将需要导出的数据提交给这个服务,这个服务会完成一系列操作。
1.定义一个导出服务接口。该接口用于接收导出的数据。对于这个excel导出服务来说不应该处理数据,只需要接收即可。
/**
* 通用excel导出
*
* @param list 需要导出的数据明细
* @param model 数据对象(生成表头)
* @param taskName 任务的名称
* @param
*/
default void export(List list, Class model, String taskName) {
throw new UnsupportedOperationException();
}
2.创建实现类。这里将整个流程划分成4步:
a.创建导出任务。(该任务是为了在excel处理完成后,提供数据给前端进行展示,提供下载链接等信息。)
b.生成excel文件。需要看下面注意事项
c.上传至阿里云。(其它OSS也可,自己搭建的文件服务器也可)
d.更新导出任务。
注意:在服务端生成excel是一个耗内存和磁盘的操作,所以我们需要尽可能的保证excel不能太大。这就意味着在接收数据的时候最好限制条目数,该限制方法可以在调用方处理,也可以在导出的内部处理。事例代码中没有显示处理这一问题。提供以下参考:使用了google的算法。
List
partition = null; if (!CollectionUtils.isEmpty(数据集合)) {
// 进行数据切割
partition = Lists.partition(数据集合, 5000);
}
public interface ExcelExportService {
/**
* 通用excel导出
*
* @param list 需要导出的数据明细
* @param model 数据对象
* @param taskName 任务的名称
* @param
*/
default
void export(List list, Class model, String taskName) { throw new UnsupportedOperationException();
}
}
@Service @Slf4j public class ExcelExportServiceImpl implements ExcelExportService { @Autowired private TaskMapper taskMapper; @Autowired private ExcelFactory excelFactory; @Resource private ThreadPoolTaskExecutor threadPool; @Override publicvoid export(List list, Class model, String taskName) { threadPool.execute(new Runnable() { @Override public void run() { // 1.创建导出任务 Task task = createTask(taskName); // 2.生成excel文件 ByteArrayOutputStream out = generateFile(list, model); // 3.上传至阿里云 String key = uploadToOSS(out, taskName + ".xls"); // 4.更新导出任务 updateTask(task.getId(), key); } }); } /** * @Author xuhongchang * @Date 1/3/22 3:00 PM * @Describetion 创建导出任务 */ private Task createTask(String taskName) { Task task = new Task(); task.setName(taskName); task.setState(TaskEnum.ING.getValue()); task.setGmtCreated(new Date()); task.setDownloadCount(0); taskMapper.insertSelective(task); return task; } /** * @Author xuhongchang * @Date 1/3/22 3:30 PM * @Describetion 生成文件 */ public ByteArrayOutputStream generateFile(List dataList, Class model) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { excelFactory.createExportExcel().writeExcel(out, dataList, model); } catch (Exception e) { log.error("生成文件时报错:{}", e); } return out; } /*** * @Author xuhongchang * @Date 1/3/22 3:30 PM * @Describetion 上传文件 */ private String uploadToOSS(ByteArrayOutputStream out, String filename) { String endpoint = "XXXX"; String accessKeyId = "XXXX"; String accessKeySecret = "XXXX"; String bucketName = "xhc-test-01"; String dir = "fileCenter/"; OSS ossClient = null; PutObjectResult result = null; String key = dir + filename; try { // 创建OSSClient实例。 ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); result = ossClient.putObject(bucketName, key, new ByteArrayInputStream(out.toByteArray())); log.info("上传返回结果: {}", result); } catch (OSSException e) { e.printStackTrace(); } finally { // 关闭OSSClient。 ossClient.shutdown(); return result != null ? key : ""; } } private void updateTask(Long id, String url) { Task task = new Task(); task.setId(id); task.setState(StringUtils.isBlank(url) ? TaskEnum.FAIL.getValue() : TaskEnum.FINISHED.getValue() ); task.setUrl(url); taskMapper.updateByPrimaryKeySelective(task); } }
@Component public class ExcelFactory{ public ExportExcelUtil createExportExcel() { return new ExportExcelUtil<>(); } }
@Component @Slf4j public class ExportExcelUtil{ public ExportExcelUtil() { } public void createExcel(ByteArrayOutputStream out, List data, List tableHeadList) throws IOException { try { List > head = getExcelHead(tableHeadList); ExcelWriter writer = new ExcelWriter(null, out, ExcelTypeEnum.XLSX, true); Table table = new Table(0); table.setHead(head); Sheet sheet1 = new Sheet(1, 0); sheet1.setAutoWidth(true); sheet1.setSheetName("sheet1"); writer.write(data, sheet1, table); writer.finish(); out.flush(); } finally { if (out != null) { out.close(); } } } private List
> getExcelHead(List
tableHeadList){ List > head = new ArrayList
>(); for (String s : tableHeadList) { List
column = new ArrayList (); column.add(s); head.add(column); } return head; } public void writeExcel(ByteArrayOutputStream out, List data, Class model) throws Exception { WriteSheet writeSheet = new WriteSheet(); writeSheet.setSheetName("sheet1"); writeSheet.setSheetNo(1); EasyExcel.write(out, model).build().write(data, writeSheet).finish(); out.flush(); } }
@Slf4j @Configuration public class ThreadPoolConfiguration { @Bean("threadPool") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置核心线程数 executor.setCorePoolSize(32); // 设置最大线程数 executor.setMaxPoolSize(128); // 设置队列容量 executor.setQueueCapacity(1000); // 设置线程活跃时间(秒) executor.setKeepAliveSeconds(60); // 设置默认线程名称 executor.setThreadNamePrefix("业务线程-"); // 设置拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); return executor; } }
导出的对象表头设置,导出的中model参数,可以这样设置
/** * 用户名称 */ @ColumnWidth(35) @ExcelProperty(value = "用户名称", index = 0) private String userName; /** * 年龄 */ @ColumnWidth(35) @ExcelProperty(value = "年龄", index = 1) private Integer age; /** * 地址 */ @ColumnWidth(35) @ExcelProperty(value = "地址", index = 2) private String address;
嗯,结束啦!!!