首页 > 其他分享 >使用EasyExcel异步导出excel技术方案

使用EasyExcel异步导出excel技术方案

时间:2023-07-27 10:11:58浏览次数:61  
标签:异步 null String utf8mb4 EasyExcel excel exportFileFlow new NULL

  1. 主线程:处理请求响应,同时开启子线程,让子线程处理导出任务
  2. 子线程:将导出的文件写入到磁盘临时文件,临时文件上传到oss中获取上传文件的url路径,记录url路径到数据库中,最后再删除临时文件
  3. 通过单独一个页面查询导出文件流水的列表,进行下载文件

代码实现

线程池配置

@Component
public class ExcelExportThreadPoolConfig {

    private static final int CPU_SIZE = Runtime.getRuntime().availableProcessors();

    @Bean(value = "exportThreadPoolExecutor")
    public ThreadPoolExecutor poolExecutorSetting() {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CPU_SIZE * 2,
            CPU_SIZE * 2,
            3,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(20),
            new CustomizableThreadFactory("文件导出线程池")
        );

        threadPoolExecutor.prestartAllCoreThreads();
        return threadPoolExecutor;
    }
}

controller层

/**
     * easyExcel异步导出通用案例2
     *
     * @param response
     * @return
     */
@GetMapping("asyncExportFileFlow")
public String asyncExportFileFlow(HttpServletResponse response) {

    CompletableFuture.runAsync(() -> {

        this.studentService.asyncExportFileFlow(response);

    }, exportThreadPoolExecutor).exceptionally(e -> {
        log.error("导出文件失败", e);
        return null;
    });

    return "ok";

}

service层

@Override
public void exportUserData(HttpServletResponse response) {

    List<Map<String, Object>> data = this.sysUserMapper.list();
    if (data.isEmpty()) {
        throw new RuntimeException("数据为空");
    }

    recordFileFlow(response, data, ExcelExportEnum.USER_EXCEL);

}

/**
     * 记录导出文件的流水
     *
     * @param response
     * @param data
     * @param excelExportEnum
     */
private void recordFileFlow(HttpServletResponse response,
                            List<Map<String, Object>> data,
                            ExcelExportEnum excelExportEnum) {

    ExportFileFlow exportFileFlow = new ExportFileFlow();
    exportFileFlow.setFileName(excelExportEnum.getFilename());
    exportFileFlow.setCreateTime(LocalDateTime.now());
    exportFileFlow.setOperateUser("系统用户");
    this.exportFileFlowMapper.insert(exportFileFlow);

    long beginTime = System.currentTimeMillis();
    try {
        String fileUrl = this.exportTemplate.uploadFileToOss(response, excelExportEnum, data);
        long endTime = System.currentTimeMillis();
        exportFileFlow.setExportStatus(1);
        exportFileFlow.setExportConsumeTime((endTime - beginTime) / 1000);
        exportFileFlow.setFileUrl(fileUrl);
    } catch (Exception e) {
        exportFileFlow.setExportStatus(2);
        exportFileFlow.setFailReason(e.toString());
        exportFileFlow.setCreateTime(null);
        throw new RuntimeException(e);
    } finally {
        this.exportFileFlowMapper.update(exportFileFlow);
    }
}
public String uploadFileToOss(HttpServletResponse response,
                              ExcelExportEnum excelExportEnum,
                              List<Map<String, Object>> writeData) throws IOException {

    BufferedOutputStream tempOutStream = null;
    BufferedInputStream tempInStream = null;
    Path newFilePath = null;
    try {

        // 1.加载类路径下的模板
        ClassPathResource classPathResource = new ClassPathResource(excelExportEnum.getFileClassPath());
        String originalFileName = classPathResource.getFilename();
        if (StringUtils.isEmpty(originalFileName)) {
            throw new RuntimeException("文件名不能为空");
        }

        // 2.创建临时目录
        File file = new File(DISK_TEMPORARY_LOCATION);
        // 目录不存在则新建,如果新建失败则终止
        boolean createDiskDirFail = !file.exists() && !file.mkdirs();
        if (createDiskDirFail) {
            throw new RuntimeException("临时文件目录创建失败");
        }
        String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        newFilePath = Paths.get(file.getPath() + UUID.randomUUID() + fileSuffix);
        // 使用缓冲流,加快读写
        tempOutStream = new BufferedOutputStream(Files.newOutputStream(newFilePath));

        // 3.往模板中写数据,并将写好的文件写入到磁盘临时文件
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
            // 用于指定生成 Excel 文件的路径
            .file(tempOutStream)
            // 指定模板位置
            .withTemplate(classPathResource.getInputStream()).build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        FillConfig fillConfig = FillConfig.builder().build();
        excelWriter.fill(new FillWrapper("data", writeData), fillConfig, writeSheet);
        excelWriter.finish();

        // 4.上传oss
        tempInStream = new BufferedInputStream(Files.newInputStream(newFilePath));
        String fileUrl = this.aliOssTemplate.uploadFile(tempInStream, classPathResource.getFilename());
        log.info("文件上传成功,地址:{}", fileUrl);
        return fileUrl;

    } finally {
        // 5.最后再删掉磁盘上临时文件
        assert newFilePath != null;
        Files.delete(newFilePath);
        assert tempOutStream != null;
        tempOutStream.close();
        assert tempInStream != null;
        tempInStream.close();
    }

}
public String uploadFile(InputStream inputStream, String originalFileName) {

    if (StringUtils.isEmpty(originalFileName)) {
        throw new RuntimeException("文件名不能为空");
    }

    // 1.重命名文件,oss目录是 2021-9-10/uuid.文件后缀
    String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf("."));
    String newFileName = UUID.randomUUID().toString().replace("-", "");
    String renameFileName = LocalDate.now() + "/" + newFileName + fileSuffix;

    // 2.文件上传
    OSS ossClient = null;
    try {
        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, renameFileName, inputStream);
        ossClient.putObject(putObjectRequest);
    } finally {
        assert ossClient != null;
        ossClient.shutdown();
    }

    // 3.组装获取返回的url
    //https://classroomsys.oss-cn-beijing.aliyuncs.com/objectdir/a.png
    return "https://" + bucketName + "." + endpoint + "/" + renameFileName;
}
CREATE TABLE `export_file_flow`  (
    `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
    `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '文件名',
    `file_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件url',
    `export_status` int(0) NULL DEFAULT 2 COMMENT '导出状态:1处理中,2处理成功,3处理失败',
    `fail_reason` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '导出失败原因',
    `export_consume_time` bigint(0) NULL DEFAULT NULL COMMENT '导出耗时(秒)',
    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
    `operate_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 244 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文件下载列表' ROW_FORMAT = Dynamic;

标签:异步,null,String,utf8mb4,EasyExcel,excel,exportFileFlow,new,NULL
From: https://www.cnblogs.com/party-abu/p/17584198.html

相关文章

  • 这可能是前端处理excel最好的工具了
    大家好,我是程序视点的小二哥!今天小二哥要分享的是一个纯前端实现读取和导出excel文件的工具库:ExcelJSExcelJs简介功能十分简单:读取,操作并写入电子表格数据和样式到XLSX和JSON文件。一个Excel电子表格文件逆向工程项目。在本文中,我们使用xlsx文件。xlsx是Microsoft......
  • 浅谈Excel开发:十 Excel 开发中与线程相关的若干问题
    采用VSTO或者SharedAdd-in等技术开发Excel插件,其实是在与Excel提供的API在打交道,Excel本身的组件大多数都是COM组件,也就是说通过ExcelPIA来与COM进行交互。这其中会存在一些问题,这些问题如果处理不好,通常会导致在运行的时候会抛出难以调试的COM异常,从而导致我们开发出的Excel插......
  • 浅谈Excel开发:三 Excel 对象模型
    前一篇文章介绍了Excel中的菜单系统,在创建完菜单和工具栏之后,就要着手进行功能的开发了。不论您采用何种方式来开发Excel应用程序,了解Excel对象模型尤其重要,这些对象是您与Excel进行交互的基石。据不完全统计,Excel的对象模型中有270多个对象及超过5000多个属性和方法。通过这些对......
  • 浅谈Excel开发:六 Excel 异步自定义函数
    上文介绍了Excel中的自定义函数(UDF),它极大地扩展了Excel插件的功能,使得我们可以将业务逻辑以Excel函数的形式表示,并可以根据这些细粒度的自定义函数,构建各种复杂的分析报表。普通的UDF自定义函数的基本执行逻辑是,Excel接受用户输入的函数表达式,然后通过UDF函数的处理逻辑进行处......
  • 浅谈Excel开发:七 Excel 自定义任务窗体
    前面花了三篇文章讲解了Excel中的UDF函数,RTD函数和异步UDF函数,这些都是Excel开发中的重中之重。本文现在开始接着第二篇文章的菜单系统开始讲解Excel中可供开发的界面元素,本文要讲解的是Excel中的自定义任务面板(CustomeTaskPanel,CTP)。自定义任务面板在Office2003中就引入了......
  • 浅谈Excel开发:八 Excel 项目的安装部署
    前面几篇文章讲解了Excel开发的几个比较主要的也是比较重要的方面,比如菜单系统,Excel对象模型,自定义函数,RTD函数,异步自定义函数,用户自定义任务面板等,在实际开发中我们还会遇到各种“千奇百怪”的问题,以及开发中的一些注意事项和技巧等,后面有空我会写文介绍。当我们的Excel外接应用......
  • excel 表格操作
    1.工作簿工作表工作簿:表示整个excle文件工作表:工作簿(里面可以有很多工作表)2.3.4.5.6.7.8.......
  • bartender通过查询的方式打印Excel数据
    使用bartender打印标签,根据弹出的对话框提示输入material值,如57616136. 设置如下:(下图二选一) ......
  • excel导出报错
    Causedby:java.lang.NoSuchMethodError:org.apache.poi.ss.usermodel.Cell.getCellType()Lorg/apache/poi/ss/usermodel/CellType;错误原因日志1现象,hutool5.7.3版本开发导出excel,发现有的无法导出,报错,错误日志如上2原因,poi版本冲突或有其他easy-poi版本过低,低于4.0.0导致......
  • python读取excel为什么是小数
    Python读取Excel为什么是小数在使用Python读取Excel文件时,经常会遇到一个问题:为什么读取的数据会以小数的形式显示,而不是原始的整数或文本呢?这个问题涉及到Python读取Excel的原理以及Excel中的数据类型的转换。Excel中的数据类型在Excel中,每个单元格都有自己的数据类型。常见的......