欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現(xiàn)

 更新時間:2022年07月08日 08:54:01   作者:小小猴沖刺  
本文主要介紹了springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

對于大文件的處理,無論是用戶端還是服務端,如果一次性進行讀取發(fā)送、接收都是不可取,很容易導致內(nèi)存問題。所以對于大文件上傳,采用切塊分段上傳,從上傳的效率來看,利用多線程并發(fā)上傳能夠達到最大效率。

 本文是基于 springboot + vue 實現(xiàn)的文件上傳,本文主要介紹服務端實現(xiàn)文件上傳的步驟及代碼實現(xiàn),vue的實現(xiàn)步驟及實現(xiàn)請移步本人的另一篇文章

vue 大文件分片上傳 - 斷點續(xù)傳、并發(fā)上傳

上傳分步:

本人分析上傳總共分為:

  • 檢查文件是否已上傳,如已上傳可實現(xiàn)秒傳
  • 創(chuàng)建臨時文件(._tmp)和上傳的配置文件(.conf)
  • 使用RandomAccessFile獲取臨時文件
  • 調(diào)用RandomAccessFile的getChannel()方法,打開文件通道 FileChannel
  • 獲取當前是第幾個分塊,計算文件的最后偏移量
  • 獲取當前文件分塊的字節(jié)數(shù)組,用于獲取文件字節(jié)長度
  • 使用文件通道FileChannel類的 map()方法創(chuàng)建直接字節(jié)緩沖器 MappedByteBuffer
  • 將分塊的字節(jié)數(shù)組放入到當前位置的緩沖區(qū)內(nèi) mappedByteBuffer.put(byte[] b)
  • 釋放緩沖區(qū)
  • 檢查文件是否全部完成上傳,如上傳完成將臨時文件名為正式文件名

直接上代碼

public class FlieChunkUtils {
?
? ? /**
? ? ?* 分塊上傳
? ? ?* 第一步:獲取RandomAccessFile,隨機訪問文件類的對象
? ? ?* 第二步:調(diào)用RandomAccessFile的getChannel()方法,打開文件通道 FileChannel
? ? ?* 第三步:獲取當前是第幾個分塊,計算文件的最后偏移量
? ? ?* 第四步:獲取當前文件分塊的字節(jié)數(shù)組,用于獲取文件字節(jié)長度
? ? ?* 第五步:使用文件通道FileChannel類的 map()方法創(chuàng)建直接字節(jié)緩沖器 ?MappedByteBuffer
? ? ?* 第六步:將分塊的字節(jié)數(shù)組放入到當前位置的緩沖區(qū)內(nèi) ?mappedByteBuffer.put(byte[] b);
? ? ?* 第七步:釋放緩沖區(qū)
? ? ?* 第八步:檢查文件是否全部完成上傳
? ? ?*
? ? ?* @param param
? ? ?* @return
? ? ?* @throws Exception
? ? ?*/
? ? public static ApiResult uploadByMappedByteBuffer(MultipartFileParam param) throws Exception {
? ? ? ? if (param.getIdentifier() == null || "".equals(param.getIdentifier())) {
? ? ? ? ? ? param.setIdentifier(UUID.randomUUID().toString());
? ? ? ? }
? ? ? ? // 判斷是否上傳
? ? ? ? if (ObjectUtil.isEmpty(param.getFile())) {
? ? ? ? ? ? return checkUploadStatus(param);
? ? ? ? }
? ? ? ? // 文件名稱
? ? ? ? String fileName = getFileName(param);
? ? ? ? // 臨時文件名稱
? ? ? ? String tempFileName = param.getIdentifier() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
? ? ? ? // 獲取文件路徑
? ? ? ? String filePath = getUploadPath(param);
? ? ? ? // 創(chuàng)建文件夾
? ? ? ? FileUploadUtils.getAbsoluteFile(filePath, fileName);
? ? ? ? // 創(chuàng)建臨時文件
? ? ? ? File tempFile = new File(filePath, tempFileName);
? ? ? ? //第一步 獲取RandomAccessFile,隨機訪問文件類的對象
? ? ? ? RandomAccessFile raf = RandomAccessFileUitls.getModelRW(tempFile);
? ? ? ? //第二步 調(diào)用RandomAccessFile的getChannel()方法,打開文件通道 FileChannel
? ? ? ? FileChannel fileChannel = raf.getChannel();
? ? ? ? //第三步 獲取當前是第幾個分塊,計算文件的最后偏移量
? ? ? ? long offset = (param.getChunkNumber() - 1) * param.getChunkSize();
? ? ? ? //第四步 獲取當前文件分塊的字節(jié)數(shù)組,用于獲取文件字節(jié)長度
? ? ? ? byte[] fileData = param.getFile().getBytes();
? ? ? ? //第五步 使用文件通道FileChannel類的 map()方法創(chuàng)建直接字節(jié)緩沖器 ?MappedByteBuffer
? ? ? ? MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
? ? ? ? //第六步 將分塊的字節(jié)數(shù)組放入到當前位置的緩沖區(qū)內(nèi) ?mappedByteBuffer.put(byte[] b)
? ? ? ? mappedByteBuffer.put(fileData);
? ? ? ? //第七步 釋放緩沖區(qū)
? ? ? ? freeMappedByteBuffer(mappedByteBuffer);
? ? ? ? fileChannel.close();
? ? ? ? raf.close();
? ? ? ? //第八步 檢查文件是否全部完成上傳
? ? ? ? ApiResult result = ApiResult.success();
? ? ? ? boolean isComplete = checkUploadStatus(param, fileName, filePath);
? ? ? ? if (isComplete) {
? ? ? ? ? ? // 完成后,臨時文件名為正式文件名
? ? ? ? ? ? renameFile(tempFile, fileName);
? ? ? ? ? ? result.put("endUpload", true);
? ? ? ? }
?
? ? ? ? result.put("filePath", FileUploadUtils.getPathFileName(filePath, fileName));
? ? ? ? result.put("fileName", param.getFile().getOriginalFilename());
? ? ? ? return result;
? ? }
?
? ? /**
? ? ?* 檢查文件是否上傳
? ? ?*
? ? ?* @param param
? ? ?* @return
? ? ?* @throws Exception
? ? ?*/
? ? public static ApiResult checkUploadStatus(MultipartFileParam param) throws Exception {
? ? ? ? String fileName = getFileName(param);
? ? ? ? // 校驗conf文件
? ? ? ? File confFile = checkConfFile(fileName, getUploadPath(param));
? ? ? ? // 獲取完成列表
? ? ? ? byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
? ? ? ? List<String> uploadeds = new ArrayList<>();
? ? ? ? for (int i = 0; i < completeStatusList.length; i++) {
? ? ? ? ? ? if (completeStatusList[i] == Byte.MAX_VALUE) {
? ? ? ? ? ? ? ? uploadeds.add(i + 1 + "");
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? ApiResult<Void> success = ApiResult.success();
? ? ? ? success.put("uploaded", uploadeds);
? ? ? ? success.put("skipUpload", completeStatusList.length > 0 && completeStatusList.length == uploadeds.size());
? ? ? ? // 新文件
? ? ? ? if (ObjectUtil.isEmpty(completeStatusList)) {
? ? ? ? ? ? success.put("chunk", false);
? ? ? ? ? ? return success;
? ? ? ? }
? ? ? ? if (completeStatusList.length < param.getChunkNumber()) {
? ? ? ? ? ? success.put("chunk", false);
? ? ? ? ? ? return success;
? ? ? ? }
? ? ? ? byte b = completeStatusList[param.getChunkNumber() - 1];
? ? ? ? if (b != Byte.MAX_VALUE) {
? ? ? ? ? ? success.put("chunk", false);
? ? ? ? ? ? return success;
? ? ? ? }
? ? ? ? success.put("filePath", FileUploadUtils.getPathFileName(getUploadPath(param), fileName));
? ? ? ? success.put("chunk", true);
? ? ? ? return success;
? ? }
?
? ? /**
? ? ?* 文件下載
? ? ?*
? ? ?* @param filePath 文件地址
? ? ?* @param request
? ? ?* @param response
? ? ?* @throws IOException
? ? ?*/
? ? public static void download(String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
? ? ? ? // 初始化 response
? ? ? ? response.reset();
? ? ? ? // 獲取文件
? ? ? ? File file = new File(getDownloadPath(filePath));
? ? ? ? long fileLength = file.length();
? ? ? ? //獲取從那個字節(jié)開始讀取文件
? ? ? ? String rangeString = request.getHeader("Range");
? ? ? ? long range = 0;
? ? ? ? if (StrUtil.isNotBlank(rangeString)) {
? ? ? ? ? ? range = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
? ? ? ? }
? ? ? ? if (range >= fileLength) {
? ? ? ? ? ? throw new CustomException("文件讀取長度過長");
? ? ? ? }
? ? ? ? long byteLength = 1024 * 1024;
? ? ? ? if (range + byteLength > fileLength) {
? ? ? ? ? ? byteLength = fileLength;
? ? ? ? }
? ? ? ? // 隨機讀文件RandomAccessFile
? ? ? ? RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
? ? ? ? try {
? ? ? ? ? ? // 移動訪問指針到指定位置
? ? ? ? ? ? randomAccessFile.seek(range);
? ? ? ? ? ? // 每次請求只返回1MB的視頻流
? ? ? ? ? ? byte[] bytes = new byte[(int) byteLength];
? ? ? ? ? ? int len = randomAccessFile.read(bytes);
? ? ? ? ? ? //獲取響應的輸出流
? ? ? ? ? ? OutputStream outputStream = response.getOutputStream();
? ? ? ? ? ? //返回碼需要為206,代表只處理了部分請求,響應了部分數(shù)據(jù)
? ? ? ? ? ? response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
? ? ? ? ? ? //設置此次相應返回的數(shù)據(jù)長度
? ? ? ? ? ? response.setContentLength(len);
? ? ? ? ? ? //設置此次相應返回的數(shù)據(jù)范圍
? ? ? ? ? ? response.setHeader("Content-Range", "bytes " + range + "-" + len + "/" + fileLength);
? ? ? ? ? ? // 將這1MB的視頻流響應給客戶端
? ? ? ? ? ? outputStream.write(bytes, 0, len);
? ? ? ? ? ? outputStream.close();
? ? ? ? ? ? //randomAccessFile.close();
? ? ? ? ? ? System.out.println("返回數(shù)據(jù)區(qū)間:【" + range + "-" + (range + len) + "】");
? ? ? ? } finally {
? ? ? ? ? ? randomAccessFile.close();
? ? ? ? }
? ? }
?
? ? /**
? ? ?* 文件重命名
? ? ?*
? ? ?* @param toBeRenamed ? 將要修改名字的文件
? ? ?* @param toFileNewName 新的名字
? ? ?* @return
? ? ?*/
? ? private static boolean renameFile(File toBeRenamed, String toFileNewName) {
? ? ? ? //檢查要重命名的文件是否存在,是否是文件
? ? ? ? if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? String p = toBeRenamed.getParent();
? ? ? ? File newFile = new File(p + File.separatorChar + toFileNewName);
? ? ? ? //修改文件名
? ? ? ? return toBeRenamed.renameTo(newFile);
? ? }
?
? ? /**
? ? ?* 檢查文件上傳進度
? ? ?*
? ? ?* @return
? ? ?*/
? ? private static boolean checkUploadStatus(MultipartFileParam param, String fileName, String filePath) throws Exception {
? ? ? ? // 校驗conf文件
? ? ? ? File confFile = checkConfFile(fileName, filePath);
? ? ? ? // 讀取conf
? ? ? ? RandomAccessFile confAccessFile = new RandomAccessFile(confFile, "rw");
? ? ? ? //設置文件長度
? ? ? ? if (confAccessFile.length() != param.getTotalChunks()) {
? ? ? ? ? ? confAccessFile.setLength(param.getTotalChunks());
? ? ? ? }
? ? ? ? //設置起始偏移量
? ? ? ? confAccessFile.seek(param.getChunkNumber() - 1);
? ? ? ? //將指定的一個字節(jié)寫入文件中 127,
? ? ? ? confAccessFile.write(Byte.MAX_VALUE);
? ? ? ? byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
? ? ? ? byte isComplete = Byte.MAX_VALUE;
? ? ? ? //這一段邏輯有點復雜,看的時候思考了好久,創(chuàng)建conf文件文件長度為總分片數(shù),每上傳一個分塊即向conf文件中寫入一個127,那么沒上傳的位置就是默認的0,已上傳的就是Byte.MAX_VALUE 127
? ? ? ? for (int i = 0; i < completeStatusList.length && isComplete == Byte.MAX_VALUE; i++) {
? ? ? ? ? ? // 按位與運算,將&兩邊的數(shù)轉(zhuǎn)為二進制進行比較,有一個為0結(jié)果為0,全為1結(jié)果為1 ?eg.3&5 ?即 0000 0011 & 0000 0101 = 0000 0001 ? 因此,3&5的值得1。
? ? ? ? ? ? isComplete = (byte) (isComplete & completeStatusList[i]);
? ? ? ? }
? ? ? ? if (isComplete == Byte.MAX_VALUE) {
? ? ? ? ? ? //如果全部文件上傳完成,刪除conf文件
? ? ? ? ? ? // FileUtils.deleteFile(confFile.getPath());
? ? ? ? ? ? return true;
? ? ? ? }
? ? ? ? return false;
? ? }
?
?
? ? /**
? ? ?* 在MappedByteBuffer釋放后再對它進行讀操作的話就會引發(fā)jvm crash,在并發(fā)情況下很容易發(fā)生
? ? ?* 正在釋放時另一個線程正開始讀取,于是crash就發(fā)生了。所以為了系統(tǒng)穩(wěn)定性釋放前一般需要檢 查是否還有線程在讀或?qū)?
? ? ?*
? ? ?* @param mappedByteBuffer
? ? ?*/
? ? private static void freeMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
? ? ? ? try {
? ? ? ? ? ? if (mappedByteBuffer == null) {
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? ? ? mappedByteBuffer.force();
? ? ? ? ? ? AccessController.doPrivileged(new PrivilegedAction<Object>() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public Object run() {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
? ? ? ? ? ? ? ? ? ? ? ? //可以訪問private的權(quán)限
? ? ? ? ? ? ? ? ? ? ? ? getCleanerMethod.setAccessible(true);
? ? ? ? ? ? ? ? ? ? ? ? //在具有指定參數(shù)的 方法對象上調(diào)用此 方法對象表示的底層方法
? ? ? ? ? ? ? ? ? ? ? ? sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Object[0]);
? ? ? ? ? ? ? ? ? ? ? ? cleaner.clean();
? ? ? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? log.error("clean MappedByteBuffer error!!!", e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? return null;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
?
? ? private static String getFileName(MultipartFileParam param) {
? ? ? ? String extension;
? ? ? ? if (ObjectUtil.isNotEmpty(param.getFile())) {
? ? ? ? ? ? // return param.getFile().getOriginalFilename();
? ? ? ? ? ? String filename = param.getFile().getOriginalFilename();
? ? ? ? ? ? extension = filename.substring(filename.lastIndexOf("."));
? ? ? ? ? ? //return ?FileUploadUtils.extractFilename(param.getFile());
? ? ? ? } else {
? ? ? ? ? ? extension = param.getFilename().substring(param.getFilename().lastIndexOf("."));
? ? ? ? ? ? //return DateUtils.datePath() + "/" + IdUtil.fastUUID() + extension;
? ? ? ? }
? ? ? ? return param.getIdentifier() + extension;
? ? }
?
? ? private static String getUploadPath(MultipartFileParam param) {
? ? ? ? return FileUploadUtils.getDefaultBaseDir() + "/" + param.getObjectType();
? ? }
?
? ? private static String getDownloadPath(String filePath) {
? ? ? ? // 本地資源路徑
? ? ? ? String localPath = WhspConfig.getProfile();
? ? ? ? // 數(shù)據(jù)庫資源地址
? ? ? ? String loadPath = localPath + StrUtil.subAfter(filePath, Constants.RESOURCE_PREFIX, false);
? ? ? ? return loadPath;
? ? }
?
? ? private static File checkConfFile(String fileName, String filePath) throws Exception {
? ? ? ? File confFile = FileUploadUtils.getAbsoluteFile(filePath, fileName + ".conf");
? ? ? ? if (!confFile.exists()) {
? ? ? ? ? ? confFile.createNewFile();
? ? ? ? }
? ? ? ? return confFile;
? ? }
}

到此這篇關于springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現(xiàn)的文章就介紹到這了,更多相關springboot大文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 一篇文章帶你學會Spring?MVC表單標簽

    一篇文章帶你學會Spring?MVC表單標簽

    Spring MVC表單標簽是網(wǎng)頁的可配置和可重復使用的構(gòu)建塊,下面這篇文章主要給大家介紹了如何通過一篇文章學會Spring?MVC表單標簽的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-03-03
  • Java中的動態(tài)代理和靜態(tài)代理詳細解析

    Java中的動態(tài)代理和靜態(tài)代理詳細解析

    這篇文章主要介紹了Java中的動態(tài)代理和靜態(tài)代理詳細解析,Java中的代理可以幫助被代理者完成一些前期的準備工作和后期的善后工作,但是核心的業(yè)務邏輯仍然是由被代理者完成,需要的朋友可以參考下
    2023-11-11
  • 在mybatis中去除多余的前綴或者后綴操作

    在mybatis中去除多余的前綴或者后綴操作

    這篇文章主要介紹了在mybatis中去除多余的前綴或者后綴操作。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • BufferedInputStream(緩沖輸入流)詳解_動力節(jié)點Java學院整理

    BufferedInputStream(緩沖輸入流)詳解_動力節(jié)點Java學院整理

    這篇文章主要為大家詳細介紹了BufferedInputStream緩沖輸入流的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Springboot 掃描mapper接口的2種操作

    Springboot 掃描mapper接口的2種操作

    這篇文章主要介紹了Springboot 掃描mapper接口的2種操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Java并發(fā)編程中的Callable、Future和FutureTask詳解

    Java并發(fā)編程中的Callable、Future和FutureTask詳解

    這篇文章主要介紹了Java并發(fā)編程中的Callable、Future和FutureTask詳解,創(chuàng)建線程的2種方式,一種是直接繼承Thread,另外一種就是實現(xiàn)Runnable接口,這2種方式都有一個缺陷就是:在執(zhí)行完任務之后無法獲取執(zhí)行結(jié)果,需要的朋友可以參考下
    2023-07-07
  • Java Volatile關鍵字實現(xiàn)原理過程解析

    Java Volatile關鍵字實現(xiàn)原理過程解析

    這篇文章主要介紹了Java Volatile關鍵字實現(xiàn)原理過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • java中toString()、String.valueOf()、(String)?強轉(zhuǎn)的區(qū)別

    java中toString()、String.valueOf()、(String)?強轉(zhuǎn)的區(qū)別

    在實際開發(fā)中,少不了使用這三種方法對某一個類型的數(shù)據(jù)進行轉(zhuǎn)?String?的操作,本文就來介紹了java中toString()、String.valueOf()、(String)?強轉(zhuǎn)的區(qū)別,感興趣的可以了解一下
    2024-06-06
  • Java中List遍歷刪除元素remove()的方法

    Java中List遍歷刪除元素remove()的方法

    這篇文章主要介紹了Java中List遍歷刪除元素remove()的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • Java開發(fā)環(huán)境配置及Vscode搭建過程

    Java開發(fā)環(huán)境配置及Vscode搭建過程

    今天通過圖文并茂的形式給大家介紹Java開發(fā)環(huán)境配置及Vscode搭建過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-07-07

最新評論