博客 / 詳情

返回

異步上傳石墨文件進度條前端展示記錄(採用Redis中String數據結構實現)

事件起因是客户現場需要從石墨文檔中獲取文件信息,文件信息存在存在多個,進行批量上傳。為了用户的友好型體驗,需要做進行條展示的方式,具體實現見下文.....

上傳流程介紹

石墨文檔支持從鏈接🔗方式獲取文件信息,通過對文件鏈接的截取,會得到16位編碼的值。同時不僅僅針對單文檔,也支持指定文件夾或者空間下的方式,只需要對對應的文檔或者文件夾📁或者空間添加對應的協作者(石墨文檔API下載需要私有化部署之後才可以提供,官方不帶開放平台)。就可以通過對應的下載文檔接口進行對應的邏輯下載,關於如何下載的大致流程如下圖所示。本次只介紹進度條邏輯如何實現,關於如何上傳本次文章不做展開。

服務流程示意圖

用户層面結合技術層面大致的交互流程是,用户首先從石墨文檔獲取需要上傳的文檔鏈接,進行錄入到對應的系統界面。調用後端服務的方式,由後端服務通過配置相關密鑰與協作者(石墨管理員)獲取相關文檔信息。將獲取到的相關文檔之後,在業務服務中進行與redis交互,存儲進入進度。

Talk is easy show me the code

redis存儲類實體類

public class RedisTaskProcess {
  @Schema(description = "消息id")
    private String taskId;
    
    @Schema(description = "業務類型,定義自己的任務類型xxxxx")
    private String businessType;
    
    @Schema(description = "處理進度的百分比")
    private BigDecimal processPercent;
    
    @Schema(description = "任務狀態, processing|completed|error|cancel")
    private String status = "processing";
    
    @Schema(description = "消息描述, 異常時或者特殊場景會指定消息")
    private String msg;
    
    @Schema(description = "用户編碼,用於獲取任務Key")
    private String userCode;
    
    @Schema(description = "任務標題(文件名)")
    private String title;
    
    @Schema(description = "創建時間")
    private Long createTime;
   /**
 * 啓動一個長任務處理流程,並返回任務處理響應對象。
 * 
 * @param businessType 業務類型,用於標識任務的業務場景。
 * @param userCode 用户代碼,標識發起任務的用户。
 * @param title 任務標題,用於描述任務內容。
 * @param redisTemplate Redis模板對象,用於將任務信息存儲到Redis中。
 * @return LongTaskProcessResponse 返回長任務處理響應對象,包含任務ID、狀態、進度等信息。
 */
public static RedisTaskProcess commonStart(String businessType, String userCode, String title, RedisTemplate<String, Object> redisTemplate) {
    // 初始化長任務處理響應對象
    RedisTaskProcess res = new RedisTaskProcess();
    
    // 設置任務ID、業務類型、狀態、消息、標題、進度、用户代碼和創建時間
    res.setTaskId(IdUtil.getSnowflakeNextIdStr());
    res.setBusinessType(businessType);
    res.setStatus("processing");
    res.setMsg("開始處理");
    res.setTitle(title);
    res.setProcessPercent(new BigDecimal(1));
    res.setUserCode(userCode);
    res.setCreateTime(System.currentTimeMillis());
    
    // 將任務信息存儲到Redis中,並設置過期時間為1天
    redisTemplate.opsForValue().set(res.findTaskCacheKey(), res, 1, TimeUnit.DAYS);
    
    return res;
}
    /**
 * 完成任務的通用方法,用於設置任務狀態為“已完成”,並更新相關信息到Redis緩存中。
 *
 * @param completeMsg 任務完成時的消息內容,用於設置任務的msg字段。
 * @param redisTemplate Redis操作模板,用於將任務信息存儲到Redis緩存中。
 */
public void commonComplete(String completeMsg, RedisTemplate<String, Object> redisTemplate) {
    // 設置任務狀態為“已完成”
    this.setStatus("completed");

    // 設置任務完成消息
    this.setMsg(completeMsg);

    // 設置任務進度為100%
    this.setProcessPercent(new BigDecimal(100));

    // 設置任務創建時間為當前時間
    this.setCreateTime(System.currentTimeMillis());

    // 將任務信息存儲到Redis緩存中,並設置過期時間為1天
    redisTemplate.opsForValue().set(this.findTaskCacheKey(), this, 1, TimeUnit.DAYS);
}
  
    /**
 * 執行通用的更新操作,並調用重載的 `commonUpdate` 方法。
 *
 * @param status 當前狀態信息,通常用於表示操作的狀態。
 * @param msg 更新操作的消息或描述信息。
 * @param addPercent 需要增加的百分比值,通常用於表示進度或比例的增加。
 * @param redisTemplate Redis 操作模板,用於與 Redis 進行交互。
 */
public void commonUpdate(String status, String msg, Integer addPercent, RedisTemplate<String, Object> redisTemplate) {
    this.commonUpdate(status, msg, addPercent, null, redisTemplate);
}

    
    /**
 * 更新任務狀態信息,並將更新後的任務信息存儲到Redis中。
 *
 * @param status 任務狀態,用於設置當前任務的狀態。
 * @param msg 任務消息,用於設置當前任務的消息內容。
 * @param addPercent 增加的進度百分比,用於計算當前任務的進度。
 * @param title 任務標題,如果非空則更新任務的標題。
 * @param redisTemplate Redis模板對象,用於將任務信息存儲到Redis中。
 */
public void commonUpdate(String status, String msg, Integer addPercent, String title, RedisTemplate<String, Object> redisTemplate) {
    // 設置任務狀態和消息
    this.setStatus(status);
    this.setMsg(msg);

    // 如果提供了任務標題,則更新標題
    if (title != null) {
        this.setTitle(title);
    }

    // 計算並更新任務進度,確保進度不超過99%
    BigDecimal calRes = this.getProcessPercent().add(new BigDecimal(addPercent));
    this.setProcessPercent(calRes.min(new BigDecimal(99)));

    // 設置任務的創建時間
    this.setCreateTime(System.currentTimeMillis());

    // 將更新後的任務信息存儲到Redis中,並設置過期時間為1天
    redisTemplate.opsForValue().set(this.findTaskCacheKey(), this, 1, TimeUnit.DAYS);
}
     /**
 * 處理通用失敗情況,並將失敗信息存儲到Redis中。
 *
 * 該方法將當前對象的狀態設置為"error",並設置失敗信息。同時,記錄當前時間作為創建時間,
 * 並將整個對象存儲到Redis中,緩存時間為1天。
 *
 * @param failMsg 失敗信息,用於描述失敗的原因或詳情。
 * @param redisTemplate Redis操作模板,用於與Redis進行交互。
 */
public void commonFailure(String failMsg, RedisTemplate<String, Object> redisTemplate) {
    // 設置狀態為"error",表示任務失敗
    this.setStatus("error");

    // 設置失敗信息
    this.setMsg(failMsg);

    // 記錄當前時間作為創建時間
    this.setCreateTime(System.currentTimeMillis());

    // 將當前對象存儲到Redis中,緩存時間為1天
    redisTemplate.opsForValue().set(this.findTaskCacheKey(), this, 1, TimeUnit.DAYS);
}

    
    /**
 * 生成任務緩存的唯一鍵。
 *
 * 該函數通過將任務處理前綴、用户代碼和任務ID拼接成一個字符串,生成一個唯一的緩存鍵。
 * 該鍵通常用於在緩存系統中標識和存儲與特定任務相關的數據。
 *
 * @return 返回一個格式化的字符串,表示任務緩存的唯一鍵。格式為:"任務處理前綴:用户代碼:任務ID"。
 */
public String findTaskCacheKey() {
    return String.format("%s:%s:%s", TASK_PROCESS_PREFIX_KEY, this.userCode, this.taskId);
}
  
}

上傳服務偽代碼

public void updatexxxx{
 try{
    // 業務測文件集合
  List fileIdList = new ArrayList();
  
  // 業務代碼填充
  xxxxxxx
  
  // 初始化進度響應對象
  RedisTaskProcess redisTaskProcess = RedisTaskProcess.commonStart("業務代碼","業務代碼","業務代碼");
  
  // 計算文件進度
  int documentationProgress = 99 / fileIdList.size();
  
  // 指定上傳邏輯偽代碼
  fileIdList.forEach(fileId -> {
    // 業務代碼邏輯
    xxxxxx
    
    // 更新文件進度
   redisTaskProcess..commonUpdate("processing", "正在處理文件: " + save.getFileName(), progressIncrement, redisTemplate);
      
      
  })
    
   // 完成長傳邏輯
    commonComplete("文檔下載完成", redisTemplate);
 } catch {
   log.error("文檔下載處理失敗", e);
   redisTaskProcess.commonFailure("文檔下載處理失敗: " + e.getMessage(), redisTemplate);
   throw new RuntimeException(e);
 }
  
  
  
}
上述方式採用redis的string數據結構進行存儲進度,存在些許弊端。在高併發場景下需要額外考慮樂觀鎖等機制避免數據覆蓋,需要使用WATCH命令或Lua腳本確保原子性。上述問題將在下一篇文章中進行解決...
user avatar phodal 頭像 lingfeng23 頭像 maimengdehuochai 頭像 yumengzhong2010 頭像
4 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.