找回密码
 立即注册
首页 业界区 业界 Excel高性能异步导出完整方案!

Excel高性能异步导出完整方案!

能氐吨 3 天前
前言

在大型电商系统中,数据导出是一个高频且重要的功能需求。
传统的同步导出方式在面对大数据量时往往会导致请求超时、内存溢出等问题,严重影响用户体验。
苏三商城项目创新性地设计并实现了一套完整的Excel异步导出机制,通过注解驱动、任务队列、定时调度、消息通知等技术手段,完美解决了大数据量导出的技术难题,成为项目的重要技术亮点。
系统架构设计

整体架构图

1.webp

核心组件说明


  • 注解驱动层:通过@ExcelExport注解实现声明式编程
  • 切面处理层:CommonTaskAspect负责拦截和任务创建
  • 任务管理层:ExcelExportTask执行具体的导出逻辑
  • 调度引擎层:基于Quartz的定时任务调度
  • 消息通知层:RocketMQ + WebSocket实现异步通知
  • 存储层:MySQL存储任务状态,OSS存储导出文件
异步导出流程详解

完整流程图

2.webp

关键步骤分析

1. 注解驱动任务创建
  1. @ExcelExport(ExcelBizTypeEnum.USER)
  2. @ApiOperation(notes = "导出用户数据", value = "导出用户数据")
  3. @PostMapping("/export")
  4. public void export(HttpServletResponse response, UserConditionEntity userConditionEntity) {
  5.     // 方法体可以为空,切面会自动处理
  6. }
复制代码
设计亮点

  • 声明式编程:通过注解实现功能声明,代码简洁
  • 零侵入性:业务方法无需修改,切面自动处理
  • 类型安全:通过枚举确保业务类型的正确性
2. 切面拦截与任务创建
  1. @Aspect
  2. @Component
  3. public class CommonTaskAspect {
  4.    
  5.     @Before("@annotation(cn.net.susan.annotation.ExcelExport)")
  6.     public void before(JoinPoint joinPoint) throws Throwable {
  7.         // 获取注解信息
  8.         ExcelBizTypeEnum excelBizTypeEnum = method.getAnnotation(ExcelExport.class).value();
  9.         
  10.         // 创建任务实体
  11.         CommonTaskEntity commonTaskEntity = createCommonTaskEntity(excelBizTypeEnum);
  12.         
  13.         // 保存任务到数据库
  14.         commonTaskMapper.insert(commonTaskEntity);
  15.     }
  16. }
复制代码
技术特色

  • AOP切面编程:实现横切关注点的分离
  • 反射机制:动态获取注解信息和方法参数
  • 任务持久化:将任务信息保存到数据库,确保可靠性
3. 定时任务调度机制
  1. @Component
  2. public class CommonTaskJob extends BaseJob {
  3.    
  4.     @Override
  5.     public JobResult doRun(String params) {
  6.         // 查询待执行任务
  7.         CommonTaskConditionEntity condition = new CommonTaskConditionEntity();
  8.         condition.setStatusList(Arrays.asList(
  9.             TaskStatusEnum.WAITING.getValue(),
  10.             TaskStatusEnum.RUNNING.getValue()
  11.         ));
  12.         
  13.         List<CommonTaskEntity> tasks = commonTaskMapper.searchByCondition(condition);
  14.         
  15.         // 执行任务
  16.         for (CommonTaskEntity task : tasks) {
  17.             AsyncTaskStrategyContextFactory.getInstance()
  18.                 .getStrategy(task.getType())
  19.                 .doTask(task);
  20.         }
  21.         
  22.         return JobResult.SUCCESS;
  23.     }
  24. }
复制代码
核心机制

  • 定时扫描:通过Quartz定时扫描任务队列
  • 策略模式:根据任务类型选择对应的处理器
  • 并发处理:支持多个任务并发执行
4. 异步任务处理器
  1. @AsyncTask(TaskTypeEnum.EXPORT_EXCEL)
  2. @Service
  3. public class ExcelExportTask implements IAsyncTask {
  4.    
  5.     @Override
  6.     public void doTask(CommonTaskEntity commonTaskEntity) {
  7.         try {
  8.             // 1. 更新任务状态为执行中
  9.             commonTaskEntity.setStatus(TaskStatusEnum.RUNNING.getValue());
  10.             commonTaskMapper.update(commonTaskEntity);
  11.             
  12.             // 2. 获取业务类型和请求参数
  13.             ExcelBizTypeEnum excelBizTypeEnum = getExcelBizTypeEnum(commonTaskEntity.getBizType());
  14.             String requestParam = commonTaskEntity.getRequestParam();
  15.             Object toBean = JSONUtil.toBean(requestParam, aClass);
  16.             
  17.             // 3. 获取对应的Service并执行导出
  18.             String serviceName = this.getServiceName(requestEntity);
  19.             BaseService baseService = (BaseService) SpringBeanUtil.getBean(serviceName);
  20.             String fileName = getFileName(excelBizTypeEnum.getDesc());
  21.             String fileUrl = baseService.export(toBean, fileName, this.getEntityName(requestEntity));
  22.             
  23.             // 4. 更新任务状态为成功
  24.             commonTaskEntity.setFileUrl(fileUrl);
  25.             commonTaskEntity.setStatus(TaskStatusEnum.SUCCESS.getValue());
  26.             
  27.         } catch (Exception e) {
  28.             // 5. 处理失败情况
  29.             handleTaskFailure(commonTaskEntity, e);
  30.         } finally {
  31.             // 6. 更新任务记录并发送通知
  32.             commonTaskMapper.update(commonTaskEntity);
  33.             sendNotifyMessage(commonTaskEntity);
  34.         }
  35.     }
  36. }
复制代码
处理流程

  • 状态管理:完整的任务状态流转(等待→执行中→成功/失败)
  • 异常处理:完善的异常捕获和失败重试机制
  • 动态调用:通过反射动态获取Service实例
  • 通知机制:任务完成后自动发送通知
5. 消息通知机制
  1. @RocketMQMessageListener(
  2.     topic = "${mall.mgt.excelExportTopic:EXCEL_EXPORT_TOPIC}",
  3.     consumerGroup = "${mall.mgt.excelExportGroup:EXCEL_EXPORT_GROUP}"
  4. )
  5. @Component
  6. public class ExcelExportConsumer implements RocketMQListener<MessageExt> {
  7.    
  8.     @Override
  9.     public void onMessage(MessageExt message) {
  10.         String content = new String(message.getBody());
  11.         CommonNotifyEntity commonNotifyEntity = JSONUtil.toBean(content, CommonNotifyEntity.class);
  12.         pushNotify(commonNotifyEntity);
  13.     }
  14.    
  15.     private void pushNotify(CommonNotifyEntity commonNotifyEntity) {
  16.         // 通过WebSocket推送通知
  17.         WebSocketServer.sendMessage(commonNotifyEntity);
  18.         
  19.         // 更新通知状态
  20.         commonNotifyEntity.setIsPush(1);
  21.         commonNotifyMapper.update(commonNotifyEntity);
  22.     }
  23. }
复制代码
通知特色

  • 异步解耦:通过消息队列实现系统解耦
  • 实时推送:WebSocket确保用户及时收到通知
  • 可靠性保证:消息队列确保通知的可靠传递
技术架构亮点

1. 策略模式 + 工厂模式
  1. public class AsyncTaskStrategyContextFactory {
  2.     private static Map<Integer, IAsyncTask> asyncTaskMap;
  3.    
  4.     public IAsyncTask getStrategy(Integer taskType) {
  5.         return asyncTaskMap.get(taskType);
  6.     }
  7. }
复制代码
设计优势

  • 扩展性强:新增任务类型只需实现IAsyncTask接口
  • 维护性好:每种任务类型独立实现,互不影响
  • 配置灵活:通过工厂模式统一管理任务策略
2. 注解驱动编程
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ExcelExport {
  4.     ExcelBizTypeEnum value();
  5. }
复制代码
编程范式

  • 声明式编程:通过注解声明功能,而非命令式实现
  • 元数据驱动:注解携带的元数据驱动系统行为
  • 代码简洁:业务代码保持简洁,关注点分离
3. 异步任务状态机

3.webp

状态管理

  • 状态流转:清晰的状态转换逻辑
  • 重试机制:失败任务自动重试,提高成功率
  • 状态持久化:任务状态持久化到数据库
4. 分页大数据处理

[code]private String doExport(V v, String fileName, String clazzName) {    RequestConditionEntity conditionEntity = (RequestConditionEntity) v;        // 计算分页参数    int totalCount = getBaseMapper().searchCount(conditionEntity);    int sheetCount = totalCount % sheetDataSize == 0 ?         totalCount / sheetDataSize : totalCount / sheetDataSize + 1;        // 创建ExcelWriter    ExcelWriter excelWriter = EasyExcel.write(file).build();        // 分页处理数据    for (int sheetIndex = 1; sheetIndex

相关推荐

昨天 11:19

举报

新版吗?好像是停更了吧。
您需要登录后才可以回帖 登录 | 立即注册