文件实体类

@Data
public class UploadFile {
private String fileName;
private String fileType;
private long fileSize;
private String timeStampName;
private int success;
private String message;
private String url;
private Integer storageType;
//切片上传相关参数
private String taskId;
private int chunkNumber;
private long chunkSize;
private int totalChunks;
private String identifier;
private long totalSize;
private long currentChunkSize;


}

文件上传工具父类:

@Slf4j
public abstract class Uploader {


public static final String ROOT_PATH = "upload";
public static final String FILE_SEPARATOR = "/";
// 文件大小限制,单位KB
public final int maxSize = 10000000;


public abstract List<UploadFile> upload(HttpServletRequest request, UploadFile uploadFile);
public abstract void cancelUpload(UploadFile uploadFile);

/**
* 获取本地文件保存路径
*
* @return
*/
protected String getLocalFileSavePath() {

String path = ROOT_PATH;
SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd");
path = FILE_SEPARATOR + path + FILE_SEPARATOR + formater.format(new Date());

String staticPath = PathUtil.getStaticPath();

File dir = new File(staticPath + path);
//LOG.error(PathUtil.getStaticPath() + path);
if (!dir.exists()) {
try {
boolean isSuccessMakeDir = dir.mkdirs();
if (!isSuccessMakeDir) {
log.error("目录创建失败:" + PathUtil.getStaticPath() + path);
}
} catch (Exception e) {
log.error("目录创建失败" + PathUtil.getStaticPath() + path);
return "";
}
}
return path;
}

/**
* 依据原始文件名生成新文件名
*
* @return
*/
protected String getTimeStampName() {
try {
SecureRandom number = SecureRandom.getInstance("SHA1PRNG");
return "" + number.nextInt(10000)
+ System.currentTimeMillis();
} catch (NoSuchAlgorithmException e) {
log.error("生成安全随机数失败");
}
return ""
+ System.currentTimeMillis();

}

public synchronized boolean checkUploadStatus(UploadFile param, File confFile) throws IOException {
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;
}

protected String getFileName(String fileName){
if (!fileName.contains(".")) {
return fileName;
}
return fileName.substring(0, fileName.lastIndexOf("."));
}
}

阿里云文件上传子类:

package com.qiwenshare.ufo.operation.upload.product;


import com.alibaba.fastjson.JSON;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.qiwenshare.common.util.FileUtil;
import com.qiwenshare.ufo.autoconfiguration.UFOAutoConfiguration;
import com.qiwenshare.ufo.autoconfiguration.UFOProperties;
import com.qiwenshare.ufo.exception.UploadException;
import com.qiwenshare.ufo.operation.upload.Uploader;
import com.qiwenshare.ufo.operation.upload.domain.UploadFile;
import com.qiwenshare.ufo.util.PathUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.*;

@Slf4j
@Component
public class AliyunOSSUploader extends Uploader {

// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
public static Map<String, List<PartETag>> partETagsMap = new HashMap<String, List<PartETag>>();
public static Map<String, UploadFileInfo> uploadPartRequestMap = new HashMap<>();

public static Map<String, OSS> ossMap = new HashMap<>();

@Override
public List<UploadFile> upload(HttpServletRequest httpServletRequest, UploadFile uploadFile) {
log.info("开始上传upload");

List<UploadFile> saveUploadFileList = new ArrayList<>();
StandardMultipartHttpServletRequest request = (StandardMultipartHttpServletRequest) httpServletRequest;

boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
throw new UploadException("未包含文件上传域");
}

Iterator<String> iter = request.getFileNames();
while (iter.hasNext()) {

saveUploadFileList = doUpload(request, iter, uploadFile);
}


log.info("结束上传");
return saveUploadFileList;
}

private List<UploadFile> doUpload(StandardMultipartHttpServletRequest standardMultipartHttpServletRequest, Iterator<String> iter, UploadFile uploadFile) {
String savePath = getLocalFileSavePath();
OSS ossClient = getClient(uploadFile);

List<UploadFile> saveUploadFileList = new ArrayList<>();

try {
MultipartFile multipartfile = standardMultipartHttpServletRequest.getFile(iter.next());

String timeStampName = getTimeStampName();
String originalName = multipartfile.getOriginalFilename();
String fileName = getFileName(originalName);
String fileType = FileUtil.getFileExtendName(originalName);
uploadFile.setFileName(fileName);
uploadFile.setFileType(fileType);
uploadFile.setTimeStampName(timeStampName);

String ossFilePath = savePath + FILE_SEPARATOR + timeStampName + FILE_SEPARATOR + fileName + "." + fileType;
String confFilePath = savePath + FILE_SEPARATOR + uploadFile.getIdentifier() + "." + "conf";
File confFile = new File(PathUtil.getStaticPath() + FILE_SEPARATOR + confFilePath);

synchronized (AliyunOSSUploader.class) {
if (uploadPartRequestMap.get(uploadFile.getIdentifier()) == null) {
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(UFOAutoConfiguration.aliyunConfig.getOss().getBucketName(), ossFilePath.substring(1));
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
String uploadId = upresult.getUploadId();

UploadFileInfo uploadPartRequest = new UploadFileInfo();
uploadPartRequest.setBucketName(UFOAutoConfiguration.aliyunConfig.getOss().getBucketName());
uploadPartRequest.setKey(ossFilePath.substring(1));
uploadPartRequest.setUploadId(uploadId);
uploadPartRequestMap.put(uploadFile.getIdentifier(), uploadPartRequest);
}

}

UploadFileInfo uploadFileInfo = uploadPartRequestMap.get(uploadFile.getIdentifier());
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(uploadFileInfo.getBucketName());
uploadPartRequest.setKey(uploadFileInfo.getKey());
uploadPartRequest.setUploadId(uploadFileInfo.getUploadId());
uploadPartRequest.setInputStream(multipartfile.getInputStream());
uploadPartRequest.setPartSize(uploadFile.getCurrentChunkSize());
uploadPartRequest.setPartNumber(uploadFile.getChunkNumber());
log.info(JSON.toJSONString(uploadPartRequest));

UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
synchronized (AliyunOSSUploader.class) {
log.info("上传结果:" + JSON.toJSONString(uploadPartResult));
if (partETagsMap.get(uploadFile.getIdentifier()) == null) {
List<PartETag> partETags = new ArrayList<PartETag>();
partETags.add(uploadPartResult.getPartETag());
partETagsMap.put(uploadFile.getIdentifier(), partETags);
} else {
partETagsMap.get(uploadFile.getIdentifier()).add(uploadPartResult.getPartETag());
}
}

boolean isComplete = checkUploadStatus(uploadFile, confFile);
if (isComplete) {
log.info("分片上传完成");
completeMultipartUpload(uploadFile);

uploadFile.setUrl("/" + uploadPartRequestMap.get(uploadFile.getIdentifier()).getKey());
uploadFile.setSuccess(1);
uploadFile.setMessage("上传成功");
partETagsMap.remove(uploadFile.getIdentifier());
uploadPartRequestMap.remove(uploadFile.getIdentifier());
ossMap.remove(uploadFile.getIdentifier());
} else {
uploadFile.setSuccess(0);
uploadFile.setMessage("未完成");
}

} catch (Exception e) {
log.error("上传出错:" + e);
throw new UploadException(e);
}

uploadFile.setStorageType(1);

uploadFile.setFileSize(uploadFile.getTotalSize());
saveUploadFileList.add(uploadFile);
return saveUploadFileList;
}

/**
* 将文件分块进行升序排序并执行文件上传。
*/
protected void completeMultipartUpload(UploadFile uploadFile) {

List<PartETag> partETags = partETagsMap.get(uploadFile.getIdentifier());
Collections.sort(partETags, Comparator.comparingInt(PartETag::getPartNumber));
UploadFileInfo uploadFileInfo = uploadPartRequestMap.get(uploadFile.getIdentifier());
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(UFOAutoConfiguration.aliyunConfig.getOss().getBucketName(),
uploadFileInfo.getKey(),
uploadFileInfo.getUploadId(),
partETags);
log.info("----:" + JSON.toJSONString(partETags));
// 完成上传。
CompleteMultipartUploadResult completeMultipartUploadResult = getClient(uploadFile).completeMultipartUpload(completeMultipartUploadRequest);
log.info("----:" + JSON.toJSONString(completeMultipartUploadRequest));
getClient(uploadFile).shutdown();

//
}

private void listFile(UploadFile uploadFile) {
// 列举已上传的分片,其中uploadId来自于InitiateMultipartUpload返回的结果。
ListPartsRequest listPartsRequest = new ListPartsRequest(UFOAutoConfiguration.aliyunConfig.getOss().getBucketName(), uploadPartRequestMap.get(uploadFile.getIdentifier()).getKey(), uploadPartRequestMap.get(uploadFile.getIdentifier()).getUploadId());
// 设置uploadId。
//listPartsRequest.setUploadId(uploadId);
// 设置分页时每一页中分片数量为100个。默认列举1000个分片。
listPartsRequest.setMaxParts(100);
// 指定List的起始位置。只有分片号大于此参数值的分片会被列举。
// listPartsRequest.setPartNumberMarker(1);
PartListing partListing = getClient(uploadFile).listParts(listPartsRequest);

for (PartSummary part : partListing.getParts()) {
log.info("分片号:"+part.getPartNumber() + ", 分片数据大小: "+
part.getSize() + ",分片的ETag:"+part.getETag()
+ ", 分片最后修改时间:"+ part.getLastModified());
// 获取分片号。
part.getPartNumber();
// 获取分片数据大小。
part.getSize();
// 获取分片的ETag。
part.getETag();
// 获取分片的最后修改时间。
part.getLastModified();
}

}

/**
* 取消上传
*/
@Override
public void cancelUpload(UploadFile uploadFile) {
AbortMultipartUploadRequest abortMultipartUploadRequest =
new AbortMultipartUploadRequest(UFOAutoConfiguration.aliyunConfig.getOss().getBucketName(), uploadPartRequestMap.get(uploadFile.getIdentifier()).getKey(), uploadPartRequestMap.get(uploadFile.getIdentifier()).getUploadId());
getClient(uploadFile).abortMultipartUpload(abortMultipartUploadRequest);
}

private synchronized OSS getClient(UploadFile uploadFile) {
OSS ossClient = null;
if (ossMap.get(uploadFile.getIdentifier()) == null) {
ossClient = new OSSClientBuilder().build(UFOAutoConfiguration.aliyunConfig.getOss().getEndpoint(), UFOAutoConfiguration.aliyunConfig.getOss().getAccessKeyId(), UFOAutoConfiguration.aliyunConfig.getOss().getAccessKeySecret());
ossMap.put(uploadFile.getIdentifier(), ossClient);
} else {
ossClient = ossMap.get(uploadFile.getIdentifier());
}
return ossClient;
}

@Data
public class UploadFileInfo {
private String bucketName;
private String key;
private String uploadId;
}

}

路径工具类

import com.qiwenshare.common.constant.FileConstant;
import com.qiwenshare.ufo.autoconfiguration.UFOAutoConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

public class PathUtil {

/**
* 获取项目所在的根目录路径 resources路径
* @return
*/
public static String getProjectRootPath() {
String absolutePath = null;
try {
String url = ResourceUtils.getURL("classpath:").getPath();
absolutePath = urlDecode(new File(url).getAbsolutePath()) + File.separator;
} catch (FileNotFoundException e) {
e.printStackTrace();
}

return absolutePath;
}

/**
* 路径解码
* @param url
* @return
*/
public static String urlDecode(String url){
String decodeUrl = null;
try {
decodeUrl = URLDecoder.decode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return decodeUrl;
}

/**
* 得到static路径
*
* @return
*/
public static String getStaticPath() {
String localStoragePath = UFOAutoConfiguration.localStoragePath;//PropertiesUtil.getProperty("qiwen-file.local-storage-path")
if (StringUtils.isNotEmpty(localStoragePath)) {
return localStoragePath;
}else {
String projectRootAbsolutePath = getProjectRootPath();

int index = projectRootAbsolutePath.indexOf("file:");
if (index != -1) {
projectRootAbsolutePath = projectRootAbsolutePath.substring(0, index);
}

return projectRootAbsolutePath + "static" + File.separator;
}


}


public static String getParentPath(String path) {
return path.substring(0, path.lastIndexOf(FileConstant.pathSeparator));
}

}