首页 > 其他分享 >Gin存储文件与oss对象存储(二)

Gin存储文件与oss对象存储(二)

时间:2024-12-14 22:10:16浏览次数:3  
标签:文件 存储 err oss ctx gin 分片 Gin 上传

Gin存储文件与oss对象存储(二)

原创 何泽丰 ProgrammerHe    2024年12月13日 20:53 广东 听全文

Gin存储文件与oss对象存储(二)

概述

朋友们大家好啊,这一篇笔记我们来简单记录一下前端在Vue2项目中base64转图片,在文件上传时实现分片上传、断点续传功能;最后将视频文件存储到OSS对象存储,转码为M3U8格式在Web前端实现HLS播放。

前端base64转图片

图片

后端直接返回base64格式字符串不需要做额外的数据封装,格式转换在客户端进行,按照上一篇笔记的做法,后端将某个路径下的图片取回并转换成base64编码的图像字符串返回给web前端,前端接收后将该转换成图像。

...

this.base64Data= res.data.pic;
this.imageSrc='data:image/png;base64,'+this.base64Data;
this.convertBase64ToImage();
...

convertBase64ToImage(){
const img =newImage();// 创建一个Image对象
    img.src=this.imageSrc;
    img.onload=() =>{
const canvas =document.createElement('canvas');// 创建一个canvas对象
const context = canvas.getContext('2d');
        canvas.width= img.width;// 设置canvas长宽与图像保持一致
        canvas.height= img.height;
        context.drawImage(img,0,0);
const imageURL = canvas.toDataURL('image/png');// 调用canvas的toDataURL方法
this.imageSrc= imageURL;// 更新 imageSrc
    };
},

分片传输与断点续传

分片传输有不少好处,上传时将大文件分割成多个小分片进行上传,能提高文件的上传速度、减少内存消耗。而断点续传可以在传输中断后继续传输,提高数据传输的稳定性和可靠性,避免重复传输整个大文件、节省了时间和带宽。简易实现的思路是前端将大文件分割成同等大小的分片(最后一片除外),调用后端接口将分片上传,上传完成后再告诉后端将分片合成文件。至于最简单的断点续传实现,本次没有在后端做校验,只在前端实现记录上传分片索引实现的断点续传功能。

前端思路与实现

那我们可以将分片传输分成两步,将文件分片再上传。

文件通过input组件上传后,通过handleFileChange函数处理,获取用户选择的第一个文件(event.target.files[0]),event是触发事件的对象,target指的是事件的目标元素。当点击开始上传时调用uploadFile方法,判断文件是否为空或是否已暂停上传。然后根据chunkSize切割分片,分出来之后调用reader.readAsArrayBuffer方法,readAsArrayBuffer会异步执行,将读取文件数据到内存中,完成后会触发onloadend事件上传单个分片,直到所有分片传输完成。

// 放几个按钮
<div class="user-upload-file">
<input type="file"ref="fileInput"@change="handleFileChange"/>
<button @click="uploadFile">开始上传</button>
<button @click="pauseUpload">暂停上传</button>
<button @click="resumeUpload">继续上传</button>
<button @click="cancelUpload">取消上传</button>
</div>

...
<script>
exportdefault{
...
   data(){
return{
            file:null,// 存储选择的文件
              chunkSize:5*1024*1024,// 每片 分片大小设置为5M
              totalChunks:0,// 所有分片总数
              currentChunk:0,// 初始化分片索引
              isPaused:false,// 是否暂停上传
}
},
...
    methods:{
      handleFileChange(event){
this.file =event.target.files[0];
this.totalChunks =Math.ceil(this.file.size /this.chunkSize);// 计算总分片数
this.currentChunk =0;// 当前分片索引是0
this.isPaused =false;// 没有在暂停传输
},
    async uploadFile(){
if(!this.file){
        alert("没有文件上传");
return;
}

while(this.currentChunk <this.totalChunks){
if(this.isPaused){
          console.log("上传已暂停")
return;
}
const start =this.currentChunk *this.chunkSize;
constend=Math.min(start +this.chunkSize,this.file.size);
const chunk =this.file.slice(start,end);

        await newPromise((resolve, reject)=>{
const reader =newFileReader();
          reader.onloadend =()=>{
// 上传分片
this.uploadChunk(reader.result,this.currentChunk,this.totalChunks)
.then(()=>{
                  resolve();// 分片上传成功后继续处理下一个分片
this.currentChunk ++;
})
.catch((err)=>{
                  reject(err);// 上传失败,停止并返回错误
});
};
// 读取分片数据
          reader.readAsArrayBuffer(chunk);
    });
}
}
}
</script>

在全部分片传输完成后调一下后端的接口,表示可以开始将文件合并了。

... 
asyncmergeChunk(filename, totalChunk){
console.log("mergeChunk=>", filename, totalChunk);
try{
const formData =newFormData();
        formData.append('fileName', filename);
        formData.append('totalChunk', totalChunk);

// 发送请求
const response =await axios.post('/users/merge_chunk', formData,{
headers:{
"Content-Type":"'multipart/form-data",
}
});
console.log(response);
}catch(err){
console.log(err);
}
},

// 暂停上传
pauseUpload(){
this.isPaused=true;
console.log("暂停上传");
},


// 继续上传
resumeUpload(){
if(this.isPaused){
this.isPaused=false;
console.log("继续上传");
this.uploadFile();// 继续上传
    }
},

后端思路与实现

  1. 接收上传分片;将分片先暂存到某个文件夹,并根据上传的index给分片命名。
// 处理上传分片的接口
func (h *UserHandler)HandleUploadChunk(ctx *gin.Context){
...
// 存放临时文件夹
const chunkDir ="C:\\Users\\hzf19\\Desktop\\chunk"

// 检查分片存储目录是否存在,不存在则创建
if _, err := os.Stat(chunkDir); os.IsNotExist(err){
        err := os.MkdirAll(chunkDir, os.ModePerm)// 创建目录
if err !=nil{
            log.Println("创建分片目录失败:", err)
            ctx.JSON(500, gin.H{"error":"创建分片目录失败"})
return
}
}

    file, err := ctx.FormFile("chunk")
if err !=nil{
        log.Println(err)
        ctx.JSON(400, gin.H{"error":"文件上传失败"})
return
}

// 获取文件名,文件名从表单字段获取
    fileName := ctx.DefaultPostForm("fileName","")// 获取文件名
if fileName ==""{
        fileName = file.Filename// 如果没有从表单中获取到文件名,则使用上传的文件名
}
    index, err := strconv.Atoi(ctx.DefaultPostForm("index","0"))// 分片索引
if err !=nil{
        log.Println(err,"无效的分片索引")
        ctx.JSON(400, gin.H{"error":"无效的分片索引"})
return
}
    chunkCount, err := strconv.Atoi(ctx.DefaultPostForm("chunkCount","0"))// 获取总分片数
if err !=nil{
        ctx.JSON(400, gin.H{"error":"无效的总分片数"})
return
}

// 创建存储分片的临时文件
    chunkFilePath := filepath.Join(chunkDir, fmt.Sprintf("%s-chunk-%d", fileName, index))
// 保存分片到临时文件
if err := ctx.SaveUploadedFile(file, chunkFilePath); err !=nil{
        ctx.JSON(500, gin.H{"error":"保存分片文件失败"})
return
}

// 返回上传成功的响应
    ctx.JSON(200, gin.H{
"message":"分片上传成功",
"chunkIndex": index,
"chunkCount": chunkCount,
})
}
  1. 合并分片接口。就说合并时要顺序读取分片,不然合并后文件就损坏了。
HandleMergeChunk(ctx *gin.Context){
....
    fileName := ctx.PostForm("fileName")
    totalChunk, err := strconv.Atoi(ctx.PostForm("totalChunk"))
if err !=nil|| totalChunk <=0{
        ctx.JSON(400, gin.H{"error":"无效的总分片数"})
return
}

// 分片的临时文件夹
const chunkDir ="C:\\Users\\hzf19\\Desktop\\chunk"

// 文件存储路径
    mergePath := fmt.Sprintf("C:\\Users\\hzf19\\Desktop\\1821841651400708096\\%s", fileName)

// 确保临时文件夹存在
if _, err := os.Stat(chunkDir); os.IsNotExist(err){
        ctx.JSON(500, gin.H{"error":"临时文件不存在"})
return
}

// 创建合并文件
    mergeFile, err := os.Create(mergePath)
if err !=nil{
        ctx.JSON(500, gin.H{"error":"创建合并文件失败"})
return
}
defer mergeFile.Close()

// 按索引顺序合并分片 顺序很重要
for i :=0; i < totalChunk; i++{
        chunkPath := filepath.Join(chunkDir, fmt.Sprintf("%s-chunk-%d", fileName, i))
        chunkData, err := os.ReadFile(chunkPath)
if err !=nil{
            log.Printf("读取分片失败: %v", err)
            ctx.JSON(500, gin.H{"error": fmt.Sprintf("读取分片 %d 失败: %v", i, err)})
return
}
if _, err := mergeFile.Write(chunkData); err !=nil{
            log.Printf("写入分片失败: %v", err)
            ctx.JSON(500, gin.H{"error": fmt.Sprintf("写入分片 %d 失败: %v", i, err)})
return
}
}

    ctx.JSON(200, gin.H{
"data":"merge chunk success",
})
}
           

OSS视频mp4转m3u8

m3u8是一种基于HTTP Live Streaming(HLS)协议的媒体播放列表文件格式,主要用于流媒体传输。HLS由苹果公司开发,目前已成为一种广泛支持的标准;HLS通常以UTF-8编码,包含了一系列媒体文件的URL地址,指向视频或音频流的不同分段。m3u8文件实质上是一个播放列表(playlist),它并不包含音视频数据,而是记录了一个索引,播放软件根据这个索引找到对应的音视频文件的地址进行播放。m3u8文件支持自适应码率流、动态更新和分段传输,能够支持稳定、高效的流媒体播放。它通过定义媒体段、播放顺序以及其他元数据,确保了流媒体的可靠传输与播放。与MP4文件相比,HLS将视频分割成许多片段(如5s的 .ts文件),通过M3U8文件中的URL进行有序传输。

这里记的是将视频上传到七牛云OSS,将mp4文件转为m3u8格式,在前端播放器进行播放。

上传到OSS

上传到七牛云oss后,对视频进行转码。转码后将播放链接复制到videoSrc上。

图片图片

前端播放器

           

首先需要引用hls的包并引用

npm install hls.js
...
import Hls from 'hls.js';
<div id="app">
<divclass="hls-player">
<video
ref="videoPlayer"
controls
autoplay="true"
style="width:100%; max-width:800px; background:#000;"
></video>
</div>

<script>
exportdefault{
....
    mounted(){
this.initHlsPlayer();
},
    methods:{
    initHlsPlayer(){
const video =this.$refs.videoPlayer;

if(Hls.isSupported()){
const hls =newHls();
        hls.loadSource(this.videoSrc);
        hls.attachMedia(video);

        hls.on(Hls.Events.MANIFEST_PARSED,()=>{
          video.play();
});

        hls.on(Hls.Events.ERROR,(event, data)=>{
          console.error('HLS.js 播放错误:', data);
});
}elseif(video.canPlayType('application/vnd.apple.mpegurl')){
// 如果浏览器原生支持 HLS(如 Safari)
        video.src =this.videoSrc;
        video.addEventListener('loadedmetadata',()=>{
          video.play();
});
}else{
        console.error('该浏览器不支持 HLS 播放');
}
},
}
}

</script>

写在最后

本人是新手小白,如果这篇笔记中有任何错误或不准确之处,真诚地希望各位读者能够给予批评和指正,如有更好的实现方法请给我留言,谢谢!欢迎大家在评论区留言!觉得写得还不错的话欢迎大家关注一波!


对象存储2 OSS2 Gin5 Go13 服务器2 对象存储 · 目录 上一篇Gin存储文件与oss对象存储(一) 阅读 277   ​

标签:文件,存储,err,oss,ctx,gin,分片,Gin,上传
From: https://www.cnblogs.com/cheyunhua/p/18607316

相关文章

  • nginx 简介+应用
    文章目录nginx简介nginx二级目录处理二级目录实例列举1.第一个`location/`块2.第二个`location~^/(ui)`块3.第三个`location/api`块第一个`location/`与第二个`location~^/(ui)`是否重复nginx前端部署iframe嵌套配置设置后端服务转发实例......
  • maven docker-maven-plugin 发布docker 20241214
    1、docker开启远程访问 端口 2375  docker主机:192.168.177.128vi/usr/lib/systemd/system/docker.service#修改ExecStart这行ExecStart=/usr/bin/dockerd-Htcp://0.0.0.0:2375 #重新加载配置文件[root@localhost~]#systemctldaemon-reload#重启服务[......
  • Keepalived-Nginx实现高可用
    master机器配置文件:======================================================================!ConfigurationFileforkeepalived#全局配置global_defs{#路由ID,不能重复,通常为hostnamerouter_idmaster}#keepalived会定时执行脚本并对脚本执行的结果进行分析,动态......
  • docker搭建nginx-php环境
    首先,创建一个Dockerfile文件,内容如下:FROMphp:7.4-fpmRUNsed-i's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g'/etc/apt/sources.listRUNsed-i's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g'/etc/apt/sources.list#安装NginxRUNapt-ge......
  • AtCoder Beginner Contest 383
    省流版A.模拟加水漏水即可B.枚举两个加湿器的位置,然后统计加湿的单元格数量即可C.从每个加湿器进行\(BFS\)即可D.考虑因子个数的计算,分情况枚举质因数即可E.考虑\(f\)函数的求法,从小到大加边,考虑每条边对答案的贡献即可F.对颜色排序,在\(01\)背包的基础上,新增一个......
  • uniCloud云开发视频教程-从基础入门到项目开发实战-uniapp进阶课文章管理系统(云函数/
    uniCloud云开发视频教程-从基础入门到项目开发实战-uniapp进阶课文章管理系统(云函数/云数据库/云存储)https://www.bilibili.com/video/BV1PP411E7qG513894357@qq.comP11.1.uniCloud课程介绍unicloud可老P21.2.新建uniapp项目及创建uniCloud服务空...2022-10-12腾讯云收......
  • 使用从HuggingFace上下载Llama模型
    背景这里记录一下从HuggingFace上下载Llama模型的流程,方便后续使用。注册账号目前Llama的模型是需要提交申请才可以使用,因此需要先注册HuggingFace的账号。这里有两点需要注意。用户名会用来登录:用户名会用作后续下载模型使用,因此需要使用一个自己方便记忆的名称。用户名修......
  • 与一个方法将origin转化为tree,要求支持无限级和性能
    将一个扁平的origin数据结构转换为树形结构,并且需要支持无限级和高性能,这在前端开发中是一个常见问题。最佳方法取决于origin数据的具体格式和你的性能需求。以下提供几种方法,并分析其优缺点:方法一:递归方法(简单易懂,但可能性能较差)这种方法简单易懂,但对于大型数据集,递......
  • zenoh rest plugin 简单使用说明
    zenohrestplugin提供了restapi能力,包含了管理adminspace以及通过get,put,delete操作key的能力配置包含了独立模式以及plugin模式,可以解决实际场景使用,基于plugin模式是一个不错的选择参考配置{"mode":"router","plugins":{"mqtt":{"port":1883......
  • 转发:【AI系统】指令和存储优化
    除了应用极广的循环优化,在AI编译器底层还存在指令和存储这两种不同优化。指令优化指令优化依赖于硬件提供的特殊加速计算指令。这些指令,如向量化和张量化,能够显著提高计算密度和执行效率。向量化允许我们并行处理数据,而张量化则进一步扩展了这一概念,通过将数据组织成更高维度......