前端页面代码

<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";
    }
}