微服务超大Excel文件导出方案优化

1、在导出Excel时经常会碰到文件过大,导出特别慢

2、微服务限制了请求超时时间,文件过大情况必然超时

优化思路:

1、文件过大时通过文件拆分、打包压缩zip,然后上传到oss,并设置有效期(30天过期)

2、把同步下载改成异步

3、文件生成完成,更新任务状态

4、通过oss的下载链接下载文件

5、多模块复用,只需要实现各自查询Excel业务数据的接口即可

以下是部分参考代码:

java 复制代码
public class TxnETExecutor extends AbstractExportTaskExecutor {
    private final MposScene mposScene;
    private final OssService ossService;

    @Override
    public boolean support(int taskType) {
        return ExportTaskType.MPOS_TXN_BILL_EXPORT.ordinal() == taskType;
    }

    @Override
    public int getFileSize(String reqBody, String audClientId, Long groupId) {
        TxnMposBillItemFindReq req = JSON.parseObject(reqBody, TxnMposBillItemFindReq.class);
        req.setAudClientId(audClientId);
        req.setLoginGroupId(groupId);
        req.setPage(1);
        req.setPageSize(1);
        req.setDiffStatus(true);
        Pageable pageable = mposScene.findByChannelBillItem(req);
        return pageable.getTotalPages().intValue();
    }

    public void execute(Long taskId, int fileSize, String reqBody, String audClientId, Long groupId) {
        TxnMposBillItemFindReq req = JSON.parseObject(reqBody, TxnMposBillItemFindReq.class);
        req.setAudClientId(audClientId);
        req.setLoginGroupId(groupId);
        req.setPage(1);
        req.setDiffStatus(true);
        int pageSize = Math.min(fileSize, DEFAULT_PAGE_SIZE);
        req.setPageSize(pageSize);
        Pageable pageable = mposScene.findByChannelBillItem(req);
        Long totalPages = pageable.getTotalPages();
        List<Map<String, Object>> first = parse(pageable.getItems());
        ByteArrayOutputStream outputStream = WebFluxUtils.buildExcelOutputStream(first, null);
        if (totalPages <= 1) {
            OssPutObjectResp objectResp = upload(audClientId, "xlsx", new ByteArrayInputStream(outputStream.toByteArray()));
            String fileUrl = objectResp.getFileUrl();
            update(CashierExportTaskUpdateReq.newBuilder().setId(taskId).setFileStatus(ExportTaskFileStatus.SUCCESS.ordinal()).setFileFormat("xlsx").setFileProgressRate("100%").setFileUrl(fileUrl).build());
            return;
        }
        Map<String, ByteArrayOutputStream> files = new HashMap<>();
        files.put(String.format("渠道对账单[%s-%s].xlsx", 1, totalPages), outputStream);
        for (int i = 2; i <= totalPages; i++) {
            req.setPage(i);
            Pageable pageableExportMore = mposScene.findByChannelBillItem(req);
            ByteArrayOutputStream j = WebFluxUtils.buildExcelOutputStream(parse(pageableExportMore.getItems()), null);
            files.put(String.format("渠道对账单[%s-%s].xlsx", i, totalPages), j);
        }
        Path tmp = zip(files);
        if (tmp == null) {
            update(CashierExportTaskUpdateReq.newBuilder().setId(taskId).setFileStatus(ExportTaskFileStatus.FAIL.ordinal()).setFileFormat("zip").setFileProgressRate("100%").build());
            return;
        }
        InputStream inputStream;
        try {
            inputStream = Files.newInputStream(tmp);
        } catch (IOException e) {
            e.printStackTrace();
            update(CashierExportTaskUpdateReq.newBuilder().setId(taskId).setFileStatus(ExportTaskFileStatus.FAIL.ordinal()).setFileFormat("zip").setFileProgressRate("100%").build());
            return;
        }
        try {
            OssPutObjectResp objectResp = upload(audClientId, "zip", inputStream);
            update(CashierExportTaskUpdateReq.newBuilder().setId(taskId).setFileStatus(ExportTaskFileStatus.SUCCESS.ordinal()).setFileFormat("zip").setFileProgressRate("100%").setFileUrl(objectResp.getFileUrl()).build());
        } catch (Exception e) {
            e.printStackTrace();
            update(CashierExportTaskUpdateReq.newBuilder().setId(taskId).setFileStatus(ExportTaskFileStatus.FAIL.ordinal()).setFileFormat("zip").setFileProgressRate("100%").build());
        } finally {
            try {
                Files.delete(tmp);
            } catch (IOException ignore) {
            }
        }


    }

    private List<Map<String, Object>> parse(Collection<TxnMposBillItemFindResp> items) {
        List<Map<String, Object>> result = new ArrayList<>();
        if (items == null || items.isEmpty()) {
            throw FibException.ofNotFound("暂无数据");
        }
        items.forEach(item -> {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("创建时间", item.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("批次号", item.getBatchNo());
            map.put("交易时间", item.getTransTime());
            map.put("交易月份", item.getTransMonth());
            map.put("平台订单号", item.getServerOrderId());
            ......
            result.add(map);
        });
        return result;
    }

    private OssPutObjectResp upload(String audClientId, String suffix, InputStream inputStream) {
        return ossService.putObject(OssPutObjectReq.newBuilder()
                .setBucketName(DEFAULT_BUCKET)
                .setAudClientId(audClientId)
                .setDirectoryPath("***")
                .setSuffix(suffix)
                .build(), inputStream);
    }
}

效果图:

相关推荐
SL-staff2 分钟前
规则引擎如何实现风控报告的自动化?JVS-Rules提供解决方案
大数据·自动化·excel·规则引擎·jvs-rules·风控报告·数据溯源
Yyyyyy~4 分钟前
【java】运算符
java
古法安卓13 分钟前
Android-切换白天黑夜内存不足问题排查
android·java·android studio
Dreams_l20 分钟前
如何保证RabbitMQ消息可靠传输
java·rabbitmq·java-rabbitmq
MacroZheng22 分钟前
面试官皱眉:"你懂 Vibe Coding,那你说superpowers和grill-me怎么选?",我:"小孩才做选择,我全都要!"
java·人工智能·后端
大模型码小白24 分钟前
【AI大模型】DeepSeek Harness 深度解析:大模型评测框架的架构与实践
java·运维·人工智能·spring·架构·自动化
Mr.Java.30 分钟前
本地测试全绿,上线 SQL 报错?Spring Boot 容器级 DT 测试实战指南(附Skills)
java·测试用例·springboot·testcontainers·dt·java测试skills·dt测试
2501_9336707941 分钟前
内容电商运营岗位能力模型:SQL、Excel、投放数据与AI工具怎么准备
人工智能·sql·excel
维天说1 小时前
Agent会听人话,反而更难管
java·开发语言·人工智能
SamDeepThinking1 小时前
if嵌套最好控制在3层以内
java·后端·程序员