• Vue大文件上传 vuesimpleloader分片上传到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
    }
  • 相关阅读:
    数据库性能优化一:数据库自身优化(大数据量)
    在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。Microsoft SQL Server,错误: 10061
    跨域请求解决方案
    转:intent简介
    转:屏幕适配
    java.util.concurrent.RejectedExecutionException
    转:Android事件传递机制
    转:通过重写ViewGroup学习onMeasure()和onLayout()方法
    android:layout_weight属性详解
    转:Android自动测试之Monkey工具
  • 原文地址:https://www.cnblogs.com/aiyowei/p/15763262.html
Copyright © 2020-2023  润新知