视频分段上传服务实现

该文描述了一个分片文件上传的逻辑,包括单片上传和多片合并。当只有一个分片时直接上传,否则将分片存储在Redis中,待所有分片上传完成后,使用固定线程池下载并合并成完整文件,最后上传到指定目录并删除临时文件。
摘要由CSDN通过智能技术生成

入参:

//任务ID
private String identifier;
//当前为第几块分片
private int chunkNumber;
//总分片数量
private int totalChunks;
//当前分片大小
private long currentChunkSize = 0L;
//每分片大小
private long chunkSize = 0L;
//总大小
private long totalSize = 0L;
//文件名
private String filename;
//分片对象
private MultipartFile file;

逻辑:

校验资源类型

判断分片文件数量(如果只有一个分片文件就直接上传,多个分片文件才走特殊流程)

if (param.getTotalChunks() == 1) {
    return uploadFile(param);
}

多个文件上传到指定唯一存放数据的服务器临时目录(不能放在多实例服务器上面)

每个上传文件存储redis并判断是否传完

redisUtil.set(RedisConstant.UPLOAD_FILE.concat(param.getIdentifier() + (param.getChunkNumber() - 1)), param.getFilename(), 600, TimeUnit.SECONDS);
boolean isSuccess = true;
for (int i = 0; i < param.getTotalChunks(); i++) {
    if (!redisUtil.hasKey(RedisConstant.UPLOAD_FILE.concat(param.getIdentifier() + i))) {
        isSuccess = false;
    }
}
if (isSuccess) {
    return mergeMultipleFile(param);
}

上传完成开始合并

创建10个线程数量的线程池下载所有的块文件

private final ExecutorService threadPool = Executors.newFixedThreadPool(10);
private File[] downloadChunkFilesFromMinio(MultipartFileParam param, String temporaryFilePath) throws Exception {
        // 得到分块文件所在目录
        String chunkFileFolderPath = temporaryFilePath + param.getIdentifier();
        // 块总数
        int chunkTotal = param.getTotalChunks();
        // 分块文件数组
        File[] chunkFiles = new File[chunkTotal];
        // 设置计数器
        CountDownLatch countDownLatch = new CountDownLatch(chunkTotal);
        // 开始逐个下载
        for (int i = 0; i < chunkTotal; i++) {
            int index = i;
            threadPool.execute(() -> {
                //得到分块文件的路径
                String chunkFilePath = chunkFileFolderPath + index;
                //下载分块文件
                try {
                    chunkFiles[index] = minioUtils.downloadFile("chunk", null, bucketName, chunkFilePath);
                } catch (Exception e) {
                    // 计数器减1
                    countDownLatch.countDown();
                    throw new RuntimeException(e);
                }
                // 计数器减1
                countDownLatch.countDown();
            });
        }
        /*
            阻塞到任务执行完成,当countDownLatch计数器归零,这里的阻塞解除等待,
            给一个充裕的超时时间,防止无限等待,到达超时时间还没有处理完成则结束任务
         */
        countDownLatch.await(30, TimeUnit.MINUTES);
        //返回所有块文件
        return chunkFiles;
    }

将所有块文件合成生成一个新的完整临时源文件

上传该临时源文件到指定目录

删除所有的临时文件

//开始合成
        int index = 0;
        for (File file : chunkFiles) {
            //上传文件到指定目录
            File newFile = null;
            try {
                // 创建临时文件
                String tmpFileName = param.getIdentifier() + "_tmp";
                File tmpDir = new File(tempPath);
                if (!tmpDir.exists()) {
                    tmpDir.mkdirs();
                }
                File tmpFile = new File(tempPath, tmpFileName);
                RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
                FileChannel fileChannel = tempRaf.getChannel();
                //写入该分片数据
                long offset = param.getChunkSize() * index;
                FileInputStream fileInputStream = new FileInputStream(file);
                MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(), ContentType.OCTET_STREAM.toString(), fileInputStream);
                byte[] fileData = multipartFile.getBytes();
                MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
                mappedByteBuffer.put(fileData);
                // 释放
                fileChannel.close();
//检查文件是否已完整
                boolean isOk = checkAndSetUploadProgress(param.getTotalChunks(), index, tempPath, param.getIdentifier());
                if (isOk) {
                    String fileName = param.getFilename();
                    int lastIndex = fileName.lastIndexOf(".");
                    String type = fileName.substring(lastIndex + 1);
                    String newFileName = resourceId.concat("_").concat(String.valueOf(System.currentTimeMillis())).concat(".").concat(type);
                    newFile = new File(tempPath, newFileName);
                    //修改文件名
                    tmpFile.renameTo(newFile);
                    FileInputStream newFileInputStream = new FileInputStream(newFile);
                    MultipartFile newMultipartFile = new MockMultipartFile(newFile.getName(), newFile.getName(), ContentType.OCTET_STREAM.toString(), newFileInputStream);
                    //上传文件

                    //删除临时文件

                }
                index++;
            } catch (Exception e) {
                e.printStackTrace();
                throw new BaseException(BusinessError.FILE_COMPOSE_ERROR);
            } finally {
//删除临时文件
                if (file != null && file.exists()) {
                    file.delete();
                }
                if (newFile != null && newFile.exists()) {
                    newFile.delete();
                }
            }
        }



private boolean checkAndSetUploadProgress(long totalChunks, long chunkNumber, String uploadDirPath, String fileName) throws IOException {
        File confFile = new File(uploadDirPath, fileName + ".conf");
        RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
        //把该分段标记为 true 表示完成
        accessConfFile.setLength(totalChunks);
        accessConfFile.seek(chunkNumber);
        accessConfFile.write(Byte.MAX_VALUE);
        //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
        byte[] completeList = FileUtils.readFileToByteArray(confFile);
        byte isComplete = Byte.MAX_VALUE;
        for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
            isComplete = (byte) (isComplete & completeList[i]);
        }
        accessConfFile.close();
        if (isComplete == Byte.MAX_VALUE) {
            confFile.delete();
            return true;
        } else {
            return false;
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现视频分段上传服务器并返回本地地址,可以按照以下步骤: 1. 使用Python的`requests`库或其他第三方库从本地读取视频文件,或直接从网络中下载视频文件。 2. 根据需要将视频文件分成多个块,每个块的大小可以根据实际情况进行设置。 3. 为每个块生成一个唯一的文件名,可以使用时间戳和随机字符串等方式生成唯一文件名。 4. 将每个块分别上传服务器上,可以使用`requests`库或其他第三方库进行上传,并确保每个块的大小不超过服务器的限制。 5. 如果需要,可以使用某种索引机制来记录每个块的文件名和文件位置,以便在需要时能够快速访问每个块。 6. 在服务器上将所有块组合成一个完整的视频文件,并返回该文件的本地地址。 以下是一个简单的Python代码示例,用于将视频文件分成多个块并上传服务器上: ```python import requests import os url = 'http://example.com/upload' chunk_size = 1024 * 1024 # 1 MB # 分块上传视频文件 def upload_video(video_path): chunk_paths = [] with open(video_path, 'rb') as f: for chunk_num, chunk in enumerate(iter(lambda: f.read(chunk_size), b'')): chunk_filename = f'video_chunk_{chunk_num}.mp4' chunk_paths.append(chunk_filename) files = {'file': (chunk_filename, chunk)} response = requests.post(url, files=files) if response.status_code != 200: raise Exception(f'Failed to upload chunk {chunk_num} ({response.status_code})') return chunk_paths # 将所有块组合成一个完整的视频文件 def combine_video(chunk_paths): with open('video.mp4', 'wb') as f: for chunk_path in chunk_paths: with open(chunk_path, 'rb') as chunk_file: f.write(chunk_file.read()) os.remove(chunk_path) return os.path.abspath('video.mp4') # 示例用法 video_path = 'video.mp4' chunk_paths = upload_video(video_path) video_path = combine_video(chunk_paths) print(f'Video uploaded and saved to {video_path}') ``` 该代码将从指定的本地文件读取视频文件,并将其分成大小为1MB的块。每个块都将分别上传到指定的URL,以单独的文件形式保存在服务器上。在上传完成后,所有块将在服务器上被组合成一个完整的视频文件,并将该文件的本地路径返回给调用者。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值