引入工具 commons-io
commons-io
2.6
commons-lang
commons-lang
2.6
控制类 import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
@RestController
@RequestMapping("/chunk")
public class ChunkController {
@RequestMapping("/chunkUpload")
public StdOut chunkUpload(MultipartFileParam param, HttpServletRequest request, HttpServletResponse response) {
StdOut out = new StdOut();
File file = new File("C:\\chunk_test");//存储路径
ChunkService chunkService = new ChunkService();
String path = file.getAbsolutePath();
response.setContentType("text/html;charset=UTF-8");
try {
//判断前端Form表单格式是否支持文件上传
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
out.setCode(StdOut.PARAMETER_NULL);
out.setMessage("表单格式错误");
return out;
} else {
param.setTaskId(param.getIdentifier());
out.setModel(chunkService.chunkUploadByMappedByteBuffer(param, path));
return out;
}
} catch (NotSameFileExpection e) {
out.setCode(StdOut.FAIL);
out.setMessage("MD5校验失败");
return out;
} catch (Exception e) {
out.setCode(StdOut.FAIL);
out.setMessage("上传失败");
return out;
}
}
}
StdOut类(只是封装的返回类) public class StdOut {
public static final int SUCCESS = 200;
public static final int FAIL = 400;
public static final int PARAMETER_NULL = 500;
public static final int NO_LOGIN = 600;
private int code = 200;
private Object model = null;
private String message = null;
public StdOut() {
this.setCode(200);
this.setModel((Object)null);
}
public StdOut(int code) {
this.setCode(code);
this.setModel((Object)null);
}
public StdOut(List> model) {
this.setCode(200);
this.setModel(model);
}
public StdOut(int code, List> model) {
this.setCode(code);
this.setModel(model);
}
public int getCode() {
return this.code;
}
public void setCode(int code) {
this.code = code;
}
public String toString() {
return JSON.toJSONString(this);
}
public Object getModel() {
return this.model;
}
public void setModel(Object model) {
this.model = model;
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}
MultipartFileParam类(文件信息类) import org.springframework.web.multipart.MultipartFile;
public class MultipartFileParam {
private String taskId;
private int chunkNumber;
private long chunkSize;
private int totalChunks;
private String identifier;
private MultipartFile file;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public int getChunkNumber() {
return chunkNumber;
}
public void setChunkNumber(int chunkNumber) {
this.chunkNumber = chunkNumber;
}
public long getChunkSize() {
return chunkSize;
}
public void setChunkSize(long chunkSize) {
this.chunkSize = chunkSize;
}
public int getTotalChunks() {
return totalChunks;
}
public void setTotalChunks(int totalChunks) {
this.totalChunks = totalChunks;
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
ChunkService类 import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.UUID;
public class ChunkService {
public String chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) throws IOException, NotSameFileExpection {
if (param.getTaskId() == null || "".equals(param.getTaskId())) {
param.setTaskId(UUID.randomUUID().toString());
}
String fileName = param.getFile().getOriginalFilename();
String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
File fileDir = new File(filePath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File tempFile = new File(filePath, tempFileName);
//第一步 打开将要写入的文件
RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
//第二步 打开通道
FileChannel fileChannel = raf.getChannel();
//第三步 计算偏移量
long position = (param.getChunkNumber() - 1) * param.getChunkSize();
//第四步 获取分片数据
byte[] fileData = param.getFile().getBytes();
//第五步 写入数据
fileChannel.position(position);
fileChannel.write(ByteBuffer.wrap(fileData));
fileChannel.force(true);
fileChannel.close();
raf.close();
//判断是否完成文件的传输并进行校验与重命名
boolean isComplete = checkUploadStatus(param, fileName, filePath);
if (isComplete) {
FileInputStream fileInputStream = new FileInputStream(tempFile.getPath());
String md5 = DigestUtils.md5Hex(fileInputStream);
fileInputStream.close();
if (StringUtils.isNotBlank(md5) && !md5.equals(param.getIdentifier())) {
throw new NotSameFileExpection();
}
renameFile(tempFile, fileName);
return fileName;
}
return null;
}
public void renameFile(File toBeRenamed, String toFileNewName) {
if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
System.err.println("文件不存在");
return;
}
String p = toBeRenamed.getParent();
File newFile = new File(p + File.separatorChar + toFileNewName);
toBeRenamed.renameTo(newFile);
}
public boolean checkUploadStatus(MultipartFileParam param, String fileName, String filePath) throws IOException {
File confFile = new File(filePath, fileName + ".conf");
RandomAccessFile confAccessFile = new RandomAccessFile(confFile, "rw");
//设置文件长度
confAccessFile.setLength(param.getTotalChunks());
//设置起始偏移量
confAccessFile.seek(param.getChunkNumber() - 1);
//将指定的一个字节写入文件中 127,
confAccessFile.write(Byte.MAX_VALUE);
byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
confAccessFile.close();//不关闭会造成无法占用
//创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是127
for (int i = 0; i < completeStatusList.length; i++) {
if (completeStatusList[i] != Byte.MAX_VALUE) {
return false;
}
}
confFile.delete();
return true;
}
}