文件分片实体类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;
}
}