java:分塊上傳 和 合並分塊


我們上傳文件時,當文件比較大的時候,我們往往采用前端將大文件分割,分塊多次上傳給后端,全部上傳成功再合並分塊的方式上傳。(這里僅介紹后端操作)

import com.sundear.model.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

/**
* 文件處理工具類
*
* @author luc
* @date 2020/8/515:52
*/
@Slf4j
public class FileUtils {

/**
* 校驗文件內容不為空
*
* @param file 文件對象
*/
public static void checkContent(MultipartFile file) {
//校驗文件的內容
if (file.isEmpty()) {
throw new ServiceException(-1, "文件內容為空");
}
}

/**
* 判斷文件是否在磁盤存在
*
* @param filePath 文件路徑
* @return 存在=true;不存在=false
*/
public static boolean isExist(String filePath) {
return new File(filePath).exists();
}

/**
* 獲取時間文件夾
*
* @param dateTime 時間
* @param pattern 格式
* @return 文件夾
*/
public static String dateTime2String(LocalDateTime dateTime, Object... pattern) {
if (dateTime == null) {
return "";
}
DateTimeFormatter df;
if (pattern != null && pattern.length > 0) {
df = DateTimeFormatter.ofPattern(pattern[0].toString());
} else {
df = DateTimeFormatter.ofPattern("yyyy/MM/dd/HH");
}
return dateTime.format(df);
}

/**
* 創建文件目錄
*
* @param dirPath 文件地址
*/
@SuppressWarnings("all")
public static void createDir(String dirPath) {
//目錄不存在,創建 分塊文件目錄
File dirFile = new File(dirPath);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
}

/**
* 上傳文件寫入磁盤
*
* @param file 文件大小
* @param realPath 真是地址
* @return 是否寫入成功
*/
@SuppressWarnings("all")
public static Boolean writeFile(MultipartFile file, String realPath) {
//在路徑下創建文件
File dest = new File(realPath);
//將上傳的文件保存
try {
if (!dest.exists()) {
//文件不存在,則創建
dest.createNewFile();
}
file.transferTo(dest);
return Boolean.TRUE;
} catch (IOException e) {
log.error(e.getMessage());
return Boolean.FALSE;
}
}

/**
* 刪除文件夾及其目錄下所有文件
*
* @param folderPath 文件夾地址
*/
@SuppressWarnings("all")
public static void delFolder(String folderPath) {
try {
//刪除完里面所有內容
delAllFile(folderPath);
File myFilePath = new File(folderPath);
//刪除空文件夾
myFilePath.delete();
} catch (Exception e) {
log.error(e.getMessage());
}
}

/**
* 刪除指定文件夾下的所有文件
*
* @param path 路徑
* @return true/false
*/
@SuppressWarnings("all")
public static void delAllFile(String path) {
File file = new File(path);
//文件夾不存在返回false\不是文件夾返回false
if (!file.exists() || !file.isDirectory()) {
return;
}
String[] tempList = file.list();
File temp = null;
for (int i = 0; i < Objects.requireNonNull(tempList).length; i++) {
if (path.endsWith(File.separator)) {
temp = new File(path + tempList[i]);
} else {
temp = new File(path + File.separator + tempList[i]);
}
if (temp.isFile()) {
temp.delete();
}
if (temp.isDirectory()) {
//先刪除文件夾里面的文件
delAllFile(path + "/" + tempList[i]);
//再刪除空文件夾
delFolder(path + "/" + tempList[i]);
}
}
}

/**
* 獲取文件擴展名
*
* @param file 文件
* @return 后綴
*/
public static String getFileSuffix(MultipartFile file) {
String fileName = file.getOriginalFilename();
if ((fileName != null) && (fileName.length() > 0)) {
int dot = fileName.lastIndexOf('.');
if ((dot > -1) && (dot < (fileName.length() - 1))) {
return fileName.substring(dot);
}
}
assert fileName != null;
return fileName.toLowerCase();
}


/**
* 刪除文件夾fromDir下的 howDays天前的文件
*
* @param fromDir 文件夾
* @param howDays 天數
* @return 數目
*/
@SuppressWarnings("all")
public static Integer clearFileCache(String fromDir, int howDays) {
File srcDir = new File(fromDir);
if (!srcDir.exists()) {
return 0;
}
File[] files = srcDir.listFiles();
if (files == null || files.length <= 0) {
return 0;
}
// 刪除文件總數
int delTotal = 0;
Date today = new Date();
for (File file : files) {
if (file.isFile()) {
try {
long time = file.lastModified();
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
Date lastModified = cal.getTime();
//(int)(today.getTime() - lastModified.getTime())/86400000;
long days = getDistDates(today, lastModified);
// 刪除多少天前之前文件
if (days >= howDays) {
file.delete();
delTotal++;
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
return delTotal;
}

/**
* 獲取兩個時間之間的 天數
*
* @param startDate 開始日期
* @param endDate 結束日期
* @return 天數
*/
public static long getDistDates(Date startDate, Date endDate) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
long timeStart = calendar.getTimeInMillis();
calendar.setTime(endDate);
long timeEnd = calendar.getTimeInMillis();
return Math.abs((timeEnd - timeStart)) / (1000 * 60 * 60 * 24);
}
}

開始分片上傳
/**
* 分片上傳
*
* @param file 文件對象
* @param req 唯一標識+類型分組
* @param partNum 分片號
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void uploadPart(MultipartFile file, ValidReq req, Integer partNum) {
String fileType = req.getFileType();
String fileMd5 = req.getFileMd5();
String clientId = req.getClientId();
//校驗文件內容不為空
FileUtils.checkContent(file);
//獲取分片號
partNum = (partNum == null) ? 0 : partNum;
//服務器上傳路徑
String comPath = properties.comPath();
//分塊存儲目錄
String partPath = String.join(FileConstant.SEPARATOR, comPath, fileMd5).replaceAll("//+", "/");
//當前分塊全路徑: 服務器路徑/文件md5簽名/分片文件
String partFilePath = String.join(FileConstant.SEPARATOR, partPath, String.valueOf(partNum));

//校驗分片是否上傳
boolean isUpload = FileUtils.isExist(partFilePath);
if (!isUpload) {
//分片目錄不存在,創建 分塊文件目錄
FileUtils.createDir(partPath);
//分片文件寫入磁盤
Boolean isSuccess = FileUtils.writeFile(file, partFilePath);
if (partNum == 0) {
if (isSuccess) {
//上傳第一片分塊時,寫入數據庫
String suffix = FileUtils.getFileSuffix(file);
String fileId = IdWorker.getIdWorker().nextIdStr();
UploadFile uploadFile = new UploadFile();
uploadFile.setFileId(fileId);
uploadFile.setFileMd5(fileMd5);
uploadFile.setFileName(file.getOriginalFilename());
uploadFile.setFileType(fileType);
Byte status = FileStatusEnum.UPLOADING.getCode();
uploadFile.setFileStatus(status);
uploadFile.setClientId(clientId);
uploadFile.setFileSuffix(suffix);
uploadFileService.insert(uploadFile);
log.info("上傳第一個分片文件,生成文件上傳記錄,文件id: " + fileId);
} else {
log.info("上傳第一個分片文件失敗");
}
} else {
log.info("上傳第" + partNum + "個分片成功");
}
}
}
/**
* 服務器文件地址
*/
public String comPath(){
return String.join(FileConstant.SEPARATOR, this.uploadPath, this.filePath).replaceAll("//+", "/");
}

合並分片
/**
* 分片合並完成上傳
*
* @param req 唯一標識+類型分組
* @param partTotal 分片數
* @return 文件記錄
*/
@Override
@Transactional(rollbackFor = Exception.class)
public UploadFile uploadFinish(ValidReq req, Integer partTotal) {
String fileType = req.getFileType();
String fileMd5 = req.getFileMd5();
//獲取文件上傳記錄數據
UploadFile uploadFile = uploadFileService.getByFileMd5(req);

if (uploadFile == null || StringUtils.isBlank(uploadFile.getFileId())) {
throw new ServiceException(-1, "分片未正確上傳");
}
if (partTotal == null || partTotal == 0) {
throw new ServiceException(-1, "分片總數不能為空");
}
//上傳路徑
String comPath = properties.comPath();
//相對路徑
String relativePath = String.join(FileConstant.SEPARATOR, fileType, FileUtils.dateTime2String(LocalDateTime.now())).replaceAll("//+", "/");
//xxx/files/2020/08/11/13
String dirPath = String.join(FileConstant.SEPARATOR, comPath, relativePath).replaceAll("//+", "/");
//最終文件路徑,不存在的話生成文件目錄
FileUtils.createDir(dirPath);
//文件真實路徑
String realPath = String.join(FileConstant.SEPARATOR, dirPath, uploadFile.getFileId() + uploadFile.getFileSuffix()).replaceAll("//+", "/");

//分塊存儲目錄
String partPath = String.join(FileConstant.SEPARATOR, comPath, fileMd5).replaceAll("//+", "/");

///合成后的文件流
try (FileOutputStream fileOutputStream = new FileOutputStream(realPath)) {
byte[] buf = new byte[1024];
for (long i = 0; i < partTotal; i++) {
//當前分塊全路徑: 服務器路徑/文件md5簽名/分片文件
String partFilePath = String.join(FileConstant.SEPARATOR, partPath, String.valueOf(i));
//獲取分塊文件
File file = new File(partFilePath);
//獲取文件流
InputStream inputStream = new FileInputStream(file);
int len;
while ((len = inputStream.read(buf)) != -1) {
fileOutputStream.write(buf, 0, len);
}
inputStream.close();
}
//刪除md5目錄,及臨時文件
FileUtils.delFolder(partPath);
//文件大小設置
File finalFile = new File(realPath);
String size = String.valueOf(finalFile.length());

uploadFile.setFileSize(size);
uploadFile.setFilePath(relativePath);
uploadFile.setFileStatus(FileStatusEnum.PASS.getCode());
uploadFileService.update(uploadFile);
} catch (Exception e) {
log.error(e.getMessage());
throw new ServiceException(-1, "文件合並異常");
}
return uploadFile;
}




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM