Easy云盘总结篇-文件上传01

说在前面:此项目是跟着B站一位大佬做的,不分享源码,支持项目付费

文章目录

文件列表


这段的逻辑

前端传过来query,包含用户上传文件的相关内容,和类型category

然后通过枚举类FileCategoryEnums进行文件类型识别,如果找到对应枚举,就设置好文件类型

然后就是设置好用户id,和排序顺序,文件使用情况(使用中)

将查询到的用户文件以PaginationResultVO返回,其中PaginationResultVO的list装FileInfo

然后需要返回给前端FileInfoVo类型而并非实体类类型FileInfo,所以这里编写了一个类型转换方法:convert2PaginationVo

java 复制代码
protected <S,T> PaginationResultVO<T> convert2PaginationVo(PaginationResultVO<S> result,Class<T>  classz ) {
        PaginationResultVO<T> resultVO=new PaginationResultVO<>();
        resultVO.setList(CopyTools.copyList(result.getList(),classz));
        resultVO.setPageNo(result.getPageNo());
        resultVO.setPageSize(result.getPageSize());
        resultVO.setPageTotal(result.getPageTotal());
        resultVO.setTotalCount(result.getTotalCount());
        return resultVO;
    }

这个逻辑就是将PaginationResultVO里的list类型进行s换为t,其他数据直接复制过去的过程。

文件上传

这个逻辑蛮复杂,文件上传分普通上传(第一次传)和秒传(上传相同文件)。其中还需要对文件进行分片处理,过大的文件就需要拆分成小的分片逐步上传。

文件秒传

这段的逻辑:

当分片为0,也就是传过来第一个分片时,进行文件是否在库里的判断,如果有,就秒传。

这里判断秒传:根据md5值查询到第一页使用中的文件

如果不为空,则实现秒传:

先判断当前使用空间+文件大小是否>总空间,抛出对应异常

最后就是将文件进行属性赋值,再将文件插入到表中更新用户空间。

当时有个疑惑点:为什么秒传文件是insert而不是update,现在一想,明白如果update那么意味着该用户始终只有一个文件,而他上传的相同文件相当于覆盖了之前的文件,不符合逻辑。

然后就是说明文件的重命名问题:

分片上传

在前面如果不实现秒传,则进行分片上传

这段逻辑:

当第一个分片来时,先判断是否需要秒传,不是的话执行后面分片上传。当第2第3等分片来时,不再判断秒传,直接分片上传。

分片上传,先判断空间大小,这里,因为是分片,所有就创建了临时文件大小,临时空间(已经上传了的分片大小)+用户已存文件空间+文件大小(当前分片文件大小)>总空间,就抛出异常。

然后分片文件没上传完时,dto要设置状态为"上传中",并且保存当前分片的临时大小。

假如5个分片,当前分片如果是4时,就已经上传完成了,不走if。

最后,当最后一个分片也上传后:

文件合并

因为我们上传的是一个一个的分片,临时存储在temp文件夹中,还需要将这些分片合并,才是真正实现上传了文件。


这段逻辑:

在最后一个分片上传之前的分片,只是设置了上传状态和保存临时分片大小。在最后一个分片上传后,创建FileInfo,将整个文件的信息进行属性填充,插入到数据库文件表中。

然后更新用户当前空间。

最后如果没上传成功或者临时存放文件夹都没有,就上传失败,删除文件夹。

在事务提交后,执行异步合并文件。

异步

异步通常指的是非阻塞的操作,也就是说在合并分片的过程中,不需要等待所有分片都准备好才开始处理,而是可以一边接收分片一边处理,或者在后台进行合并操作,不影响主线程或其他任务的执行。

转码过程:
转码

转码 (Transcoding)是指将数据从一种编码格式转换为另一种编码格式的过程,通常用于适配不同设备、平台或需求。其核心目标是解决兼容性问题优化数据存储/传输效率



这段逻辑:

通过传过来的源路径,找到需要合并的文件,然后listFiles()获取到这个路径下的这个文件的所有分片。然后就是就是io不断的读取所有的分片文件,把所有的数据都存储到目标文件中。

listFiles()

获取目录下的文件和子目录

RandomAccessFile

RandomAccessFile 是Java中用于访问文件的类,它允许随机访问文件,即可以跳转到文件的任意位置进行读写操作。这一特性使得 RandomAccessFile 在需要访问文件的部分内容时非常有用,而不是从头到尾完整地读取文件

视频切割

为什么要视频切割:

可以按需加载和播放视频,而不需要一次性下载完整的视频文件,从而提高视频的加载速度和播放效率


这段的逻辑:

将视频先转换成一个大的index.ts文件,然后再将它切割成几个小的ts文件和一个.m3u8索引文件(类似目录),最后删除index.ts文件。

缩略图

一般图片类型和视频需要做缩略图:

无论转码成功与否,将整个文件的文件大小,文件缩略图,文件状态(使用中/转码失败),进行文件更新,

这里通过对老状态(转码中)的限制,只有当数据库中文件的当前状态等于 oldStatus 时,才会执行状态更新。若此时状态已被其他线程修改,则更新失败(不会影响数据)。避免了转码流程中的状态覆盖和顺序错乱问题。这就是乐观锁。

生成缩略图这里主要是FFmpeg命令行知识

java 复制代码
/**
     * 为视频生成封面图(通过截取视频的一帧画面)
     * @param sourceFile 原始视频文件(输入文件)
     * @param width 生成封面图的目标宽度(高度会根据原始视频宽高比自动计算)
     * @param targetFile 生成的封面图文件(输出文件)
     */
    public static void createCover4Video(File sourceFile, Integer width, File targetFile) {
        try {
            // FFmpeg 命令模板说明:
            // -i: 指定输入文件(原始视频)
            // -y: 覆盖已存在的输出文件(避免冲突)
            // -vframes 1: 仅提取1帧画面(作为封面)
            // -vf scale=%d:%d/a: 视频过滤(Video Filter)参数,设置缩放规则
            //   第一个%d: 目标宽度(传入的width)
            //   第二个%d/a: 高度按原始宽高比自动计算(保持比例避免画面变形)
            // %s: 输出文件路径(生成的封面图)
            String cmd = "ffmpeg -i %s -y -vframes 1 -vf scale=%d:%d/a %s";

            // 格式化命令并执行(通过ProcessUtils工具类调用系统命令)
            // 第二个参数false表示不等待命令执行完成(异步执行)
            ProcessUtils.executeCommand(
                    String.format(cmd,
                            sourceFile.getAbsoluteFile(),  // 原始视频路径
                            width,                         // 目标宽度
                            width,                         // 用于计算高度的基准值(与宽度相同以保持比例)
                            targetFile.getAbsoluteFile()   // 封面输出路径
                    ),
                    false
            );
        } catch (Exception e) {
            // 记录生成封面失败的异常信息(包括堆栈跟踪)
            logger.error("生成视频封面失败", e);
        }
    }
/**
     * 使用 FFmpeg 为图片生成缩略图(仅当原始图片宽度超过指定阈值时触发压缩)
     * @param file 原始图片文件(输入文件)
     * @param thumbnailWidth 缩略图的目标宽度(高度自动按比例计算)
     * @param targetFile 生成的缩略图文件(输出文件)
     * @param delSource 是否删除原始图片文件(true: 删除;false: 保留)
     * @return 布尔值,表示是否执行了压缩操作(true: 执行了压缩;false: 未压缩,因原始宽度未超过阈值)
     */
    public static Boolean createThumbnailWithFfmpeg(File file, int thumbnailWidth, File targetFile, Boolean delSource) {
        try {
            // 读取原始图片的宽高信息(通过ImageIO读取图片元数据)
            BufferedImage src = ImageIO.read(file);
            // 注意:若ImageIO无法读取该图片格式(如WebP、HEIC等非标准格式),src会为null,后续操作会抛出空指针异常

            // 获取原始图片的宽度和高度
            int sorceW = src.getWidth();   // 原始宽度
            int sorceH = src.getHeight();  // 原始高度

            // 关键逻辑:若原始宽度 <= 目标缩略图宽度,无需压缩,直接返回false
            if (sorceW <= thumbnailWidth) {
                return false;
            }

            // 调用压缩方法(实际通过FFmpeg执行缩放操作)
            compressImage(file, thumbnailWidth, targetFile, delSource);
            return true;  // 标记已执行压缩
        } catch (Exception e) {
            // 打印异常堆栈(实际生产环境建议替换为日志记录)
            e.printStackTrace();
        }
        return false;  // 异常时返回未压缩
    }

    /**
     * 使用 FFmpeg 压缩图片(按指定宽度缩放,高度自动保持比例)
     * @param sourceFile 原始图片文件(输入文件)
     * @param width 目标宽度(高度自动按原始宽高比计算)
     * @param targetFile 压缩后的图片文件(输出文件)
     * @param delSource 是否删除原始文件(true: 删除;false: 保留)
     */
    public static void compressImage(File sourceFile, Integer width, File targetFile, Boolean delSource) {
        try {
            // FFmpeg 命令模板说明:
            // -i: 指定输入文件(原始图片)
            // -vf scale=%d:-1: 视频过滤参数,设置缩放规则
            //   %d: 目标宽度(传入的width)
            //   -1: 高度自动计算(保持原始宽高比例)
            // %s: 输出文件路径(压缩后的图片)
            // -y: 覆盖已存在的输出文件
            String cmd = "ffmpeg -i %s -vf scale=%d:-1 %s -y";

            // 格式化并执行命令
            ProcessUtils.executeCommand(
                    String.format(cmd,
                            sourceFile.getAbsoluteFile(),  // 原始图片路径
                            width,                         // 目标宽度
                            targetFile.getAbsoluteFile()   // 压缩后输出路径
                    ),
                    false
            );

            // 若需要删除原始文件,则通过FileUtils强制删除(注意:需确保文件未被其他进程占用)
            if (delSource) {
                FileUtils.forceDelete(sourceFile);
            }
        } catch (Exception e) {
            // 记录压缩失败的异常信息(不记录堆栈,仅记录错误信息)
            logger.error("压缩图片失败");
        }
    }
相关推荐
在未来等你9 分钟前
互联网大厂Java求职面试:核心技术点深度解析
java·性能优化·架构设计·互联网大厂面试·核心技术点·技术总监·程序员郑薪苦
李匠202411 分钟前
C++负载均衡远程调用学习之订阅功能与发布功能
c++·学习
李匠202415 分钟前
C++负载均衡远程调用学习之上报功能与存储线程池
c++·学习
豆沙沙包?19 分钟前
2025年- H19-Lc127-48.旋转矩阵(矩阵)---java版
java·线性代数·矩阵
小虎卫远程打卡app26 分钟前
视频编解码学习三之显示器
学习·计算机外设·视频编解码
red_redemption1 小时前
自由学习记录(58)
学习
懒懒小徐1 小时前
2023华为od机试C卷【跳格子3】
java·华为od·动态规划
moxiaoran57531 小时前
Kubernetes(k8s)学习笔记(五)--部署Ingress实现域名访问和负载均衡
笔记·学习·kubernetes
奔驰的小野码1 小时前
SpringAI实现AI应用-搭建知识库
java·人工智能·spring boot·后端·spring·知识图谱
好奇龙猫2 小时前
日语学习-日语知识点小记-进阶-JLPT-N1阶段(1):语法单词
学习