Excel高性能異步導(dǎo)出完整方案!
前言
在大型電商系統(tǒng)中,數(shù)據(jù)導(dǎo)出是一個高頻且重要的功能需求。
傳統(tǒng)的同步導(dǎo)出方式在面對大數(shù)據(jù)量時往往會導(dǎo)致請求超時、內(nèi)存溢出等問題,嚴(yán)重影響用戶體驗。
蘇三商城項目創(chuàng)新性地設(shè)計并實現(xiàn)了一套完整的Excel異步導(dǎo)出機制,通過注解驅(qū)動、任務(wù)隊列、定時調(diào)度、消息通知等技術(shù)手段,完美解決了大數(shù)據(jù)量導(dǎo)出的技術(shù)難題,成為項目的重要技術(shù)亮點。
系統(tǒng)架構(gòu)設(shè)計
整體架構(gòu)圖

核心組件說明
- 注解驅(qū)動層:通過
@ExcelExport注解實現(xiàn)聲明式編程 - 切面處理層:
CommonTaskAspect負(fù)責(zé)攔截和任務(wù)創(chuàng)建 - 任務(wù)管理層:
ExcelExportTask執(zhí)行具體的導(dǎo)出邏輯 - 調(diào)度引擎層:基于Quartz的定時任務(wù)調(diào)度
- 消息通知層:RocketMQ + WebSocket實現(xiàn)異步通知
- 存儲層:MySQL存儲任務(wù)狀態(tài),OSS存儲導(dǎo)出文件
異步導(dǎo)出流程詳解
完整流程圖

關(guān)鍵步驟分析
1. 注解驅(qū)動任務(wù)創(chuàng)建
@ExcelExport(ExcelBizTypeEnum.USER)
@ApiOperation(notes = "導(dǎo)出用戶數(shù)據(jù)", value = "導(dǎo)出用戶數(shù)據(jù)")
@PostMapping("/export")
public void export(HttpServletResponse response, UserConditionEntity userConditionEntity) {
// 方法體可以為空,切面會自動處理
}
設(shè)計亮點:
- 聲明式編程:通過注解實現(xiàn)功能聲明,代碼簡潔
- 零侵入性:業(yè)務(wù)方法無需修改,切面自動處理
- 類型安全:通過枚舉確保業(yè)務(wù)類型的正確性
2. 切面攔截與任務(wù)創(chuàng)建
@Aspect
@Component
public class CommonTaskAspect {
@Before("@annotation(cn.net.susan.annotation.ExcelExport)")
public void before(JoinPoint joinPoint) throws Throwable {
// 獲取注解信息
ExcelBizTypeEnum excelBizTypeEnum = method.getAnnotation(ExcelExport.class).value();
// 創(chuàng)建任務(wù)實體
CommonTaskEntity commonTaskEntity = createCommonTaskEntity(excelBizTypeEnum);
// 保存任務(wù)到數(shù)據(jù)庫
commonTaskMapper.insert(commonTaskEntity);
}
}
技術(shù)特色:
- AOP切面編程:實現(xiàn)橫切關(guān)注點的分離
- 反射機制:動態(tài)獲取注解信息和方法參數(shù)
- 任務(wù)持久化:將任務(wù)信息保存到數(shù)據(jù)庫,確保可靠性
3. 定時任務(wù)調(diào)度機制
@Component
public class CommonTaskJob extends BaseJob {
@Override
public JobResult doRun(String params) {
// 查詢待執(zhí)行任務(wù)
CommonTaskConditionEntity condition = new CommonTaskConditionEntity();
condition.setStatusList(Arrays.asList(
TaskStatusEnum.WAITING.getValue(),
TaskStatusEnum.RUNNING.getValue()
));
List<CommonTaskEntity> tasks = commonTaskMapper.searchByCondition(condition);
// 執(zhí)行任務(wù)
for (CommonTaskEntity task : tasks) {
AsyncTaskStrategyContextFactory.getInstance()
.getStrategy(task.getType())
.doTask(task);
}
return JobResult.SUCCESS;
}
}
核心機制:
- 定時掃描:通過Quartz定時掃描任務(wù)隊列
- 策略模式:根據(jù)任務(wù)類型選擇對應(yīng)的處理器
- 并發(fā)處理:支持多個任務(wù)并發(fā)執(zhí)行
4. 異步任務(wù)處理器
@AsyncTask(TaskTypeEnum.EXPORT_EXCEL)
@Service
public class ExcelExportTask implements IAsyncTask {
@Override
public void doTask(CommonTaskEntity commonTaskEntity) {
try {
// 1. 更新任務(wù)狀態(tài)為執(zhí)行中
commonTaskEntity.setStatus(TaskStatusEnum.RUNNING.getValue());
commonTaskMapper.update(commonTaskEntity);
// 2. 獲取業(yè)務(wù)類型和請求參數(shù)
ExcelBizTypeEnum excelBizTypeEnum = getExcelBizTypeEnum(commonTaskEntity.getBizType());
String requestParam = commonTaskEntity.getRequestParam();
Object toBean = JSONUtil.toBean(requestParam, aClass);
// 3. 獲取對應(yīng)的Service并執(zhí)行導(dǎo)出
String serviceName = this.getServiceName(requestEntity);
BaseService baseService = (BaseService) SpringBeanUtil.getBean(serviceName);
String fileName = getFileName(excelBizTypeEnum.getDesc());
String fileUrl = baseService.export(toBean, fileName, this.getEntityName(requestEntity));
// 4. 更新任務(wù)狀態(tài)為成功
commonTaskEntity.setFileUrl(fileUrl);
commonTaskEntity.setStatus(TaskStatusEnum.SUCCESS.getValue());
} catch (Exception e) {
// 5. 處理失敗情況
handleTaskFailure(commonTaskEntity, e);
} finally {
// 6. 更新任務(wù)記錄并發(fā)送通知
commonTaskMapper.update(commonTaskEntity);
sendNotifyMessage(commonTaskEntity);
}
}
}
處理流程:
- 狀態(tài)管理:完整的任務(wù)狀態(tài)流轉(zhuǎn)(等待→執(zhí)行中→成功/失敗)
- 異常處理:完善的異常捕獲和失敗重試機制
- 動態(tài)調(diào)用:通過反射動態(tài)獲取Service實例
- 通知機制:任務(wù)完成后自動發(fā)送通知
5. 消息通知機制
@RocketMQMessageListener(
topic = "${mall.mgt.excelExportTopic:EXCEL_EXPORT_TOPIC}",
consumerGroup = "${mall.mgt.excelExportGroup:EXCEL_EXPORT_GROUP}"
)
@Component
public class ExcelExportConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt message) {
String content = new String(message.getBody());
CommonNotifyEntity commonNotifyEntity = JSONUtil.toBean(content, CommonNotifyEntity.class);
pushNotify(commonNotifyEntity);
}
private void pushNotify(CommonNotifyEntity commonNotifyEntity) {
// 通過WebSocket推送通知
WebSocketServer.sendMessage(commonNotifyEntity);
// 更新通知狀態(tài)
commonNotifyEntity.setIsPush(1);
commonNotifyMapper.update(commonNotifyEntity);
}
}
通知特色:
- 異步解耦:通過消息隊列實現(xiàn)系統(tǒng)解耦
- 實時推送:WebSocket確保用戶及時收到通知
- 可靠性保證:消息隊列確保通知的可靠傳遞
技術(shù)架構(gòu)亮點
1. 策略模式 + 工廠模式
public class AsyncTaskStrategyContextFactory {
private static Map<Integer, IAsyncTask> asyncTaskMap;
public IAsyncTask getStrategy(Integer taskType) {
return asyncTaskMap.get(taskType);
}
}
設(shè)計優(yōu)勢:
- 擴(kuò)展性強:新增任務(wù)類型只需實現(xiàn)
IAsyncTask接口 - 維護(hù)性好:每種任務(wù)類型獨立實現(xiàn),互不影響
- 配置靈活:通過工廠模式統(tǒng)一管理任務(wù)策略
2. 注解驅(qū)動編程
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelExport {
ExcelBizTypeEnum value();
}
編程范式:
- 聲明式編程:通過注解聲明功能,而非命令式實現(xiàn)
- 元數(shù)據(jù)驅(qū)動:注解攜帶的元數(shù)據(jù)驅(qū)動系統(tǒng)行為
- 代碼簡潔:業(yè)務(wù)代碼保持簡潔,關(guān)注點分離
3. 異步任務(wù)狀態(tài)機

狀態(tài)管理:
- 狀態(tài)流轉(zhuǎn):清晰的狀態(tài)轉(zhuǎn)換邏輯
- 重試機制:失敗任務(wù)自動重試,提高成功率
- 狀態(tài)持久化:任務(wù)狀態(tài)持久化到數(shù)據(jù)庫
4. 分頁大數(shù)據(jù)處理
private String doExport(V v, String fileName, String clazzName) {
RequestConditionEntity conditionEntity = (RequestConditionEntity) v;
// 計算分頁參數(shù)
int totalCount = getBaseMapper().searchCount(conditionEntity);
int sheetCount = totalCount % sheetDataSize == 0 ?
totalCount / sheetDataSize : totalCount / sheetDataSize + 1;
// 創(chuàng)建ExcelWriter
ExcelWriter excelWriter = EasyExcel.write(file).build();
// 分頁處理數(shù)據(jù)
for (int sheetIndex = 1; sheetIndex <= sheetCount; sheetIndex++) {
List<K> dataEntities = getBaseMapper().searchByCondition(conditionEntity);
// 寫入數(shù)據(jù)到Sheet
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetIndex)
.head(Class.forName(clazzName)).build();
excelWriter.write(dataEntities, writeSheet);
conditionEntity.setPageNo(conditionEntity.getPageNo() + 1);
}
excelWriter.finish();
return uploadToOSS(file);
}
處理策略:
- 內(nèi)存優(yōu)化:分頁查詢避免大量數(shù)據(jù)加載到內(nèi)存
- 流式處理:使用EasyExcel的流式API
- 多Sheet支持:大數(shù)據(jù)自動分割到多個Sheet
- 進(jìn)度可控:分頁處理便于監(jiān)控和中斷
技術(shù)優(yōu)勢
1. 用戶體驗優(yōu)勢
- 即時響應(yīng):用戶請求后立即返回,無需等待
- 實時通知:通過WebSocket實時推送導(dǎo)出結(jié)果
- 進(jìn)度可見:用戶可以實時查看導(dǎo)出進(jìn)度
- 錯誤友好:導(dǎo)出失敗時提供詳細(xì)的錯誤信息
2. 系統(tǒng)性能優(yōu)勢
- 高并發(fā):異步處理支持高并發(fā)導(dǎo)出請求
- 資源優(yōu)化:分頁處理避免內(nèi)存溢出
- 負(fù)載均衡:任務(wù)隊列支持負(fù)載均衡
- 可擴(kuò)展性:支持水平擴(kuò)展和垂直擴(kuò)展
3. 開發(fā)維護(hù)優(yōu)勢
- 代碼簡潔:注解驅(qū)動,業(yè)務(wù)代碼簡潔
- 易于擴(kuò)展:新增業(yè)務(wù)類型只需添加注解
- 統(tǒng)一管理:所有導(dǎo)出任務(wù)統(tǒng)一管理和監(jiān)控
- 錯誤處理:完善的異常處理和重試機制
4. 運維管理優(yōu)勢
- 任務(wù)監(jiān)控:完整的任務(wù)執(zhí)行監(jiān)控
- 狀態(tài)管理:清晰的任務(wù)狀態(tài)流轉(zhuǎn)
- 日志記錄:詳細(xì)的操作日志記錄
- 告警機制:完善的異常告警機制
總結(jié)
蘇三商城的Excel異步導(dǎo)出機制是一個設(shè)計精良、功能完善的企業(yè)級解決方案。
它通過以下技術(shù)手段實現(xiàn)了高效、穩(wěn)定、可擴(kuò)展的異步導(dǎo)出功能:
核心技術(shù)棧
- 注解驅(qū)動:
@ExcelExport注解實現(xiàn)聲明式編程 - AOP切面:
CommonTaskAspect實現(xiàn)橫切關(guān)注點分離 - 策略模式:
AsyncTaskStrategyContextFactory實現(xiàn)任務(wù)策略管理 - 定時調(diào)度:Quartz實現(xiàn)任務(wù)定時調(diào)度
- 消息隊列:RocketMQ實現(xiàn)異步通知
- 實時通信:WebSocket實現(xiàn)實時推送
- 文件存儲:OSS實現(xiàn)文件云端存儲
設(shè)計亮點
- 異步解耦:通過任務(wù)隊列實現(xiàn)請求與處理的解耦
- 狀態(tài)管理:完整的任務(wù)狀態(tài)流轉(zhuǎn)和持久化
- 錯誤處理:完善的異常處理和重試機制
- 性能優(yōu)化:分頁處理、流式寫入、內(nèi)存優(yōu)化
- 擴(kuò)展性強:支持業(yè)務(wù)類型、導(dǎo)出格式、通知方式的擴(kuò)展
- 監(jiān)控完善:完整的任務(wù)監(jiān)控和告警機制
業(yè)務(wù)價值
- 提升用戶體驗:異步處理避免長時間等待
- 提高系統(tǒng)性能:支持大數(shù)據(jù)量導(dǎo)出和高并發(fā)請求
- 降低開發(fā)成本:注解驅(qū)動減少重復(fù)代碼
- 便于運維管理:統(tǒng)一的任務(wù)管理和監(jiān)控
這套異步導(dǎo)出機制不僅解決了傳統(tǒng)同步導(dǎo)出的技術(shù)難題,還提供了良好的擴(kuò)展性和維護(hù)性,是蘇三商城項目的技術(shù)亮點之一,值得在其他項目中推廣和應(yīng)用。
最后說一句(求關(guān)注,別白嫖我)
如果這篇文章對您有所幫助,或者有所啟發(fā)的話,幫忙關(guān)注一下我的同名公眾號:蘇三說技術(shù),您的支持是我堅持寫作最大的動力。
求一鍵三連:點贊、轉(zhuǎn)發(fā)、在看。
關(guān)注公眾號:【蘇三說技術(shù)】,在公眾號中回復(fù):進(jìn)大廠,可以免費獲取我最近整理的10萬字的面試寶典,好多小伙伴靠這個寶典拿到了多家大廠的offer。
更多精彩內(nèi)容在我的技術(shù)網(wǎng)站:http://www.susan.net.cn

浙公網(wǎng)安備 33010602011771號