非同步導入導出Excel方案

来源:https://www.cnblogs.com/better-farther-world2099/archive/2022/04/06/16106579.html
-Advertisement-
Play Games

一、非同步導出Excel文件 1、設計思想 用戶無需在當前頁面等待導出結果,點擊導出按鈕後服務端即可返回前端提示用戶導出處理中請到下載中心查看結果。 具體業務文件導出實現由後臺非同步處理導出文件到騰訊COS存儲(有效期七天,到期自動刪除)。 用戶統一在下載中心菜單欄頁面中查看導出任務結果並下載文件。 2 ...


一、非同步導出Excel文件

1、設計思想

用戶無需在當前頁面等待導出結果,點擊導出按鈕後服務端即可返回前端提示用戶導出處理中請到下載中心查看結果。

具體業務文件導出實現由後臺非同步處理導出文件到騰訊COS存儲(有效期七天,到期自動刪除)。

用戶統一在下載中心菜單欄頁面中查看導出任務結果並下載文件。

2、技術組件

① EasyExcel   文檔地址:https://www.yuque.com/easyexcel/doc

② Redisson延遲隊列或xxl-job定時任務    (定時更新文件狀態為已過期)

③ 騰訊COS對象存儲

3、具體實現

① 導出文件記錄表

下載中心就是從這裡查數據下載文件

export_record

② 導出狀態枚舉 ExportStateEnum

public enum ExportStateEnum {

 FAILED(1,"失敗"),
 SUCCESS(2,"成功"),
 GOING(3,"進行中"),
 EXPIRED(4,"已過期"),
 ;

 private Integer value;
 private String msg;
}

③ 非同步導出工具類 (PS:有待優化)

@Slf4j
@Component
public class AsyncExcelUtil {
 @Autowired
 ExportRecordFeignClient exportRecordFeignClient;

 @Autowired
 COSClient cosClient;
 @Autowired
 ThreadPoolTaskExecutor taskExecutor;

 Long recordId;

 public ResponseData asyncExport(UserInfo userInfo, String fileName, Runnable r) {
        //1、資料庫初始化操作記錄
 ResponseData<Long> initResult = this.exportRecordInit(userInfo, fileName);
 if (Objects.nonNull(initResult) && Objects.equals(initResult.getCode(), ResponseEnum.SUCCESS.getCode())) {
            this.recordId = initResult.getData();
 taskExecutor.execute(r);
 return ResponseData.success("操作成功");
 }
        return ResponseData.fail("操作失敗");
 }


    /**
 * 查詢當前用戶下導出文件記錄數據
 *
 * @param entity
 */
 public ResponseData<List<ExportRecordEntity>> queryExportRecordList(ExportRecordEntity entity) {
        return exportRecordFeignClient.queryExportRecordList(entity);
 }

    /**
 * 初始化導入導出記錄表
 *
 * @param userInfo
 * @param fileName
 */
 public ResponseData<Long> exportRecordInit(UserInfo userInfo, String fileName) {
        //1、資料庫初始化操作記錄
 ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
 exportRecordEntity.setTenantId(Long.parseLong(userInfo.getUniversityId()));
 exportRecordEntity.setOpType(1);
 exportRecordEntity.setProgress(30);
 exportRecordEntity.setIsSuccess(2);
 exportRecordEntity.setExportFileName(fileName);
 exportRecordEntity.setCreatedId(Long.parseLong(userInfo.getEmployeeId()));
 exportRecordEntity.setCreatedName(userInfo.getUserName());
 exportRecordEntity.setCreatedTime(LocalDateTime.now());
 exportRecordEntity.setUpdatedTime(exportRecordEntity.getCreatedTime());
 return exportRecordFeignClient.exportInit(exportRecordEntity);
 }

    /**
 * 數據整理完畢更新進度
 *
 * @param recordId
 */
 public boolean exportDataComplete(Long recordId) {
        ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
 exportRecordEntity.setId(recordId);
 exportRecordEntity.setProgress(80);
 exportRecordEntity.setUpdatedTime(LocalDateTime.now());
 return exportRecordFeignClient.exportDataComplete(exportRecordEntity);
 }

    /**
 * 導出excel文件上傳到騰訊COS並更新導出操作結果
 *
 * @param data 數據列表
 * @param fileNm 文件名
 * @param sheetNm excel文件sheet名稱
 * @param <T>
 */
 public <T> boolean exportToCos(List<T> data, String fileNm, String sheetNm) {
        Either<String, String> exportResult = asyncExport1(recordId, data, fileNm, sheetNm);
 ExportRecordEntity exportRecordEntity = new ExportRecordEntity();
 exportRecordEntity.setId(recordId);
 exportRecordEntity.setUpdatedTime(LocalDateTime.now());
 if (exportResult.isLeft()) {
            exportRecordEntity.setIsSuccess(0);
 exportRecordEntity.setFailReason(exportResult.getLeft());
 } else {
            exportRecordEntity.setProgress(100);
 exportRecordEntity.setIsSuccess(1);
 exportRecordEntity.setExportFileUrl(exportResult.get());
 }
        return exportRecordFeignClient.exportSaveResult(exportRecordEntity);
 }


    /**
 * 導出excel文件上傳到騰訊COS
 *
 * @param data 數據列表
 * @param fileNm 文件名
 * @param sheetNm excel文件sheet名稱
 * @param <T>
 */
 public <T> Either<String, String> asyncExport1(Long recordId, List<T> data, String fileNm, String sheetNm) {
        if (Objects.isNull(data) || CollectionUtils.isEmpty(data)) {
            return Either.left("數據為空");
 } else {
            this.exportDataComplete(recordId);
 }
        String filePath = "";
 try {
            //導出操作
 String basePath = ResourceUtils.getURL("classpath:").getPath() + "static/";
 // 建立新的文件
 File fileExist = new File(basePath);
 // 文件夾不存在,則新建
 if (!fileExist.exists()) {
                fileExist.mkdirs();
 }
            String fileName = fileNm + "-" + System.currentTimeMillis() + ".xlsx";
 filePath = basePath + fileName;
 EasyExcel.write(filePath, data.get(0).getClass()).sheet(sheetNm).doWrite(data);
 // 指定要上傳的文件
 File localFile = new File(filePath);
 // 指定文件將要存放的存儲桶
 String bucketName = Constants.DOCUMENT_BUCKET;
 // 指定文件上傳到 COS 上的路徑,即對象鍵。例如對象鍵為folder/picture.jpg,則表示將文件 picture.jpg 上傳到 folder 路徑下
 String key = "temp/asyncexcel/" + fileName;
 PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, localFile);
 PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
 return Either.right(cosClient.getObjectUrl(bucketName, key).toString());
 } catch (Exception e) {
            log.error("非同步導出excel異常:", e);
 return Either.left(e.getMessage());
 } finally {
            removeTempFile(filePath);
 }
}


    /***************************** private私有方法 *********************************/
 private static void removeTempFile(String filePath) {
        File delFile = new File(filePath);
   if (delFile.exists()) {
        delFile.delete();
   }
 }

}

4、AsyncExcelUtil工具類具體使用示例

    @PostMapping(value = "/testAsyncExport", produces = {"application/json"})
    public ResponseData testAsyncExport(@RequestBody CommonRequestParam param) {
        UserInfo userInfo = param.getUserInfo();
        String fileName = "非同步導出測試文件";
        UserInfoParam userParam = new UserInfoParam();
        userParam.setUserName(userInfo.getUserName());
        userParam.setUserId(userInfo.getUserId());
        userParam.setModule("各自業務模塊名稱,如:直播數據");
        return asyncExcelUtil.asyncExport(userParam, fileName, () -> {
            //模擬封裝得到要導出的數據
            List<ExportVo> retVo = new ArrayList<>();
            for (int i = 0; i < 7000; i++) {
                ExportVo vo = new ExportVo();
                vo.setModule("商城");
                vo.setName("張三" + i);
                vo.setUserDept("部門" + i);
                vo.setWatchTotal("20");
                retVo.add(vo);
            }
            asyncExcelUtil.exportToCos(userParam, retVo, fileName, "直播測試數據");
        });
    }

參數說明

① userParam 記錄操作人信息

② fileName 文件名稱

③ 行為參數化 不需要每個業務再創建對應的實現類,Lamda表達式代替內名內部類實現靈活。Runnable 接收傳入線程池非同步執行。

5、優化點:子線程異常處理

由於上面的實現如果非同步子線程中發生異常是會直接退出的,無法記錄任何日誌,如果讓業務方自己添加try catch模塊有可能會造成疏漏而且也不方便。

優化方案:為線程設置“未捕獲異常處理器”UncaughtExceptionHandler

在子線程中多添加一行固定代碼設置當前線程的異常處理器:

Thread.currentThread().setUncaughtExceptionHandler(new CustomThreadExceptionHandler());
public class CustomThreadExceptionHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t,Throwable e) {
        //處理 記錄到庫數據獲取異常
    }
}

6、非同步導入Excel方案

實現思路整合和非同步導出一致,在下載中心列表中區分導入和導出的操作,並且導入操作須記錄能夠直接跳轉到對應業務菜單頁面去,能夠下載導入錯誤數據的excel文件。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 一.簡介 二.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 OpenGL (ES) 學習路 ...
  • 當經歷了無數的日日夜夜,朝九晚九,攻剋了無數難關,終於將系統預定功能開發完成,通過測試,部署上線後。你是否會感覺志得意滿,到達了人生巔峰,高唱無敵是多麼寂寞。 現實情況是,如果你這個系統,業務沒有做起來,沒啥人用,huan則罷liao。如果有越來越多的人,持續使用。隨著用戶增多,業務數據增多,那系統 ...
  • 位元組流寫數據一共有兩種方式 throws直接拋出異常 try...catch進行異常處理,在位元組流中因為考慮到需要釋放資源,要在此基礎上加入finally塊進行釋放資源 finally:在異常處理時提供finally塊來執行所有的清楚操作。比如說IO流中的close()方法,釋放資源 特點:被fin ...
  • 今天來一個好玩一點的,汽車已經能夠自動駕駛了,Python怎麼能沒有呢?這不,必須安排上。 一、安裝環境 gym是用於開發和比較強化學習演算法的工具包,在python中安裝gym庫和其中子場景都較為簡便。 安裝gym: pip install gym 安裝自動駕駛模塊,這裡使用Edouard Leur ...
  • 使用easyExcel寫出信息到excel文件出現只有表頭沒有數據的現象。 參考來源 https://www.cnblogs.com/jeanfear/p/13409792.html 原因是我javabean上用到了@Data註解,並且裡面的欄位名是類似fUserName這種格式。 我猜測,第二個字 ...
  • 最近女朋友在玩連連看,玩了一個星期了還沒通關,真的是菜。 我實在是看不過去了,直接用python寫了個腳本代碼,一分鐘一把游戲。 快是快,就是聯網玩容易被罵,嘿嘿~ 直接上代碼 模塊導入 import cv2 import numpy as np import win32api import win ...
  • 前言 作為目前全世界最大的視頻網站,它幾乎全是用Python來寫的該網站當前行業內線上視頻服務提供商,該網站的系統每天要處理上千萬個視頻片段,為全球成千上萬的用戶提供高水平的視頻上傳、分發、展示、瀏覽服務。2015年2月,央視首次把春晚推送到該網站。今天,我們就要用Python來快速批量下載該網站的 ...
  • 前言 最近疫情真的是非常嚴重,據“百度疫情實時大數據報告”2022年3月27日19點實時數據顯示,上海較昨日新增確診51例,新增無癥狀2633例,形勢嚴峻。 不少在上海的朋友們也尤為關註其所在地周邊的疫情確診情況,涌現了一些小程式幫助我們通過地圖查看周邊的疫情情況。 而今天的文章,我就來帶大家學習如 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...