文件分片上传

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.mzph.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;看我如何更新秘密的【知识炼金…