Vue大文件上傳 vue-simple-loader分片上傳到AWS S3


前言

前端使用vue2.0,上傳組件為vue-simple-loader分片上傳文件。

后台使用java8接收,接收文件后,保存在項目路徑下,分片上傳到AWS S3存儲桶。

流程:大文件通過vue-simple-loader分片上傳到java后台,保存到本地項目下。再將本地項目下的文件分片上傳到s3,上傳成功后,刪除本地文件。

現存待研究問題:

1. vue-simple-loader上傳一個分片,s3接收一個分片的形式,但未實現(暫未找到s3接收此種形式的方法)。

2.通過js直連s3進行上傳,js版本2方式:https://www.cnblogs.com/aiyowei/p/15769695.html。js版本3方式暫未實現

參考的大佬筆記:

vue-simple-loader github鏈接:https://github.com/simple-uploader/vue-uploader/blob/master/README_zh-CN.md

vue-simple-upload options屬性 github鏈接:https://github.com/simple-uploader/Uploader#events

vue-simple-uploader筆記:https://www.cnblogs.com/xiahj/p/vue-simple-uploader.html

后台分片上傳筆記:https://blog.csdn.net/jxysgzs/article/details/107778949

aws s3分片上傳參考文檔:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/mpu-upload-object.html

重要::雖然我很菜,寫的也不夠好,但我不接受任何批評,本文僅供有需要的人參考及自己記錄用。

前端部分

安裝vue-simple-loader 

npm install vue-simple-uploader --save  本文使用0.7.6版本

main.js中

import uploader from 'vue-simple-uploader'
Vue.use(uploader)

將vue-simple-uploader項目下,src文件夾中的common和components文件夾下的文件引入自己的項目

下載地址:https://github.com/simple-uploader/vue-uploader/tree/master/src

我的項目中引入位置,分別放在components/upload 和 utils/upload 文件夾下

   

前端代碼,將大文件分片上傳到本地,在上傳成功的回調onFileSuccess中,將本地文件上傳到S3存儲桶

<template>
    <div class="uploader">
        <!-- autoStart 需要設置成 false -->
        <uploader :options="options" :autoStart="false"
            :fileStatusText="{
                    success: '上傳成功,等待后台處理...',
                    error: '上傳失敗',
                    uploading: '正在上傳',
                    paused: '暫停上傳',
                    waiting: '等待上傳'
            }"
            @file-success="onFileSuccess"  @file-added="fileAdded"  @file-error="onFileError"
        ></uploader>
    </div>
</template>

<script>
    import uploader from '../../components/upload/uploader.vue'
    import {
        localFileToS3
    } from '@/api/file/file.js';
    
    export default {
        components: {
            uploader
        },
        data() {
            return {
                options: {
                    target: '/bigFileToLocal.do', // 目標上傳 URL
                    chunkSize: 5 * 1024 * 1024, // 分塊大小,要和后台合並的大小對應
                    singleFile: true, // 是否單文件
                    maxChunkRetries: 3, //最大自動失敗重試上傳次數
                    testChunks: false, //是否開啟服務器分片校驗, 默認true
                    query: { // 參數
                    },
                    headers: { // 請求頭認證
                        "token": localStorage.getItem('token')
                    },
                }
            }
        },
        methods: {
            //大文件上傳所需
            fileAdded(file) {
                //選擇文件后暫停文件上傳,上傳時手動啟動
                file.pause()
            },
            onFileError(file) {
                console.log('error', file)
            },
            onFileSuccess(rootFile, file, response, chunk) { // 文件上傳到本地成功后的回調
                var res = JSON.parse(response);
                if (res.code == "200") {
                    // 上傳成功,上傳本地文件到s3
                    var fileName = res.obj.fileName;
                    var filePath = res.obj.filePath;
                    let params = {
                        fileName: fileName,
                        filePath: filePath
                    }
                    
                    localFileToS3(params).then(res => { // 底層是axios請求
                        // 將上傳到本地的文件上傳到AWS s3
                        console.log(res);
                    })
                }
            },
        },
    }
</script>

<style>
    .uploader {
        position: relative;
    }
</style>

后台部分 

步驟:

1. 接收vue-simple-loader分片傳過來的參數,保存到本地項目目錄下

2. 取得本地項目目錄下的文件,分片上傳到s3

3. 刪除本地保存的文件

Controller部分

import com.systron.common.controller.BaseController;
import com.systron.common.utils.ResponseApi;
import com.systron.models.sys.Chunk;
import com.systron.service.sys.FileService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Controller
public class UploadController {

    private Logger logger = LoggerFactory.getLogger(UploadController.class);

    @Autowired
    private FileService fileService;

    /**
     * 大文件分片上傳后保存到本地項目目錄
     *
     * @param chunk
     * @param request
     * @param response
     */
    @RequestMapping(value="/bigFileToLocal.do")
    public void bigFileToLocal(@ModelAttribute Chunk chunk, HttpServletRequest request, HttpServletResponse response) {
        ResponseApi<Object> responseApi = new ResponseApi<Object>();

        // 分片上傳
        responseApi = fileService.bigFileToLocal(chunk);
        if (null != responseApi && StringUtils.isNotEmpty(responseApi.getCode())) {
            response.setStatus(Integer.valueOf(responseApi.getCode()));
        } else {
            response.setStatus(201);
        }
        outObjectToJson(response, responseApi);
    }

    /**
     * 本地大文件分片上傳到s3存儲桶
     * @param request
     * @param response
     */
    @RequestMapping(value="/localFileToS3.do")
    public void localFileToS3(HttpServletRequest request, HttpServletResponse response) {
        ResponseApi<Object> responseApi = new ResponseApi<Object>();

        String allFilePath = request.getParameter("filePath"); // 文件路徑
        String fileName = request.getParameter("fileName"); // 文件名稱
        
        responseApi = fileService.localFileToS3(fileName, allFilePath);
        outObjectToJson(response, responseApi);
    }
}

Service部分 

import com.alibaba.fastjson.JSONObject;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;
import com.amazonaws.services.s3.transfer.Upload;
import com.systron.common.utils.ResponseApi;
import com.systron.common.utils.cache.CacheConfigUtil;
import com.systron.dao.sys.FileDao;
import com.systron.models.sys.Chunk;
import com.systron.utils.HelpUtil;
import com.systron.utils.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

@Service
public class FileService {

    private Logger logger = LoggerFactory.getLogger(FileService.class);

    @Resource(name = "fileDao")
    private FileDao fileDao;

    // 存儲桶名稱
    private static String bucketName = CacheConfigUtil.getProperty("bucket.name");

    /**
     * 大文件分片上傳到本地項目下
     * @param chunk 每個塊信息
     * @return
     */
    public ResponseApi<Object> bigFileToLocal(Chunk chunk) {
        ResponseApi<Object> responseApi = new ResponseApi<Object>();
        /**
         * 每一個上傳塊都會包含如下分塊信息:
         * chunkNumber: 當前塊的次序,第一個塊是 1,注意不是從 0 開始的。
         * totalChunks: 文件被分成塊的總數。
         * chunkSize: 分塊大小,根據 totalSize 和這個值你就可以計算出總共的塊數。注意最后一塊的大小可能會比這個要大。
         * currentChunkSize: 當前塊的大小,實際大小。
         * totalSize: 文件總大小。
         * identifier: 這個就是每個文件的唯一標示。
         * filename: 文件名。
         * relativePath: 文件夾上傳的時候文件的相對路徑屬性。
         * 一個分塊可以被上傳多次,當然這肯定不是標准行為,但是在實際上傳過程中是可能發生這種事情的,這種重傳也是本庫的特性之一。
         *
         * 根據響應碼認為成功或失敗的:
         * 200 文件上傳完成
         * 201 文加快上傳成功
         * 500 第一塊上傳失敗,取消整個文件上傳
         * 507 服務器出錯自動重試該文件塊上傳
         */
        String path = PathUtils.getFileDir();
        String fileName = chunk.getFilename();
        String allFilePath = path + "/" + fileName;
        File file = new File(path, fileName);
        // 第一個塊,則新建文件
        if (chunk.getChunkNumber() == 1 && !file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                responseApi.setCode("500");
                responseApi.setMsg("exception:createFileException");
                return responseApi;
            }
        }
        // 進行寫文件操作
        try (
                //將塊文件寫入文件中
                InputStream fos = chunk.getFile().getInputStream();
                RandomAccessFile raf = new RandomAccessFile(file, "rw")
        ) {
            int len = -1;
            byte[] buffer = new byte[1024];
            raf.seek((chunk.getChunkNumber() - 1) * 1024 * 1024 * 5);
            while ((len = fos.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
            if (chunk.getChunkNumber() == 1) {
                file.delete();
            }
            responseApi.setCode("507");
            responseApi.setMsg("exception:writeFileException");
            return responseApi;
        }

        if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
            // 保存到本地文件成功
            responseApi.setCode("200");
            responseApi.setMsg("over");
       // 返回文件路徑和文件名稱  JSONObject json
= new JSONObject(); json.put("fileName", fileName); json.put("filePath", allFilePath); responseApi.setObj(json); System.out.println(json); return responseApi; } else { responseApi.setCode("201"); responseApi.setMsg("ok"); return responseApi; } } /** * 本地大文件分片上傳到s3存儲桶 * @param fileName * @param allFilePath */ public ResponseApi<Object> localFileToS3(String fileName, String allFilePath) { ResponseApi<Object> responseApi = new ResponseApi<Object>(); // 1. 前端上傳的文件整合保存到本地成功,將本地文件分片上傳到s3存儲桶 String suffix = fileName.split("[.]")[1]; String url = ""; responseApi = awsLocalFileToS3(fileName, allFilePath); if ("200".equals(responseApi.getCode())) { // 2. 上傳到s3成功后,獲取返回url url = String.valueOf(responseApi.getObj()); // 3. 刪除本地文件 boolean fileDelFlag = HelpUtil.delete(allFilePath); if (!fileDelFlag) { logger.info("刪除本地文件失敗,文件路徑:" + allFilePath); } } return responseApi; } /** * 本地大文件分片上傳到s3存儲桶 * 具體實現 * * @param fileName 文件名稱 * @param path 文件路徑 */ public ResponseApi<Object> awsLocalFileToS3(String fileName, String path) { ResponseApi<Object> responseApi = new ResponseApi<Object>(); Regions clientRegion = Regions.CN_NORTHWEST_1; try { AmazonS3 s3Client = AmazonS3ClientBuilder.standard() .withRegion(clientRegion) .withCredentials(new ProfileCredentialsProvider()) .build(); TransferManager tm = TransferManagerBuilder.standard() .withS3Client(s3Client) .build(); String objectKey = System.currentTimeMillis() + "_" + Math.random() + "_" + fileName; Upload upload = tm.upload(bucketName, objectKey, new File(path)); logger.info("上傳開始:" + fileName); // 上傳完成 upload.waitForCompletion(); logger.info("上傳完成:" + fileName); String url = "https://" + bucketName + ".s3.cn-northwest-1.amazonaws.com.cn/" + objectKey; responseApi.setCode("200"); responseApi.setMsg("ok"); responseApi.setObj(url); return responseApi; } catch (AmazonServiceException e) { // The call was transmitted successfully, but Amazon S3 couldn't process // it, so it returned an error response. e.printStackTrace(); responseApi.setCode("508"); responseApi.setMsg("AmazonServiceException"); return responseApi; } catch (SdkClientException e) { // Amazon S3 couldn't be contacted for a response, or the client // couldn't parse the response from Amazon S3. e.printStackTrace(); responseApi.setCode("508"); responseApi.setMsg("SdkClientException"); return responseApi; } catch (InterruptedException e) { e.printStackTrace(); responseApi.setCode("508"); responseApi.setMsg("InterruptedException"); return responseApi; } } }

獲取服務器根路徑

public class PathUtils {

    /**
     * 獲取服務器存放文件的目錄路徑
     *
     * @return 目錄路徑(String)
     */
    public static String getFileDir() {
        String path = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "static/file";
        File dir = new File(path);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return path;
    }
}

刪除本地文件HelpUtil中的delete方法

/**
 * 刪除文件
 *
 * @param fileName 待刪除的完整文件名
 * @return
 */
public static boolean delete(String fileName) {
    boolean result = false;
    File f = new File(fileName);
    if (f.exists()) {
        result = f.delete();
    } else {
        result = true;
    }
    return result;
}

其他

ResponseApi幫助類,返回結果

/**
 * 返回結果類
 */
public class ResponseApi<T> {
    private String code;
    private String msg;
    private T obj; 
 
    public ResponseApi() {  
        code = "0000";
        msg = "成功";  
    }
    public ResponseApi(T obj) { 
       super();
       code = "0000";
       msg = "成功";  
       this.obj = obj; 
    }
    public ResponseApi(String code,String msg, T obj) { 
        super();
        this.code = code;
        this.msg = msg;  
        this.obj = obj; 
    }
    
    // getter/setter
    
}

 Chunk幫助類

/**
 * 文件塊
 *
 */
public class Chunk implements Serializable {
    /**
     * 當前文件塊,從1開始
     */
    private Integer chunkNumber;
    /**
     * 分塊大小
     */
    private Long chunkSize;
    /**
     * 當前分塊大小
     */
    private Long currentChunkSize;
    /**
     * 總大小
     */
    private Long totalSize;
    /**
     * 文件標識
     */
    private String identifier;
    /**
     * 文件名
     */
    private String filename;
    /**
     * 相對路徑
     */
    private String relativePath;
    /**
     * 總塊數
     */
    private Integer totalChunks;

    /**
     * 二進制文件
     */
    private MultipartFile file;

    // getter/setter
}

 


免責聲明!

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



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