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();// 继续上传
}
},
后端思路与实现
- 接收上传分片;将分片先暂存到某个文件夹,并根据上传的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,
})
}
- 合并分片接口。就说合并时要顺序读取分片,不然合并后文件就损坏了。
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