首页 > 其他分享 >大文件断点续传工具类

大文件断点续传工具类

时间:2022-10-20 15:12:32浏览次数:60  
标签:断点续传 文件 param File new import 工具 上传

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

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

赞赏一下

标签:断点续传,文件,param,File,new,import,工具,上传
From: https://www.cnblogs.com/gustavo/p/16809952.html

相关文章

  • JUC - 共享模型之工具 - 第六篇
    六、共享模型之工具1.线程池1.1自定义线程池步骤1:自定义拒绝策略接口@FunctionalInterface//拒绝策略interfaceRejectPolicy<T>{voidreject(BlockingQueu......
  • 42 C语言文件操作
    01什么是文件文件有不同的类型,在程序设计中,主要用到两种文件:(1)程序文件。包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。这种文件的内容时程序代码......
  • 奇迹客户端服务端目录PLAYER文件怎么改​
    奇迹客户端服务端目录PLAYER文件怎么改​我是艾西,很多想开服的小伙伴对游戏技术肯定是感兴趣的,今天跟大家聊聊Player目录文件代表什么意思怎么修改:​这一期就是大家很关心的......
  • wrk 性能测试工具
    常用的性能测试工具,如Apacheab,ApacheJMeter(互联网公司用的较多),LoadRunner等。我们今天主要说一说轻量级性能测试工具wrk。一、什么是wrkwrk是一款针对Http......
  • HTML快速入门和HTML标签-文件标签
    HTML快速入门快速入门:语法:1.html文档后缀名.html或者.htm2.标签分为1.围堵标签:有开始标签和结束标签。如<html> </html>2.自闭和标......
  • windows端ping 工具带时间戳保存ping记录到本地
    新建txt文件,复制代码保存到新建文件后,修改文件后缀为.bat文件后。双击运行后会在本地脚本相同路径生成日志记录文件@echooffset/phost=host:setlogfile=ping_%host......
  • SwitchResX(Mac屏幕分辨率修改工具)
    如何修改屏幕分辨率?switchresxmac是一款Mac上实用的屏幕分辨率修改工具,快速的帮助您更改Mac显示屏的分辨率。操作界面简单,采用嵌套的上下文菜单管理,更加方便您控制修改分......
  • Java并发编程学习8-同步工具类
    同步工具类同步工具类可以是任意一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,类似地还有信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)......
  • 【工具使用】docker(八)用例管理工具 testlink
    简介:基于web页面的测试用例管理系统,测试项目管理,产品需求管理,测试用例管理,测试几乎按惯例,测试用例的创建,管理和执行,并且还提供了统计的功能。部署数据库:mariadb......
  • Linux du命令:统计目录或文件所占磁盘空间大小
    du是统计目录或文件所占磁盘空间大小的命令。需要注意的是,使用"ls-r"命令是可以看到文件的大小的。但是大家会发现,在使用"ls-r"命令査看目录大小时,目录的大小多数是4KB......