完整项目在此:https://github.com/NoonLoveSnow/bigfileupload.git
先说下为什么用数据库记录文件分片,因为文件分片可能不会存储在本机上,用数据库可以记录分片存储位置,方便获取。。合并后的文件也可以记录其md5值,作为真实文件,用户文件可以只需要新建表引用它就行了。
大文件传输主要注意的有以下几点:
大文件传输要求:
大文件传输往往是比较耗时的,所以要求传输操作可继续的即断点续传,不能因为一些故障导致重新上传整个文件。
断点续传思路:
在浏览器传输时需要将文件分片上传,当遇到意外情况继续上传时,则之前传输完成的分片就不用传输了而传输未上传成功的分片。
如何记录上传成功的分片:
文件MD5与分片编号作为联合主键记录在数据库中。
分片上传前如何判断分片已经传输完成:
数据库中有记录,且文件长度与分片大小(chunkSize)相等
合并后文件校验:
文件大小是否不一致,每个分片应有的大小加起来与合并后的文件相比较
关于WebUploader:
网上有很多教程和例子。。
主要代码
后端:
package com.noonsnow.bigfileupload.controller;
import com.alibaba.fastjson.JSONObject;
import com.noonsnow.bigfileupload.mapper.FileBlockMapper;
import com.noonsnow.bigfileupload.mapper.UploadFileMapper;
import com.noonsnow.bigfileupload.pojo.FileBlock;
import com.noonsnow.bigfileupload.pojo.UploadFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.Date;
import java.util.List;
//http://localhost:8081/uploadPage
@Controller
public class FileUploadController {
@Autowired
FileBlockMapper blockMapper;
@Autowired
UploadFileMapper fileMapper;
final static String filesDir = "C:/files";
final static String blocksDir = "C:/bolcks";
@RequestMapping("uploadPage")
public String page() {
return "bigFileUpload";
}
@PostMapping("checkFileIsExist")//检查文件是否存在
@ResponseBody
public Object checkFileExist(String md5value) {
JSONObject resp = new JSONObject();
List<UploadFile> uploadFiles = fileMapper.getByMd5(md5value);//逻辑上只有一个文件,由于不是按主键查找返回的是一个List
if (uploadFiles.size() == 0)
resp.put("exist", "0");
else
resp.put("exist", 1);
return resp.toString();
}
@PostMapping("checkChunkIsExist")//检查分片是否存在且完整
@ResponseBody
public Object checkChunkExist(String md5value, int chunk, int chunkSize) {
JSONObject resp = new JSONObject();
//查数据库,分片是否存在
FileBlock fileBlock = blockMapper.getByMd5AndChunk(md5value, chunk);
if (fileBlock == null) {//数据库不存在分片记录或者分片不完整,则重传
resp.put("exist", "0");
return resp.toString();
}
File block = new File(blocksDir + "/" + md5value + "/" + chunk); //前面没通过就不用执行后面的了,性能会好点吧。。
// System.out.println("blocklen:"+block.length()+" "+"chunkSize:"+chunkSize);
if (block.length() != chunkSize) {//分片是否完整
resp.put("exist", "0");
return resp.toString();
}
resp.put("exist", "1");
return resp.toString();
}
@PostMapping("upload")//接收上传的分片
@ResponseBody
public String fileUpload(String md5value, String chunks, int chunk, String id, String name,
String type, String lastModifiedDate, int chunkSize, MultipartFile file) {
//-----------------------保存分片--------------------
File blockDir = new File(blocksDir + "/" + md5value);
blockDir.mkdirs();
File block = new File(blockDir, String.valueOf(chunk));
try {
//搞了N多天,WebUploader tmd要重复发送验证通过的分片,导致多插入数据,导致合并文件变大,坑 (其实还是之前我没把MD5和chunk弄成联合主键,不然它重复插入不了的,自作孽啊)
boolean exist = blockMapper.getByMd5AndChunk(md5value, chunk) == null ? false : true;
if (!exist) {
FileBlock fileBlock = new FileBlock(md5value, new Date(), block.getPath(), chunk, name, chunkSize);
blockMapper.addFileBlock(fileBlock);//增加分片记录
}
file.transferTo(block);
} ca