文件分片实体类FileUploadDTO

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.web.multipart.MultipartFile;

@Data
@Accessors(chain = true)
public class FileUploadDTO {
    private Integer owerId;

    private Integer tenantId;
    //文件名
    @ApiModelProperty("文件名")
    private String name;

    //md5码
    @ApiModelProperty("md5码")
    private String md5;

    //文件总大小
    @ApiModelProperty("文件总大小")
    private Long size;

    //文件一共被分了多少个片
    @ApiModelProperty("文件一共被分了多少个片")
    private Integer chunks;

    //文件当前是第几个分片(第一个分片从0开始)
    @ApiModelProperty("文件当前是第几个分片(第一个分片从0开始)")
    private Integer chunk;

    //文件的数据
    @ApiModelProperty("文件数据")
    private MultipartFile file;
}
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Date;

文件分片实体类FileUploadVO 

@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileUploadVO {

    @ApiModelProperty("站点id")
    private Integer id;

    @ApiModelProperty("文件类型 1图片 2 视频  3音频 4其他")
    private Integer type;

    @ApiModelProperty("文件名")
    private String filename;

    @ApiModelProperty("文件url")
    private String url;

    @ApiModelProperty("缩略图")
    private String thumbnail;

    @ApiModelProperty("文件大小")
    private Long size;

    @ApiModelProperty("文件path")
    private String path;

}

文件分片上传工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;

public class FileUploadUtils {

    private static final Logger logger = LoggerFactory.getLogger(FileUploadUtils.class);

    private static String uploadFolder = "E:\\";
    private static String realPath = "https://baidu.com/";


    /**
     * key = md5
     * value = ChunkInfo
     * <p>
     * 通过md5记录文件分片数据
     */
    private static Map<String, ChunkInfo> fileMap = new HashMap<>();

    /**
     * 生成一个与文件md5关联的数据信息
     * <p>
     * 根据md5生成一个唯一的文件名和每个chunk的初始上传状态false
     *
     * @param uploadDTO
     * @return
     */
    public static String getFilename(FileUploadDTO uploadDTO) {
        String md5 = uploadDTO.getMd5();

        if (!checkFile(md5)) {
            synchronized (FileUploadUtils.class) {
                if (!checkFile(md5)) {
                    fileMap.put(md5, new ChunkInfo(uploadDTO.getName(), uploadDTO.getChunks()));
                }
            }
        }

        return fileMap.get(md5).uniqueName;
    }

    /**
     * 判断文件是否有分块
     *
     * @param md5
     * @return
     */
    public static Boolean checkFile(String md5) {
        return fileMap.containsKey(md5);
    }

    /**
     * 为文件的某个chunk添加上传完毕的分块记录
     *
     * @param md5   md5值
     * @param chunk 分块号
     */
    public static void addFileChunk(String md5, int chunk) {
        fileMap.get(md5).chunkStatus[chunk] = true;
    }

    /**
     * 删除文件信息
     *
     * @param md5
     */
    public static void removeFile(String md5) {
        if (checkFile(md5)) {
            fileMap.remove(md5);
        }
    }

    /**
     * 判断文件所有分块是否已上传完毕
     * @param md5
     * @return
     */
    public static boolean finished(String md5) {
        if (checkFile(md5)) {
            for (Boolean done : fileMap.get(md5).chunkStatus) {
                if (null == done || !done) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 上传文件分片
     *
     * @param uploadDTO
     */
    public static RestResponse uploadChunk(FileUploadDTO uploadDTO)  {
        RestResponse restResponse = new RestResponse<>();
        try{
            FileUploadVO uploadVO = saveChunk(uploadDTO);
            //指定某个chunk上传完毕
            String md5 = uploadDTO.getMd5();
            addFileChunk(md5, uploadDTO.getChunk());
            if(finished(md5)){
                logger.info("===================");
                logger.info("uploadChunk finished fileMap="+fileMap.toString());
                removeFile(md5);
                logger.info("uploadChunk finished fileMap="+fileMap.toString());
                logger.info("===================");
            }


            Integer chunks = uploadDTO.getChunks();
            Integer chunk = uploadDTO.getChunk();
            if (chunk == chunks - 1 /*&& chunk != 0*/) {
                restResponse = mergeFiles2(chunks,uploadDTO.getMd5(),uploadDTO.getName());
                uploadVO = (FileUploadVO) restResponse.getData();
                uploadVO.setSize(uploadDTO.getSize());
                restResponse.setData(uploadVO);
                restResponse.setMsg("文件上传成功");
                restResponse.setSuccess(true);
                return restResponse;
            }

            restResponse.setData(uploadVO);
            restResponse.setMsg("分片上传成功");
            restResponse.setSuccess(true);
            return restResponse;
        }catch (Exception e){
            logger.info("上传异常!!");
            e.printStackTrace();
        }
        restResponse.setMsg("上传失败!!");
        restResponse.setSuccess(false);
        return restResponse;

    }

    /**
     * 保存文件分片
     * @param uploadDTO
     * @throws Exception
     */
    private static FileUploadVO saveChunk(FileUploadDTO uploadDTO) throws Exception{
        //文件路径
        String filename = getFilename(uploadDTO);
        String md5 = uploadDTO.getMd5();
        String dirpath =  System.getProperty("file.separator")+getSysYear()+
                System.getProperty("file.separator")+getSysMonth()+
                System.getProperty("file.separator")+getSysDay()+System.getProperty("file.separator");
        String dest = uploadFolder + dirpath+ md5+System.getProperty("file.separator")+filename+"_"+uploadDTO.getChunk();

        File fileDirty = new File(uploadFolder+dirpath+uploadDTO.getMd5());
        if (!fileDirty.exists()) {
            fileDirty.mkdirs();
        }
        //得到文件信息
        MultipartFile file = uploadDTO.getFile();
        if (null == file) {
            throw new RuntimeException("缺少file数据");
        }
        InputStream fis = file.getInputStream();
        Long fileSize = uploadDTO.getSize();
        Long chunkSize = file.getSize();

        //得到总分片数和当前分片
        Integer chunks = uploadDTO.getChunks();
        Integer chunk = uploadDTO.getChunk();

        logger.info("===================");
        logger.info("saveChunk filename="+filename);
        logger.info("saveChunk dest="+dest);
        logger.info("saveChunk chunk="+chunk);
        logger.info("saveChunk md5="+uploadDTO.getMd5());
        logger.info("===================");

        //文件上传
        RandomAccessFile randomAccessFile = new RandomAccessFile(dest, "rw");
        randomAccessFile.setLength(chunkSize);
        //最后一个分片的size直接获得差值,其余的通过当前分片*size即可(工作条件是除最后一个分片外,每个分片size大小一致)
        /*if (chunk == chunks - 1 && chunk != 0) {
            randomAccessFile.seek(chunk * (fileSize - chunkSize) / chunk);
        } else {
            randomAccessFile.seek(chunk * chunkSize);
        }*/
        //保存数据
        byte[] buf = new byte[1024];
        int len;
        while (-1 != (len = fis.read(buf))) {
            randomAccessFile.write(buf, 0, len);
        }
        randomAccessFile.close();

        //返回的数据
        FileUploadVO uploadVO = new FileUploadVO();
        uploadVO.setFilename(filename);
        uploadVO.setUrl(realPath+dest);
        uploadVO.setPath(dest);
        logger.info("===================");
        logger.info("saveChunk uploadVO="+uploadVO);
        logger.info("===================");
        return uploadVO;
    }


    /**
     * 合并分片2
     * @return
     */
    public static RestResponse mergeFiles2(Integer chunks,String md5File, String name) {
        RestResponse restResponse = new RestResponse<>();
        String dirpath =   System.getProperty("file.separator")+getSysYear()+
                System.getProperty("file.separator")+getSysMonth()+
                System.getProperty("file.separator")+getSysDay()+System.getProperty("file.separator");
        List<String> list = readfile(uploadFolder+dirpath+md5File);
        if(chunks != null && list != null && !chunks.equals(list.size())){
            restResponse.setSuccess(false);
            restResponse.setMsg("文件不完整,请重新上传");
            return restResponse;
        }
        String[] fpaths = list.toArray(new String[list.size()]);

        String resultPath = uploadFolder+dirpath+generateFilenameSuffix(name);
        if (fpaths == null || fpaths.length < 1) {
            restResponse.setSuccess(false);
            restResponse.setMsg("分片失败!");
            return restResponse;
        }

        if (fpaths.length == 1) {
             new File(fpaths[0]).renameTo(new File(resultPath));
        }
        else{
            File[] files = new File[fpaths.length];
            for (int i = 0; i < fpaths.length; i ++) {
                files[i] = new File(fpaths[i]);
                if (!files[i].exists() || !files[i].isFile()) {
                    restResponse.setSuccess(false);
                    restResponse.setMsg("分片不存在!");
                    return restResponse;
                }
            }

            File resultFile = new File(resultPath);

            try {
                FileChannel resultFileChannel = new FileOutputStream(resultFile, true).getChannel();
                for (int i = 0; i < fpaths.length; i ++) {
                    FileChannel blk = new FileInputStream(files[i]).getChannel();
                    resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size());
                    blk.close();
                }
                resultFileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
                restResponse.setSuccess(false);
                restResponse.setMsg("分片异常!");
                return restResponse;
            }

            for (int i = 0; i < fpaths.length; i ++) {
                files[i].delete();
            }

        }
        File fileDirty = new File(uploadFolder+dirpath+md5File);
        if (fileDirty.exists()) {
            fileDirty.delete();
        }


        FileUploadVO uploadVO = new FileUploadVO();
        uploadVO.setFilename(name);
        uploadVO.setUrl(realPath+dirpath+generateFilenameSuffix(name));
        uploadVO.setPath(uploadFolder+dirpath);


        restResponse.setData(uploadVO);
        restResponse.setSuccess(true);
        return restResponse;
    }

    /**
     * 生成随机文件名
     *
     * 不要加后缀,加了后缀后会造成文件被额外的进程使用
     *
     * @return
     */
    public static String generateFilename(String filename) {
        // String suffix = filename.substring(filename.lastIndexOf(".") + 1);
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 生成带后缀名的文件名
     * @param filename
     * @return
     */
    public static String generateFilenameSuffix(String filename) {
        String suffix = filename.substring(filename.lastIndexOf(".") );
        return UUID.randomUUID().toString().replace("-", "")+suffix;
    }

    /**
     * 内部类记录分块上传文件信息
     */
    private static class ChunkInfo {
        //md5确定文件的唯一名称
        String uniqueName;

        //记录每个分块的状态,上传完毕与否
        Boolean[] chunkStatus;

        ChunkInfo(String oldFilename, Integer chunks) {
            this.uniqueName = generateFilename(oldFilename);
            this.chunkStatus = new Boolean[chunks];
        }
    }


    /**当前年份**/
    public static String getSysYear() {
        Calendar date = Calendar.getInstance();
        String year = String.valueOf(date.get(Calendar.YEAR));
        return year;
    }
    public static String getSysMonth() {
        Calendar date = Calendar.getInstance();
        String year = String.valueOf(date.get(Calendar.MONTH)+1);
        return year;
    }
    public static String getSysDay() {
        Calendar date = Calendar.getInstance();
        String year = String.valueOf(date.get(Calendar.DATE));
        return year;
    }

    /**
     * 读取某个文件夹下的所有文件
     */
    public static List<String> readfile(String filepath)  {
        List<String> list = new ArrayList<>();
        try {

            File file = new File(filepath);
            if (!file.isDirectory()) {
                System.out.println("文件");
                System.out.println("path=" + file.getPath());
                System.out.println("absolutepath=" + file.getAbsolutePath());
                System.out.println("name=" + file.getName());

            } else if (file.isDirectory()) {
                System.out.println("文件夹");
                String[] filelist = file.list();
                for (int i = 0; i < filelist.length; i++) {
                    File readfile = new File(filepath + System.getProperty("file.separator") + filelist[i]);
                    if (!readfile.isDirectory()) {
                        System.out.println("path=" + readfile.getPath());
                        System.out.println("absolutepath=" + readfile.getAbsolutePath());
                        System.out.println("name=" + readfile.getName());

                        list.add(readfile.getPath());
                    } else if (readfile.isDirectory()) {
                        readfile(filepath + System.getProperty("file.separator") + filelist[i]);
                    }
                }

            }

        } catch (Exception e) {
            System.out.println("readfile()   Exception:" + e.getMessage());
        }
        return list;
    }
}

调用Controller层方法

@PostMapping("upload")
@ApiOperation(value = "分片上传")
@ResponseBody
public RestResponse upload(FileUploadDTO dto) { //第几片,从0开始
   RestResponse restResponse = FileUploadUtils.uploadChunk(dto);
  
   return restResponse;
}

返回结果实体类

import java.io.Serializable;

public class RestResponse<T, B> implements Serializable {

    /**
     * 描述 : id
     */
    private static final long serialVersionUID = 1L;


    /**
     * 描述 : 状态码(业务定义)
     */
    private Integer code = RestStatusCode.OK.code();

    /**
     * 描述 : 状态码描述(业务定义)
     */
    private String msg = RestStatusCode.OK.message();

    /**
     * 描述 : 结果集(泛型)
     */
    private T data = null;

    private B success = null;

    /**
     * 描述 : 构造函数
     */
    public RestResponse() {
        super();
    }

    /**
     * 描述 : 构造函数
     *
     * @param result 结果集(泛型)
     */
    public RestResponse(T result, B suc) {
        this.data = result;
        this.success = suc;
    }

    /**
     * 描述 : 构造函数
     *
     * @param httpStatus http状态
     */
    public RestResponse(RestStatus httpStatus, B success) {
        this.code = httpStatus.code();
        this.msg = httpStatus.message();
        this.success=success;
    }

    /**
     * 描述 : 构造函数
     *
     * @param httpStatus http状态
     * @param result     数据集
     */
    public RestResponse(RestStatus httpStatus, T result,B success) {
        this.code = httpStatus.code();
        this.msg = httpStatus.message();
        this.success=success;
        this.data = result;
    }

    /**
     * 描述 : 构造函数
     *
     * @param code    状态码(业务定义)
     * @param message 状态码描述(业务定义)
     */
    public RestResponse(Integer code, String message) {
        this.code = code;
        this.msg = message;
    }

    /**
     * 描述 : 构造函数
     *
     * @param code    状态码(业务定义)
     * @param message 状态码描述(业务定义)
     * @param result  结果集(泛型)
     */
    public RestResponse(Integer code, String message, T result,B success) {
        this.code = code;
        this.msg = message;
        this.data = result;
        this.success=success;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public B getSuccess() {
        return success;
    }

    public void setSuccess(B success) {
        this.success = success;
    }
}