前端页面代码
<script>
import axios from "axios";
import {uploadChunk} from "@/http/api.js";
export default {
name: 'Video',
data() {
return {
file: null,
};
},
created() {
},
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
},
async upload() {
const chunkSize = 1024 * 1024; // 每个分块的大小(这里设置为1MB)
const totalChunks = Math.ceil(this.file.size / chunkSize); // 总的分块数量
let currentChunk = 0; // 当前上传的分块索引
while (currentChunk <= totalChunks) {
const start = currentChunk * chunkSize;
const end = Math.min(this.file.size, start + chunkSize);
const formData = new FormData();
formData.append('file', this.file.slice(start, end));
formData.append('chunkNumber', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('filename', this.file.name);
formData.append('videoId', this.file.name);
try {
// 发送分块数据到服务器
await uploadChunk(formData);
currentChunk++;
} catch (error) {
console.error('Error uploading chunk:', error);
// 处理上传错误
break;
}
}
// 所有分块上传完成后的处理
console.log('Upload complete!');
},
},
computed: {},
}
</script>
<template>
<div>
<input type="file" @change="handleFileChange" />
<button @click="upload">Upload</button>
</div>
</template>
<style scoped>
</style>
后端代码
package com.example.demo.controller.video;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 视频传输
**/
@RestController
@RequestMapping("/video")
public class MP4Controller {
@Autowired
RedisTemplate<String,String> redisTemplate;
//redis实现
@PostMapping("/upload")
public void uploadVideoChunkRedis(@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("videoId") String videoId) {
String key = "video:" + videoId;
String chunkKey = key + ":chunk:" + chunkNumber;
//前端可以生成文件的唯一id videoId
//可以判断 chunkKey 是否在redis存在存在则不需要缓存直接返回
/*
* 判断代码 ...
* */
try {
byte[] chunkData = file.getBytes();
// 存储分块数据到Redis
redisTemplate.opsForValue().set(chunkKey, new String(chunkData, StandardCharsets.ISO_8859_1));
// 判断是否已上传完所有分块
if (chunkNumber == totalChunks) { // 以下方法可以自行封装
//为了防止网络问题第2个还没有上传完成第3个 或者 最后一个 已经完成了
boolean redisChunk = redisChunk(totalChunks, key, 3);
if(!redisChunk){
// 返回网络错误提示 用户可以重新上传
}
// 合并所有分块并进行后续处理
StringBuilder videoData = new StringBuilder();
for (int i = 0; i <= totalChunks; i++) {
String chunkDataStr = redisTemplate.opsForValue().get(key + ":chunk:" + i);
videoData.append(chunkDataStr);
redisTemplate.delete(key + ":chunk:" + i);
}
// 执行视频处理逻辑,例如保存到磁盘或进行进一步处理
saveVideoData(videoData.toString(),videoId);//不推荐该方法内小的服务器会内存溢出
}
} catch (IOException e) {
// 处理上传失败的情况
e.printStackTrace();
}
}
private boolean redisChunk(int totalChunks,String key,int size) {
if(size<0){
return false;
}
size--;
for (int i = 0; i <= totalChunks; i++) {
String chunkDataStr = redisTemplate.opsForValue().get(key + ":chunk:" + i);
//判断 是否每个快都上传完成了
//逻辑代码 ...
if(StringUtils.isEmpty(chunkDataStr)){ //如果该数据不存在则说明网络问题没有上传全快数据 - 方法一
try {
Thread.sleep(3000); //等待3秒再去查询 不推荐该方法
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
redisChunk(totalChunks,key,size);
}
if(StringUtils.isEmpty(chunkDataStr)){ //如果该数据不存在则说明网络问题没有上传全快数据 - 方法二
//直接返回没有上传的快告诉前端让前端
// 重新上传 以此举例
// 该 某 快 数据 如果上传过程中已经存在会被缓存查出直接返回
// 这里前端需要给个标识告诉后端 这是补充快 可直接执行合并方法
return false;
} // 方法三 前端记录所有上传结果 如果全部上传完成 最后请求一个合并请求即可
}
return true;
}
private void saveVideoData(String videoData,String videoId) {
// 在这里可以将视频数据保存到磁盘或进行其他处理操作
// System.out.println("Received video data: " + videoData);
String outputFile = "datafile/"+videoId; // 保存到磁盘的文件路径
try (FileOutputStream fos = new FileOutputStream(outputFile)){
byte[] videoBytes = videoData.getBytes(StandardCharsets.ISO_8859_1);
fos.write(videoBytes);
fos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 磁盘实现
// @PostMapping("/upload")
public String uploadVideoChunk(@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("filename") String filename) throws IOException {
// 创建上传目录(如果不存在)
String uploadPath = "datafile/chunk/";
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
boolean mkdirs = uploadDir.mkdirs();
System.out.println("文件创建:"+mkdirs);
}
// 生成保存文件名
String saveFileName = StringUtils.cleanPath(filename + "_chunk_" + chunkNumber);
// 保存分块文件
File chunkFile = new File(uploadDir, saveFileName);
FileUtils.copyInputStreamToFile(file.getInputStream(), chunkFile);
// 检查是否已接收到所有分块
if (chunkNumber == totalChunks) {
// 如果是最后一个分块,合并文件
File finalFile = new File(uploadDir, filename);
for (int i = 0; i <= totalChunks; i++) {
File partFile = new File(uploadDir, StringUtils.cleanPath(filename + "_chunk_" + i));
FileUtils.writeByteArrayToFile(finalFile, FileUtils.readFileToByteArray(partFile), true);
boolean delete = partFile.delete();
System.out.println("文件删除:"+delete+"-状态:"+filename + "_chunk_" + i);
}
// 执行保存和其他后续操作
// ...
}
return "upload_success";
}
}