事件起因是客戶現場需要從石墨文檔中獲取文件信息,文件信息存在存在多個,進行批量上傳。為了用戶的友好型體驗,需要做進行條展示的方式,具體實現見下文…
上傳流程介紹
石墨文檔支持從鏈接🔗方式獲取文件信息,通過對文件鏈接的截取,會得到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腳本確保原子性。上述問題將在下一篇文章中進行解決…