• 大文件断点续传工具类


    分享一个大文件断点续传的工具类,实测效果很好。

    package com.skyworth.file.util;
    
    import cn.hutool.core.io.FileUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.skyworth.file.model.FileInfo;
    import com.skyworth.file.model.Response;
    import com.skyworth.file.model.UploadFileParam;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    import org.apache.commons.io.FileUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * @apiNote 大文件-断点续传工具类
     * @date 2022/10/9
     **/
    @Slf4j
    @Component
    public class LocalUpload {
    
        /**
         * 秒传、断点的文件MD5验证
         * 根据文件路径获取要上传的文件夹下的 文件名.conf 文件
         * 通过判断 .conf 文件状态来验证
         */
        public static Response checkFileMd5(String fileMd5, String fileName, String confFilePath, String tmpFilePath) throws Exception {
            boolean isParamEmpty = StringUtils.isBlank(fileMd5) || StringUtils.isBlank(fileName) || StringUtils.isBlank(confFilePath) || StringUtils.isBlank(tmpFilePath);
            if (isParamEmpty) {
                throw new Exception("参数值为空");
            }
            //构建分片配置文件对象
            File confFile = new File(confFilePath + File.separatorChar + fileName + ".conf");
            //布尔值:上传的文件缓存对象是否存在
            boolean isTmpFileEmpty = new File(tmpFilePath + File.separatorChar + fileName + "_tmp").exists();
            if (confFile.exists() && isTmpFileEmpty) {
                byte[] completeList = FileUtils.readFileToByteArray(confFile);
                List missChunkList = new LinkedList<>();
                for (int i = 0; i < completeList.length; i++) {
                    if (completeList[i] != Byte.MAX_VALUE) {
                        missChunkList.add(Integer.toString(i));
                    }
                }
                JSONArray jsonArray = JSON.parseArray(JSONObject.toJSONString(missChunkList));
                return Response.createOKResponse(HttpStatus.PARTIAL_CONTENT.value(), "文件已部分上传", jsonArray);
            }
            //布尔值:上传的文件对象是否存在
            boolean isFileEmpty = new File(tmpFilePath + File.separatorChar + fileName).exists();
            if (isFileEmpty && confFile.exists()) {
                return Response.createErrorResponse(HttpStatus.OK.value(), "文件已上传成功");
            }
            return Response.createErrorResponse(HttpStatus.NOT_FOUND.value(), "文件不存在");
        }
    
    
        /**
         * 文件分片、断点续传上传程序
         * 创建 文件名.conf 文件记录已上传分片信息
         * 使用 RandomAccessFile(随机访问文件) 类随机指定位置写入文件,类似于合成分片
         * 检验分片文件是否全部上传完成,重命名缓存文件
         */
        public static synchronized Response fragmentFileUploader(UploadFileParam param, String confFilePath, String filePath, long chunkSize, HttpServletRequest request) throws Exception {
            boolean isParamEmpty = StringUtils.isBlank(filePath) || StringUtils.isBlank(confFilePath) && param.getFile() == null;
            if (isParamEmpty) {
                throw new Exception("参数值为空");
            }
            //判断enctype属性是否为multipart/form-data
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (!isMultipart) {
                throw new IllegalArgumentException("上传内容不是有效的multipart/form-data类型.");
            }
            try {
                //分片配置文件
                File confFile = FileUtil.file(FileUtil.mkdir(confFilePath), String.format("%s.conf", param.getName()));
                RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
                //把该分段标记为 true 表示完成
                accessConfFile.setLength(param.getChunks());
                accessConfFile.seek(param.getChunk());
                accessConfFile.write(Byte.MAX_VALUE);
                accessConfFile.close();
                //_tmp的缓存文件对象
                File tmpFile = FileUtil.file(FileUtil.mkdir(filePath), String.format("%s_tmp", param.getName()));
                //随机位置写入文件
                RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
                long offset = chunkSize * param.getChunk();
                //定位到该分片的偏移量、写入该分片数据、释放
                accessTmpFile.seek(offset);
                accessTmpFile.write(param.getFile().getBytes());
                accessTmpFile.close();
                //检查是否全部分片都成功上传
                byte[] completeList = FileUtils.readFileToByteArray(confFile);
                byte isComplete = Byte.MAX_VALUE;
                for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
                    // 与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
                    isComplete = (byte) (isComplete & completeList[i]);
                }
                if (isComplete != Byte.MAX_VALUE) {
                    return Response.createErrorResponse(HttpStatus.OK.value(), "文件上传成功");
                }
                boolean isSuccess = renameFile(tmpFile, param.getName());
                if (!isSuccess) {
                    throw new Exception("文件重命名时失败");
                }
                //全部上传成功后构建文件对象
                FileInfo fileInfo = FileInfo.builder()
                        .hash(param.getMd5())
                        .name(param.getName())
                        .type(param.getFile().getContentType())
                        .path(tmpFile.getParent() + File.separatorChar + param.getName())
                        .createTime(System.currentTimeMillis())
                        .build();
                return Response.createOKResponse(HttpStatus.CREATED.value(), "文件上传完成", fileInfo);
            } catch (IOException e) {
                e.printStackTrace();
                return Response.createErrorResponse("文件上传失败");
            }
        }
    
        /**
         * 用于上传成功后重命名文件
         */
        private static boolean renameFile(File toBeRenamed, String toFileNewName) {
            //检查要重命名的tmp文件是否存在,是否是文件
            if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
                return false;
            }
            //修改tmp文件名-文件传输完成
            File newFile = new File(toBeRenamed.getParent() + File.separatorChar + toFileNewName);
            //return toBeRenamed.renameTo(newFile);
            //下面逻辑兼容两个客户端同时上传一个文件,如果不需要,可忽略
            if (!newFile.exists()) {
                return toBeRenamed.renameTo(newFile);
            }
            if (newFile.exists() && toBeRenamed.exists()) {
                toBeRenamed.delete();
            }
            return true;
        }
    }
    
    

    赞赏一下

  • 相关阅读:
    漏洞分析|Adobe ColdFusion WDDX 序列化漏洞利用
    python 内置函数或函数(争取日更)
    Docker in Docker(DinD)原理与实践
    启动 idea 弹出“Failed to load JVM DLL\bin\server\jvm.dll”错误的解决方法
    Worthington 胆碱酯酶,丁酰相关说明书
    安科瑞支持以太网通讯、profibus通讯嵌入式电能表APM指导性技术要求-Susie 周
    今天不写代码,聊聊热门的知识图谱
    橘子学JVM之命令行监控05之jstack
    1514_人月神话阅读笔记_20年后的人月神话_上篇
    八核智能视频分析边缘计算盒子,4.8T+24路1080P视频解码
  • 原文地址:https://www.cnblogs.com/gustavo/p/16809952.html