文件分片上傳

1前端

<inputtype="file"accept=".mp4"ref="videoInput"@change="handleVideoChange"style="display: none;">

2生成hash

// 根據整個文件的文件名和大小組合的字符串生成hash值,大概率確定文件的唯一性fhash(file) {// console.log("哈希字段: ", file.name+file.size.toString());return new Promise(resolve => {const spark = new SparkMD5();spark.append(file.name+file.size.toString());resolve(spark.end());})},

3生成切片

// 生成切片createChunks(file) {const result = [];for (let i = 0; i < file.size; i += this.chunkSize) {result.push(file.slice(i, i + this.chunkSize));}return result;},

4查詢未上傳切片的hash,斷點續傳

// 獲取當前還沒上傳的序號 斷點續傳async askCurrentChunk(hash) {return await this.$get("/video/ask-chunk", {params: { hash: hash },headers: { Authorization: "Bearer " + localStorage.getItem("teri_token") }});},

5上傳分片

 // 上傳分片async uploadChunk(formData) {return await this.$post("/video/upload-chunk", formData, {headers: {'Content-Type': 'multipart/form-data',Authorization: "Bearer " + localStorage.getItem("teri_token"),}})},

6上傳文件

async upload() {const chunks = this.createChunks(this.selectedVideo);// 向服務器查詢還沒上傳的下一個分片序號const result = await this.askCurrentChunk(this.hash);this.current = result.data.data;// 逐個上傳分片for (this.current; this.current < chunks.length; this.current++) {const chunk = chunks[this.current];const formData = new FormData();formData.append('chunk', chunk); // 將當前分片作為單獨的文件上傳formData.append('hash', this.hash);formData.append('index', this.current); // 傳遞分片索引// 發送分片到服務器try {const res = await this.uploadChunk(formData);if (res.data.code !== 200) {// ElMessage.error("分片上傳失敗");this.isFailed = true;this.isPause = true;}} catch {// ElMessage.error("分片上傳失敗");this.isFailed = true;this.isPause = true;return;}// 暫停上傳if (this.isPause) {// 取消上傳徹底刪除已上傳分片if (this.isCancel) {await this.cancelUpload(this.hash);this.isCancel = false;}return;}this.progress = Math.round(((this.current + 1) / chunks.length) * 100); // 實時改進度條}this.progress = 100;    // 上傳完成再次確認為100%},

后端

1查詢hash

     /*** 獲取視頻下一個還沒上傳的分片序號* @param hash 視頻的hash值* @return CustomResponse對象*/public CustomResponse askCurrentChunk(String hash) {CustomResponse customResponse = new CustomResponse();// 查詢本地// 獲取分片文件的存儲目錄File chunkDir = new File(CHUNK_DIRECTORY);// 獲取存儲在目錄中的所有分片文件File[] chunkFiles = chunkDir.listFiles((dir, name) -> name.startsWith(hash + "-"));// 返回還沒上傳的分片序號if (chunkFiles == null) {customResponse.setData(0);} else {customResponse.setData(chunkFiles.length);}// 查詢OSS當前存在的分片數量,即前端要上傳的分片序號,建議分布式系統才使用OSS存儲分片,單體系統本地存儲分片效率更高
//        int counts = ossUploadUtil.countFiles("chunk/", hash + "-");
//        customResponse.setData(counts);return customResponse;}

2上傳分片

/*** 上傳單個視頻分片,當前上傳到阿里云對象存儲* @param chunk 分片文件* @param hash  視頻的hash值* @param index 當前分片的序號* @return  CustomResponse對象* @throws IOException*/@Overridepublic CustomResponse uploadChunk(MultipartFile chunk, String hash, Integer index) throws IOException {CustomResponse customResponse = new CustomResponse();// 構建分片文件名String chunkFileName = hash + "-" + index;// 存儲到本地// 構建分片文件的完整路徑String chunkFilePath = Paths.get(CHUNK_DIRECTORY, chunkFileName).toString();// 檢查是否已經存在相同的分片文件File chunkFile = new File(chunkFilePath);if (chunkFile.exists()) {log.warn("分片 " + chunkFilePath + " 已存在");customResponse.setCode(500);customResponse.setMessage("已存在分片文件");return customResponse;}// 保存分片文件到指定目錄chunk.transferTo(chunkFile);// 存儲到OSS,建議分布式系統才使用OSS存儲分片,單體系統本地存儲分片效率更高
//        try {
//            boolean flag = ossUploadUtil.uploadChunk(chunk, chunkFileName);
//            if (!flag) {
//                log.warn("分片 " + chunkFileName + " 已存在");
//                customResponse.setCode(500);
//                customResponse.setMessage("已存在分片文件");
//            }
//        } catch (IOException ioe) {
//            log.error("讀取分片文件數據流時出錯了");
//        }// 返回成功響應return customResponse;}

3合并

    /*** 合并分片并將投稿信息寫入數據庫* @param vui 存放投稿信息的 VideoUploadInfo 對象*/@Transactionalpublic void mergeChunks(VideoUploadInfoDTO vui) throws IOException {String url; // 視頻最終的URL// 合并到本地
//        // 獲取分片文件的存儲目錄
//        File chunkDir = new File(CHUNK_DIRECTORY);
//        // 獲取當前時間戳
//        long timestamp = System.currentTimeMillis();
//        // 構建最終文件名,將時間戳加到文件名開頭
//        String finalFileName = timestamp + vui.getHash() + ".mp4";
//        // 構建最終文件的完整路徑
//        String finalFilePath = Paths.get(VIDEO_DIRECTORY, finalFileName).toString();
//        // 創建最終文件
//        File finalFile = new File(finalFilePath);
//        // 獲取所有對應分片文件
//        File[] chunkFiles = chunkDir.listFiles((dir, name) -> name.startsWith(vui.getHash() + "-"));
//        if (chunkFiles != null && chunkFiles.length > 0) {
//            // 使用流操作對文件名進行排序,防止出現先合并 10 再合并 2
//            List<File> sortedChunkFiles = Arrays.stream(chunkFiles)
//                    .sorted(Comparator.comparingInt(file -> Integer.parseInt(file.getName().split("-")[1])))
//                    .collect(Collectors.toList());
//            try {
                System.out.println("正在合并視頻");
//                // 合并分片文件
//                for (File chunkFile : sortedChunkFiles) {
//                    byte[] chunkBytes = FileUtils.readFileToByteArray(chunkFile);
//                    FileUtils.writeByteArrayToFile(finalFile, chunkBytes, true);
//                    chunkFile.delete(); // 刪除已合并的分片文件
//                }
                System.out.println("合并完成!");
//                // 獲取絕對路徑,僅限本地服務器
//                url = finalFile.getAbsolutePath();
                System.out.println(url);
//            } catch (IOException e) {
//                // 處理合并失敗的情況 重新入隊等
//                log.error("合并視頻失敗");
//                throw e;
//            }
//        } else {
//            // 沒有找到分片文件 發通知用戶投稿失敗
//            log.error("未找到分片文件 " + vui.getHash());
//            return;
//        }// 合并到OSS,并返回URL地址url = ossUtil.appendUploadVideo(vui.getHash());if (url == null) {return;}// 存入數據庫Date now = new Date();Video video = new Video(null,vui.getUid(),vui.getTitle(),vui.getType(),vui.getAuth(),vui.getDuration(),vui.getMcId(),vui.getScId(),vui.getTags(),vui.getDescr(),vui.getCoverUrl(),url,0,now,null);videoMapper.insert(video);VideoStats videoStats = new VideoStats(video.getVid(),0,0,0,0,0,0,0,0);videoStatsMapper.insert(videoStats);esUtil.addVideo(video);CompletableFuture.runAsync(() -> redisUtil.setExObjectValue("video:" + video.getVid(), video), taskExecutor);CompletableFuture.runAsync(() -> redisUtil.addMember("video_status:0", video.getVid()), taskExecutor);CompletableFuture.runAsync(() -> redisUtil.setExObjectValue("videoStats:" + video.getVid(), videoStats), taskExecutor);// 其他邏輯 (發送消息通知寫庫成功)}

4oss追加上傳

    /*** 將本地的視頻分片文件追加合并上傳到OSS* @param hash  視頻的hash值,用于檢索對應分片* @return  視頻在OSS的URL地址* @throws IOException*/public String appendUploadVideo(@NonNull String hash) throws IOException {// 生成文件名String uuid = System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "");String fileName = uuid + ".mp4";// 完整路徑名String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date()).replace("-", "");String filePathName = date + "/video/" + fileName;ObjectMetadata meta = new ObjectMetadata();// 設置內容類型為MP4視頻meta.setContentType("video/mp4");int chunkIndex = 0;long position = 0; // 追加位置while (true) {File chunkFile = new File(CHUNK_DIRECTORY + hash + "-" + chunkIndex);if (!chunkFile.exists()) {if (chunkIndex == 0) {log.error("沒找到任何相關分片文件");return null;}break;}// 讀取分片數據FileInputStream fis = new FileInputStream(chunkFile);byte[] buffer = new byte[(int) chunkFile.length()];fis.read(buffer);fis.close();// 追加上傳分片數據try {AppendObjectRequest appendObjectRequest = new AppendObjectRequest(OSS_BUCKET, filePathName, new ByteArrayInputStream(buffer), meta);appendObjectRequest.setPosition(position);AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);position = appendObjectResult.getNextPosition();} catch (OSSException oe) {log.error("OSS出錯了:" + oe.getErrorMessage());throw oe;} catch (ClientException ce) {log.error("OSS連接出錯了:" + ce.getMessage());throw ce;}chunkFile.delete(); // 上傳完后刪除分片chunkIndex++;}return OSS_BUCKET_URL + filePathName;}

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

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

相關文章

機器學習的一百個概念(5)數據增強

前言 本文隸屬于專欄《機器學習的一百個概念》&#xff0c;該專欄為筆者原創&#xff0c;引用請注明來源&#xff0c;不足和錯誤之處請在評論區幫忙指出&#xff0c;謝謝&#xff01; 本專欄目錄結構和參考文獻請見[《機器學習的一百個概念》 ima 知識庫 知識庫廣場搜索&…

基于微信小程序的智慧鄉村旅游服務平臺【附源碼】

基于微信小程序的智慧鄉村旅游服務平臺&#xff08;源碼L文說明文檔&#xff09; 目錄 4系統設計 4.1系統功能設計 4.2系統結構 4.3.數據庫設計 4.3.1數據庫實體 4.3.2數據庫設計表 5系統詳細實現 5.1 管理員模塊的實現 5.1.1旅游景點管理…

數據驅動的智能BMS革新:機器學習賦能電池健康預測與安全協同優化

傳統電池管理系統&#xff08;BMS&#xff09;依賴等效電路模型和固定參數算法&#xff0c;面臨電化學機理復雜、老化行為非線性、多工況適應性差等瓶頸。例如&#xff0c;健康狀態&#xff08;SOH&#xff09;和荷電狀態&#xff08;SOC&#xff09;估算易受溫度、循環次數及電…

使用JSON.stringify報錯:Uncaught TypeError: cyclic object value

具體錯誤 Uncaught TypeError: cyclic object valueonMouseOver Amap.vue:125renderMarker Amap.vue:84emit maps:1emit maps:1ci maps:1ui maps:1fireEvent maps:1jL maps:1Xt maps:1T maps:1<anonymous> amap.vue:49promise callback*nextTick runtime-core.esm-bundl…

Spring Boot 工程創建詳解

2025/4/2 向全棧工程師邁進&#xff01; 一、SpingBoot工程文件的創建 點擊Project Structure 然后按著如下點擊 最后選擇Spring Boot &#xff0c;同時記得選擇是Maven和jar&#xff0c;而不是war。因為Boot工程內置了Tomcat&#xff0c;所以不需要war。 緊接著選擇Spring We…

Java 8 的流(Stream API)簡介

Java 8 引入的 Stream API 是一個強大的工具&#xff0c;用于處理集合&#xff08;如 List、Set&#xff09;中的元素。它支持各種操作&#xff0c;包括過濾、排序、映射等&#xff0c;并且能夠以聲明式的方式表達復雜的查詢操作。流操作可以是中間操作&#xff08;返回流以便進…

4. Flink SQL訪問HiveCatalog

一. 實驗環境 Flink版本: 1.19.1 Hive版本: 2.1.3 Hadoop版本: 3.2.4二. 操作步驟 1.上傳所需的jar包到Flink lib目錄下 [roothadoop3 ~]# mv flink-sql-connector-hive-3.1.3_2.12-1.19.1.jar /www/flink-1.19.1/lib [roothadoop3 ~]# mv hadoop-mapreduce-client-core-3.2…

虛擬試衣間-云尚衣櫥小程序-衣櫥管理實現

衣櫥管理實現 目標 (Goal): 用戶 (User): 能通過 UniApp 小程序上傳衣服圖片。 后端 (Backend): 接收圖片,存到云存儲,并將圖片信息(URL、用戶ID等)存入數據庫。 用戶 (User): 能在小程序里看到自己上傳的所有衣服圖片列表。 技術棧細化 (Refined Tech Stack for this Pha…

HAL庫 通過USB Boot進行APP程序升級

硬件&#xff1a;stm32f407VET6芯片&#xff1b; 軟件&#xff1a;STM32CubeMx、Keil5 上位機&#xff1a;Dfuse DemoV3.06 這里給出通過在Bootlaoder中使用USB方式來更新APP程序的方法&#xff0c;首先我們編寫一個自己的bootloader&#xff0c;關于bootloader的大致原理可以…

數據庫權限獲取

1. into outfile&#xff08;手寫&#xff09; 1.1. 利用條件 ? web 目錄具有寫入權限&#xff0c;能夠使用單引號 ? 知道網站絕對路徑&#xff08;根目錄&#xff0c;或則是根目錄往下的目錄都行&#xff09; ? secure_file_priv 沒有具體值&#xff08;在 mysql/my.ini…

關于ESP系列MCU的UART download原理

GPIO0&#xff0c;即BOOT&#xff0c;工作模式選擇&#xff1a; 懸空/拉高&#xff1a;正常MCU啟動工作狀態 下拉接地&#xff1a;UARTDownload下載模式 如何進入UARTDownload下載模式&#xff1f; 先按下boot按鍵不放&#xff0c;再按下rst按鍵 / en按鍵&#xff0c;隨后釋放…

無需安裝Office進行 Word、Excel操作的微軟開發庫

微軟的確有一些無需安裝完整 Office 就能進行 Word、Excel 操作的開發庫&#xff0c;以下為你介紹&#xff1a; 1. Microsoft Graph API 簡介&#xff1a;Microsoft Graph API 是一個強大的 RESTful API&#xff0c;能讓開發者通過調用接口訪問 Office 365 服務里的各種資源&…

【一起來學kubernetes】34、ReplicaSet使用詳解

Kubernetes ReplicaSet 使用詳解 ReplicaSet 是 Kubernetes 中用于確保指定數量的 Pod 副本持續運行的核心控制器。它通過動態調整 Pod 副本數&#xff0c;保障應用的高可用性和彈性。以下是其核心功能、配置方法及最佳實踐&#xff1a; 一、ReplicaSet 核心作用 維持 Pod 副本…

【力扣hot100題】(034)LRU緩存

做完這題已經沒有任何力氣寫鏈表題了。 思路很簡單&#xff0c;就是調試特別的痛苦。 老是頻頻報錯&#xff0c;唉。 class LRUCache { public:struct ListNode{int key,val;ListNode* next; ListNode* prev;ListNode() : key(0), val(0), next(nullptr), prev(nullptr) {}L…

基于隨機森林算法的信用風險評估項目

引言 這是一個基于隨機森林算法的德國信用風險評估項目&#xff0c;主要目的是構建一個機器學習模型來評估德國客戶的信用風險&#xff0c;判斷客戶是否為高風險客戶。 # -*- coding: utf-8 -*- """ 德國信用風險評估隨機森林模型 """ # 基礎…

亞馬遜云科技攜手 DeepSeek:開啟企業級生成式 AI 新征程

文章目錄 一、DeepSeek-R1模型的技術突破&#xff08;一&#xff09;卓越的性能表現&#xff08;二&#xff09;獨特的訓練方法&#xff08;三&#xff09;豐富的模型生態 二、亞馬遜云科技平臺上的部署與優化&#xff08;一&#xff09;靈活的部署方式&#xff08;二&#xff…

Windows 實戰-evtx 文件分析--筆記

Windows 取證之EVTX日志 - 蟻景網安實驗室 - 博客園 一.evtx日志文件是什么 從 Windows NT 6.0&#xff08;也就是 Windows Vista 和 Windows Server 2008&#xff09;開始&#xff0c;微軟引入了一種全新的日志文件格式&#xff0c;稱為 evtx。這種格式取代了之前 Windows 系…

LangChain/Eliza框架在使用場景上的異同,Eliza通過配置實現功能擴展的例子

LangChain與Eliza框架的異同分析 ?一、相同點? ?模塊化架構設計? 兩者均采用模塊化設計&#xff0c;支持靈活擴展和功能組合。LangChain通過Chains、Agents等組件實現多步驟任務編排?&#xff0c;Eliza通過插件系統和信任引擎實現智能體功能的動態擴展?。模塊化特性降低…

英語口語 -- 常用 1368 詞匯

英語口語 -- 常用 1368 詞匯 介紹常用單詞List1 &#xff08;96 個&#xff09;時間類氣候類自然類植物類動物類昆蟲類其他生物地點類 List2 &#xff08;95 個&#xff09;機構類聲音類食品類餐飲類蔬菜類水果類食材類飲料類營養類疾病類房屋類家具類服裝類首飾類化妝品類 Lis…

深挖 DeepSeek 隱藏玩法·智能煉金術2.0版本

前引&#xff1a;屏幕前的你還在AI智能搜索框這樣搜索嗎&#xff1f;“這道題怎么寫”“蘋果為什么紅”“怎么不被發現翹課” &#xff0c;。看到此篇文章的小伙伴們&#xff01;請準備好你的思維魔杖&#xff0c;開啟【霍格沃茨模式】&#xff0c;看我如何更新秘密的【知識煉金…