【Easylive】定時任務-每日數據統計和臨時文件清理

【Easylive】項目常見問題解答(自用&持續更新中…) 匯總版

這個定時任務系統主要包含兩個核心功能:每日數據統計和臨時文件清理。下面我將詳細解析這兩個定時任務的實現邏輯和技術要點:


@Component
@Slf4j
public class SysTask {@Resourceprivate StatisticsInfoService statisticsInfoService;@Resourceprivate AppConfig appConfig;@Scheduled(cron = "0 0 0 * * ?")public void statisticsData() {statisticsInfoService.statisticsData();}@Scheduled(cron = "0 */1 * * * ?")public void delTempFile() {String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_FOLDER_TEMP;File folder = new File(tempFolderName);File[] listFile = folder.listFiles();if (listFile == null) {return;}String twodaysAgo = DateUtil.format(DateUtil.getDayAgo(2), DateTimePatternEnum.YYYYMMDD.getPattern()).toLowerCase();Integer dayInt = Integer.parseInt(twodaysAgo);for (File file : listFile) {Integer fileDate = Integer.parseInt(file.getName());if (fileDate <= dayInt) {try {FileUtils.deleteDirectory(file);} catch (IOException e) {log.info("刪除臨時文件失敗", e);}}}}
}

一、statisticsData() 每日數據統計任務

執行時機:每天凌晨0點(0 0 0 * * ?

1. 數據統計流程
開始
獲取統計日期
統計播放量
統計粉絲量
統計評論量
統計互動數據
批量入庫
結束
2. 核心代碼解析
public void statisticsData() {// 1. 準備數據結構List<StatisticsInfo> statisticsInfoList = new ArrayList<>();final String statisticsDate = DateUtil.getBeforeDayDate(1); // 獲取昨天的日期// 2. 播放量統計(Redis → DB)Map<String, Integer> videoPlayCountMap = redisComponent.getVideoPlayCount(statisticsDate);// 處理視頻ID格式List<String> playVideoKeys = videoPlayCountMap.keySet().stream().map(item -> item.substring(item.lastIndexOf(":") + 1)).collect(Collectors.toList());// 3. 關聯用戶信息VideoInfoQuery query = new VideoInfoQuery();query.setVideoIdArray(playVideoKeys.toArray(new String[0]));List<VideoInfo> videoInfoList = videoInfoMapper.selectList(query);// 4. 按用戶聚合播放量Map<String, Integer> videoCountMap = videoInfoList.stream().collect(Collectors.groupingBy(VideoInfo::getUserId,Collectors.summingInt(item -> videoPlayCountMap.getOrDefault(Constants.REDIS_KEY_VIDEO_PLAY_COUNT + statisticsDate + ":" + item.getVideoId(), 0))));// 5. 構建播放統計對象videoCountMap.forEach((userId, count) -> {StatisticsInfo info = new StatisticsInfo();info.setStatisticsDate(statisticsDate);info.setUserId(userId);info.setDataType(StatisticsTypeEnum.PLAY.getType());info.setStatisticsCount(count);statisticsInfoList.add(info);});// 6. 補充其他維度數據addFansData(statisticsDate, statisticsInfoList);    // 粉絲統計addCommentData(statisticsDate, statisticsInfoList); // 評論統計addInteractionData(statisticsDate, statisticsInfoList); // 點贊/收藏/投幣// 7. 批量入庫statisticsInfoMapper.insertOrUpdateBatch(statisticsInfoList);
}
3. 關鍵技術點
  1. Redis+DB混合統計
    ? 播放量等高頻數據先記錄到Redis
    ? 定時任務從Redis獲取昨日數據后持久化到DB

  2. 多維度統計
    ? 播放量:基于視頻ID聚合后關聯用戶
    ? 粉絲量:直接查詢DB關系數據
    ? 互動數據:統一處理點贊/收藏/投幣等行為

  3. 批量操作優化
    ? 使用insertOrUpdateBatch實現批量upsert
    ? 減少數據庫連接次數提升性能


二、delTempFile() 臨時文件清理任務

執行時機:每分鐘執行一次(0 */1 * * * ?

1. 文件清理邏輯
public void delTempFile() {// 1. 構建臨時文件夾路徑String tempFolder = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_FOLDER_TEMP;// 2. 獲取兩天前的日期(基準線)String twodaysAgo = DateUtil.format(DateUtil.getDayAgo(2), "yyyyMMdd");Integer thresholdDate = Integer.parseInt(twodaysAgo);// 3. 遍歷臨時文件夾File folder = new File(tempFolder);File[] files = folder.listFiles();if (files == null) return;for (File file : files) {try {// 4. 按日期命名規則清理舊文件Integer fileDate = Integer.parseInt(file.getName());if (fileDate <= thresholdDate) {FileUtils.deleteDirectory(file); // 遞歸刪除}} catch (Exception e) {log.error("刪除臨時文件失敗", e);}}
}
2. 設計要點
  1. 安全機制
    ? 文件名強制使用日期格式(如20230815
    ? 只刪除命名合規的文件夾

  2. 容錯處理
    ? 捕獲IOException防止單次失敗影響后續操作
    ? 空文件夾自動跳過

  3. 性能考慮
    ? 高頻檢查(每分鐘)但低負載(僅處理過期文件)
    ? 使用FileUtils工具類保證刪除可靠性


三、架構設計亮點

  1. 解耦設計
    ? 統計服務與業務邏輯分離
    ? 文件清理與業務模塊隔離

  2. 數據一致性

    業務操作
    Redis
    定時統計任務
    DB

    ? 實時數據寫入Redis保證性能
    ? 定時同步到DB保證持久化

  3. 擴展性
    ? 新增統計維度只需添加對應方法
    ? 文件清理策略可配置化


四、潛在優化建議

  1. 統計任務優化

    // 可考慮分片統計(大用戶量場景)
    @Scheduled(cron = "0 0 1 * * ?") 
    public void statsUserShard1() {statisticsService.processByUserRange(0, 10000);
    }
    
  2. 文件清理增強

    // 添加文件大小監控
    if (file.length() > MAX_TEMP_FILE_SIZE) {alertService.notifyOversizeFile(file);
    }
    
  3. 異常處理強化

    @Scheduled(...)
    public void safeStatistics() {try {statisticsData();} catch (Exception e) {log.error("統計任務失敗", e);retryLater(); // 延遲重試機制}
    }
    

這套定時任務系統通過合理的職責劃分和穩健的實現方式,有效解決了數據統計和資源清理這兩類經典的后臺任務需求。

@Override
public void statisticsData() {// 創建統計結果容器List<StatisticsInfo> statisticsInfoList = new ArrayList<>();// 獲取統計日期(昨天)final String statisticsDate = DateUtil.getBeforeDayDate(1);// ========== 播放量統計 ==========// 從Redis獲取昨日所有視頻的播放量數據// 格式:Map<"video_play_count:20230815:video123", 播放次數>Map<String, Integer> videoPlayCountMap = redisComponent.getVideoPlayCount(statisticsDate);// 提取視頻ID列表(去掉前綴)List<String> playVideoKeys = new ArrayList<>(videoPlayCountMap.keySet());playVideoKeys = playVideoKeys.stream().map(item -> item.substring(item.lastIndexOf(":") + 1)) // 截取最后一個:后的視頻ID.collect(Collectors.toList());// 構建視頻查詢條件VideoInfoQuery videoInfoQuery = new VideoInfoQuery();videoInfoQuery.setVideoIdArray(playVideoKeys.toArray(new String[playVideoKeys.size()]));// 批量查詢視頻基本信息List<VideoInfo> videoInfoList = videoInfoMapper.selectList(videoInfoQuery);// 按用戶ID分組統計總播放量Map<String, Integer> videoCountMap = videoInfoList.stream().collect(Collectors.groupingBy(VideoInfo::getUserId, // 按用戶ID分組Collectors.summingInt(item -> {// 重組Redis key獲取該視頻播放量String redisKey = Constants.REDIS_KEY_VIDEO_PLAY_COUNT + statisticsDate + ":" + item.getVideoId();Integer count = videoPlayCountMap.get(redisKey);return count == null ? 0 : count; // 空值保護})));// 轉換播放統計結果對象videoCountMap.forEach((userId, count) -> {StatisticsInfo statisticsInfo = new StatisticsInfo();statisticsInfo.setStatisticsDate(statisticsDate); // 統計日期statisticsInfo.setUserId(userId);                // 用戶IDstatisticsInfo.setDataType(StatisticsTypeEnum.PLAY.getType()); // 數據類型=播放statisticsInfo.setStatisticsCount(count);       // 播放次數statisticsInfoList.add(statisticsInfo);          // 加入結果集});// ========== 粉絲量統計 ==========// 從數據庫查詢昨日粉絲變化數據List<StatisticsInfo> fansDataList = this.statisticsInfoMapper.selectStatisticsFans(statisticsDate);// 設置統計維度和日期for (StatisticsInfo statisticsInfo : fansDataList) {statisticsInfo.setStatisticsDate(statisticsDate);    // 統一日期格式statisticsInfo.setDataType(StatisticsTypeEnum.FANS.getType()); // 數據類型=粉絲}statisticsInfoList.addAll(fansDataList); // 合并結果// ========== 評論統計 ==========// 從數據庫查詢昨日評論數據List<StatisticsInfo> commentDataList = this.statisticsInfoMapper.selectStatisticsComment(statisticsDate);// 設置統計維度和日期for (StatisticsInfo statisticsInfo : commentDataList) {statisticsInfo.setStatisticsDate(statisticsDate);     // 統一日期格式statisticsInfo.setDataType(StatisticsTypeEnum.COMMENT.getType()); // 數據類型=評論}statisticsInfoList.addAll(commentDataList); // 合并結果// ========== 互動行為統計 ==========// 查詢點贊/收藏/投幣數據(參數為行為類型數組)List<StatisticsInfo> statisticsInfoOthers = this.statisticsInfoMapper.selectStatisticsInfo(statisticsDate,new Integer[]{UserActionTypeEnum.VIDEO_LIKE.getType(),     // 點贊UserActionTypeEnum.VIDEO_COIN.getType(),     // 投幣UserActionTypeEnum.VIDEO_COLLECT.getType()  // 收藏});// 轉換行為類型為統計類型for (StatisticsInfo statisticsInfo : statisticsInfoOthers) {statisticsInfo.setStatisticsDate(statisticsDate); // 統一日期格式// 行為類型轉換if (UserActionTypeEnum.VIDEO_LIKE.getType().equals(statisticsInfo.getDataType())) {statisticsInfo.setDataType(StatisticsTypeEnum.LIKE.getType());       // 點贊} else if (UserActionTypeEnum.VIDEO_COLLECT.getType().equals(statisticsInfo.getDataType())) {statisticsInfo.setDataType(StatisticsTypeEnum.COLLECTION.getType());  // 收藏} else if (UserActionTypeEnum.VIDEO_COIN.getType().equals(statisticsInfo.getDataType())) {statisticsInfo.setDataType(StatisticsTypeEnum.COIN.getType());       // 投幣}}statisticsInfoList.addAll(statisticsInfoOthers); // 合并結果// ========== 最終入庫 ==========// 批量插入或更新統計結果this.statisticsInfoMapper.insertOrUpdateBatch(statisticsInfoList);
}

這段代碼使用Java 8的Stream API和Collectors工具類,實現了按用戶ID分組統計視頻總播放量的功能。下面我將從多個維度進行詳細解析:


一、代碼結構分解

Map<String, Integer> videoCountMap = videoInfoList.stream().collect(Collectors.groupingBy(VideoInfo::getUserId, Collectors.summingInt(item -> {String redisKey = Constants.REDIS_KEY_VIDEO_PLAY_COUNT + statisticsDate + ":" + item.getVideoId();Integer count = videoPlayCountMap.get(redisKey);return count == null ? 0 : count;})));

二、逐層解析

1. 數據準備階段

? 輸入videoInfoList(視頻信息列表,包含videoIduserId等字段)
? 目標:生成Map<用戶ID, 該用戶所有視頻的總播放量>

2. Stream流水線
videoInfoList.stream() 

將List轉換為Stream,準備進行流式處理。

3. 核心收集器
Collectors.groupingBy(VideoInfo::getUserId,          // 分組依據:用戶IDCollectors.summingInt(...)     // 聚合方式:求和
)

? groupingBy:按用戶ID分組,生成Map<String, List<VideoInfo>>
? summingInt:對每組內的元素進行整數求和

4. 播放量計算邏輯
item -> {// 重組Redis key:video_play_count:20230815:video123String redisKey = Constants.REDIS_KEY_VIDEO_PLAY_COUNT + statisticsDate + ":" + item.getVideoId();// 從預加載的Map獲取播放量Integer count = videoPlayCountMap.get(redisKey);// 空值保護(沒有記錄則視為0次播放)return count == null ? 0 : count;
}

三、關鍵技術點

1. 嵌套收集器

? 外層groupingBy按用戶分組
? 內層summingInt對播放量求和
形成兩級聚合操作。

2. 實時Key重組
Constants.REDIS_KEY_VIDEO_PLAY_COUNT + statisticsDate + ":" + item.getVideoId()

動態拼接Redis key,與之前從Redis加載數據時的key格式嚴格一致
? 常量前綴:video_play_count:
? 日期分區:20230815
? 視頻ID::video123

3. 空值防御
count == null ? 0 : count

處理可能存在的:
? Redis中無該視頻記錄
? 視頻剛上傳尚未有播放數據


四、內存數據流演示

假設原始數據:

videoInfoList = [{videoId: "v1", userId: "u1"}, {videoId: "v2", userId: "u1"},{videoId: "v3", userId: "u2"}
]videoPlayCountMap = {"video_play_count:20230815:v1": 100,"video_play_count:20230815:v2": 50,"video_play_count:20230815:v3": 200
}

執行過程:

  1. 流處理開始
  2. 遇到v1(用戶u1)→ 計算播放量100 → u1分組累計100
  3. 遇到v2(用戶u1)→ 計算播放量50 → u1分組累計150
  4. 遇到v3(用戶u2)→ 計算播放量200 → u2分組累計200

最終結果:

{u1=150, u2=200}

五、設計優勢

  1. 高效聚合
    單次遍歷完成分組+求和,時間復雜度O(n)

  2. 內存友好
    流式處理避免中間集合的創建

  3. 可維護性
    清晰表達業務邏輯:
    “按用戶分組,求和每個用戶所有視頻的播放量”

  4. 擴展性
    如需修改統計邏輯(如求平均值),只需替換summingInt為其他收集器


六、潛在優化方向

1. 并行處理(大數據量時)
videoInfoList.parallelStream()...

注意線程安全和順序保證

2. 緩存Key構建
// 預先生成videoId到播放量的映射,避免重復拼接字符串
Map<String, Integer> videoIdToCount = ...;
3. 異常處理增強
try {Integer count = videoPlayCountMap.get(redisKey);return Optional.ofNullable(count).orElse(0);
} catch (Exception e) {log.warn("播放量統計異常 videoId:{}", item.getVideoId());return 0;
}

這段代碼展示了如何優雅地結合Stream API和集合操作,實現復雜的數據聚合統計,是Java函數式編程的典型實踐。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76531.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76531.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76531.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

藍橋杯 15g

班級活動 問題描述 小明的老師準備組織一次班級活動。班上一共有 nn 名 (nn 為偶數) 同學&#xff0c;老師想把所有的同學進行分組&#xff0c;每兩名同學一組。為了公平&#xff0c;老師給每名同學隨機分配了一個 nn 以內的正整數作為 idid&#xff0c;第 ii 名同學的 idid 為…

如何使用AI輔助開發R語言

R語言是一種用于統計計算和圖形生成的編程語言和軟件環境&#xff0c;很多學術研究和數據分析的科學家和統計學家更青睞于它。但對與沒有編程基礎的初學者而言&#xff0c;R語言也是有一定使用難度的。不過現在有了通義靈碼輔助編寫R語言代碼&#xff0c;我們完全可以用自然語言…

CISCO組建RIP V2路由網絡

1.實驗準備&#xff1a; 2.具體配置&#xff1a; 2.1根據分配好的IP地址配置靜態IP&#xff1a; 2.1.1PC配置&#xff1a; PC0&#xff1a; PC1&#xff1a; PC2&#xff1a; 2.1.2路由器配置&#xff1a; R0&#xff1a; Router>en Router#conf t Enter configuration…

React + TipTap 富文本編輯器 實現消息列表展示,類似Slack,Deepseek等對話框功能

經過幾天折騰再折騰&#xff0c;弄出來了&#xff0c;弄出來了&#xff01;&#xff01;&#xff01; 消息展示 在位編輯功能。 兩個tiptap實例1個用來展示 消息列表&#xff0c;一個用來在位編輯消息。 tiptap靈活富文本編輯器&#xff0c;拓展性太好了!!! !!! 關鍵點&#x…

Ubuntu搭建Pytorch環境

Ubuntu搭建Pytorch環境 例如&#xff1a;第一章 Python 機器學習入門之pandas的使用 提示&#xff1a;寫完文章后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 Ubuntu搭建Pytorch環境前言一、Anaconda二、Cuda1.安裝流程2、環境變量&#…

Sping Cloud配置和注冊中心

1.Nacos實現原理了解嗎&#xff1f; Nacos是注冊中心&#xff0c;主要是幫助我們管理服務列表。Nacos的實現原理大概可以從下面三個方面來講&#xff1a; 服務注冊與發現&#xff1a;當一個服務實例啟動時&#xff0c;它會向Nacos Server發送注冊請求&#xff0c;將自己的信息…

C++筆記之父類引用是否可以訪問到子類特有的屬性?

C++筆記之父類引用是否可以訪問到子類特有的屬性? code review! 參考筆記 1.C++筆記之在基類和派生類之間進行類型轉換的所有方法 文章目錄 C++筆記之父類引用是否可以訪問到子類特有的屬性?1.主要原因2.示例代碼3.說明4.如何訪問子類特有的屬性5.注意事項6.總結在 C++ 中,…

JavaScript逆向工程:如何判斷對稱加密與非對稱加密

在現代Web應用安全分析中&#xff0c;加密算法的識別是JavaScript逆向工程的關鍵環節。本文將詳細介紹如何在逆向工程中判斷JavaScript代碼使用的是對稱加密還是非對稱加密。 一、加密算法基礎概念 1. 對稱加密 (Symmetric Encryption) 特點&#xff1a;加密和解密使用相同的…

物理備份工具 BRM vs gs_probackup

什么是BRM 上一篇文章講了openGauss的物理備份工具gs_probackup&#xff0c;今天來說說BRM備份工具。 BRM備份恢復工具全稱為&#xff1a;Backup and Recovery Manager&#xff0c;是MogDB基于opengauss的備份工具 gs_probackup 做了一些封裝和優化,面向MogDB數據庫實現備份和…

問問lua怎么寫DeepSeek,,,,,

很坦白說&#xff0c;這十年&#xff0c;我幾乎沒辦法從互聯網找到這個這樣的代碼&#xff0c;互聯網引擎找不到&#xff0c;我也沒有很大的“追求”要傳承&#xff0c;或者要宣傳什么&#xff1b;直到DeepSeek的出現 兄弟&#xff0c;Deepseek現在已經比你更了解你樓下的超市…

react+Tesseract.js實現前端拍照獲取/選擇文件等文字識別OCR

需求背景 在開發過程中可能會存在用戶上傳一張圖片后下方需要自己識別出來文字數字等信息&#xff0c;有的時候會通過后端來識別后返回&#xff0c;但是也會存在純前端去識別的情況&#xff0c;這個時候就需要使用到Tesseract.js這個庫了 附Tesseract.js官方&#xff08;htt…

藍橋杯考前復盤

明天就是考試了&#xff0c;適當的停下刷題的步伐。 靜靜回望、思考、總結一下&#xff0c;我走過的步伐。 考試不是結束&#xff0c;他只是檢測這一段時間學習成果的工具。 該繼續走的路&#xff0c;還是要繼續走的。 只是最近&#xff0c;我偶爾會感到迷惘&#xff0c;看…

前端-Vue3

1. Vue3簡介 2020年9月18日&#xff0c;Vue.js發布版3.0版本&#xff0c;代號&#xff1a;One Piece&#xff08;n 經歷了&#xff1a;4800次提交、40個RFC、600次PR、300貢獻者 官方發版地址&#xff1a;Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xff0c;最…

[ctfshow web入門] web39

信息收集 題目發生了微妙的變化&#xff0c;只過濾flag&#xff0c;include后固定跟上了.php。且沒有了echo $flag;&#xff0c;雖說本來就沒什么用 if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/flag/i", $c)){include($c.".php");} }else{…

【動手學深度學習】LeNet:卷積神經網絡的開山之作

【動手學深度學習】LeNet&#xff1a;卷積神經網絡的開山之作 1&#xff0c;LeNet卷積神經網絡簡介2&#xff0c;Fashion-MNIST圖像分類數據集3&#xff0c;LeNet總體架構4&#xff0c;LeNet代碼實現4.1&#xff0c;定義LeNet模型4.2&#xff0c;定義模型評估函數4.3&#xff0…

代碼隨想錄第15天:(二叉樹)

一、二叉搜索樹的最小絕對差&#xff08;Leetcode 530&#xff09; 思路1 &#xff1a;中序遍歷將二叉樹轉化為有序數組&#xff0c;然后暴力求解。 class Solution:def __init__(self):# 初始化一個空的列表&#xff0c;用于保存樹的節點值self.vec []def traversal(self, r…

計算機操作系統-【死鎖】

文章目錄 一、什么是死鎖&#xff1f;死鎖產生的原因&#xff1f;死鎖產生的必要條件&#xff1f;互斥條件請求并保持不可剝奪環路等待 二、處理死鎖的基本方法死鎖的預防摒棄請求和保持條件摒棄不可剝奪條件摒棄環路等待條件 死鎖的避免銀行家算法案例 提示&#xff1a;以下是…

vue拓撲圖組件

vue拓撲圖組件 介紹技術棧功能特性快速開始安裝依賴開發調試構建部署 使用示例演示截圖組件源碼 介紹 一個基于 Vue3 的拓撲圖組件&#xff0c;具有以下特點&#xff1a; 1.基于 vue-flow 實現&#xff0c;提供流暢的拓撲圖展示體驗 2.支持傳入 JSON 對象自動生成拓撲結構 3.自…

go 通過匯編分析函數傳參與返回值機制

文章目錄 概要一、前置知識二、匯編分析2.1、示例2.2、匯編2.2.1、 寄存器傳值的匯編2.2.2、 棧內存傳值的匯編 三、拓展3.1 了解go中的Duff’s Device3.2 go tool compile3.2 call 0x46dc70 & call 0x46dfda 概要 在上一篇文章中&#xff0c;我們研究了go函數調用時的棧布…

python-1. 找單獨的數

問題描述 在一個班級中&#xff0c;每位同學都拿到了一張卡片&#xff0c;上面有一個整數。有趣的是&#xff0c;除了一個數字之外&#xff0c;所有的數字都恰好出現了兩次。現在需要你幫助班長小C快速找到那個拿了獨特數字卡片的同學手上的數字是什么。 要求&#xff1a; 設…