• <dd id="weawc"></dd>
  • Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳
    2022-09-06 22:35:06


    公司實現文件上傳技術選型采用后端SpringBoot/Cloud,前端vue Bootstrap ,阿里云OSS作為文件存儲,大文件上傳功能單獨抽取封裝大文件上傳組件,可供所有的大文件的操作。

    后端框架

    版本

    SpringBoot

    2.5.6

    Spring-Cloud

    2020.0.4

    mysql

    8.0.26

    pagehelper

    1.3.1

    Mybatis

    2.2.0

    Redis

    5.0

    Fastjson

    1.2.78

    前端框架

    版本

    Vue

    2.6.11

    axios

    0.24.0

    vue-router

    3.5.3

    Bootstrap

    4.6.2

    文章目錄

    一、前端部分
    1. 小節頁面

    小節頁面作為文件上傳父頁面

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java

    <div class="form-group">
    <label class="col-sm-2 control-label">視頻</label>
    <div class="col-sm-10">
    <vod :text="'上傳視頻'"
    :input-id="'video-upload'"
    :suffixs="['mp4']"
    :use="FILE_USE.COURSE.key"
    :after-upload="afterUpload">
    </vod>

    <div v-show="section.video" class="row">
    <div class="col-md-9">
    <player v-bind:player-id="'form-player-div'"
    ref="player"></player>
    <video v-bind:src="section.video" id="video" controls="controls" class="hidden"></video>
    </div>
    </div>
    </div>
    </div>
    2. js部分
    <script>

    import BigFile from "@/components/big-file";

    export default {
    components: { BigFile },
    name: 'business-section',
    data: function () {
    return {
    section: {},
    sections: [],
    FILE_USE: FILE_USE,
    }
    },
    methods: {
    /**
    * 點擊【新增】
    */
    add() {
    let _this = this
    _this.section = {}
    $("#form-modal").modal("show")
    },

    /**
    * 點擊【編輯】
    */
    edit(section) {
    let _this = this
    _this.section = $.extend({}, section)
    $("#form-modal").modal("show")
    },

    /**
    * 點擊【保存】
    */
    save() {
    let _this = this
    _this.section.video = "";

    // 保存校驗
    if (1 != 1
    || !Validator.require(_this.section.title, "標題")
    || !Validator.length(_this.section.title, "標題", 1, 50)
    || !Validator.length(_this.section.video, "視頻", 1, 200)
    ) {
    return;
    }

    _this.section.courseId = _this.course.id
    _this.section.chapterId = _this.chapter.id

    Loading.show()
    _this.$api.post(process.env.VUE_APP_SERVER + '/business/admin/section/save', _this.section).then((res) => {
    Loading.hide()
    let resp = res.data
    if (resp.success) {
    $("#form-modal").modal("hide")
    _this.list(1)
    Toast.success("保存成功!")
    } else {
    Toast.warning(resp.message)
    }
    })
    },

    afterUpload(resp) {
    let _this = this
    let video = resp.content.path;
    },
    },
    }

    </script>
    3. 大文件上傳組件
    <template>
    <div>
    <button type="button" v-on:click="selectFile()" class="btn btn-white btn-default btn-round">
    <i class="ace-icon fa fa-upload"></i>
    {{ text }}
    </button>
    <input class="hidden" type="file" ref="file" v-on:change="uploadFile()" v-bind:id="inputId+'-input'">
    </div>
    </template>

    <script>
    export default {
    name: 'big-file',
    props: {
    text: {
    default: "上傳大文件"
    },
    inputId: {
    default: "file-upload"
    },
    suffixs: {
    default: []
    },
    use: {
    default: ""
    },
    shardSize: {
    default: 50 * 1024
    },
    url: {
    default: "oss-append"
    },
    saveType: {
    default: "oss/"
    },
    afterUpload: {
    type: Function,
    default: null
    },
    },
    data: function () {
    return {}
    },
    methods: {
    uploadFile() {
    let _this = this;
    let formData = new window.FormData();
    let file = _this.$refs.file.files[0];

    console.log(JSON.stringify(file));
    /*
    name: "test.mp4"
    lastModified: 1901173357457
    lastModifiedDate: Tue May 27 2099 14:49:17 GMT+0800 (中國標準時間) {}
    webkitRelativePath: ""
    size: 37415970
    type: "video/mp4"
    */

    // 生成文件標識,標識多次上傳的是不是同一個文件
    let key = hex_md5(file.name + file.size + file.type);
    let key10 = parseInt(key, 16);
    let key62 = Tool._10to62(key10);
    console.log(key, key10, key62);
    console.log(hex_md5(Array()));
    /*
    d41d8cd98f00b204e9800998ecf8427e
    2.8194976848941264e+38
    6sfSqfOwzmik4A4icMYuUe
    */

    // 判斷文件格式
    let suffixs = _this.suffixs;
    let fileName = file.name;
    let suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
    let validateSuffix = false;
    for (let i = 0; i < suffixs.length; i++) {
    if (suffixs[i].toLowerCase() === suffix) {
    validateSuffix = true;
    break;
    }
    }
    if (!validateSuffix) {
    Toast.warning("文件格式不正確!只支持上傳:" + suffixs.join(","));
    $("#" + _this.inputId + "-input").val("");
    return;
    }

    // 文件分片
    // let shardSize = 10 * 1024 * 1024; //以10MB為一個分片
    // let shardSize = 50 * 1024; //以50KB為一個分片
    let shardSize = _this.shardSize;
    let shardIndex = 1; //分片索引,1表示第1個分片
    let size = file.size;
    let shardTotal = Math.ceil(size / shardSize); //總片數

    let param = {
    'shardIndex': shardIndex,
    'shardSize': shardSize,
    'shardTotal': shardTotal,
    'use': _this.use,
    'name': file.name,
    'suffix': suffix,
    'size': file.size,
    'key': key62
    };

    _this.check(param);
    },


    /**
    * 檢查文件狀態,是否已上傳過?傳到第幾個分片?
    */
    check(param) {
    let _this = this;
    _this.$api.get(process.env.VUE_APP_SERVER + '/file/admin/check/' + _this.saveType + param.key).then((response) => {
    let resp = response.data;
    if (resp.success) {
    let obj = resp.content;
    if (!obj) {
    param.shardIndex = 1;
    console.log("沒有找到文件記錄,從分片1開始上傳");
    _this.upload(param);
    } else if (obj.shardIndex === obj.shardTotal) {
    // 已上傳分片 = 分片總數,說明已全部上傳完,不需要再上傳
    Toast.success("文件極速秒傳成功!");
    _this.afterUpload(resp);
    $("#" + _this.inputId + "-input").val("");
    } else {
    param.shardIndex = obj.shardIndex + 1;
    console.log("找到文件記錄,從分片" + param.shardIndex + "開始上傳");
    _this.upload(param);
    }
    } else {
    Toast.warning("文件上傳失敗");
    $("#" + _this.inputId + "-input").val("");
    }
    })
    },

    /**
    * 將分片數據轉成base64進行上傳
    */
    upload(param) {
    let _this = this;
    let shardIndex = param.shardIndex;
    let shardTotal = param.shardTotal;
    let shardSize = param.shardSize;
    let fileShard = _this.getFileShard(shardIndex, shardSize);
    // 將圖片轉為base64進行傳輸
    let fileReader = new FileReader();

    Progress.show(parseInt((shardIndex - 1) * 100 / shardTotal));
    fileReader.onload = function (e) {
    let base64 = e.target.result;
    // console.log("base64:", base64);

    param.shard = base64;

    _this.$api.post(process.env.VUE_APP_SERVER + '/file/admin/' + _this.url, param).then((response) => {
    let resp = response.data;
    console.log("上傳文件成功:", resp);
    Progress.show(parseInt(shardIndex * 100 / shardTotal));
    if (shardIndex < shardTotal) {
    // 上傳下一個分片
    param.shardIndex = param.shardIndex + 1;
    _this.upload(param);
    } else {
    Progress.hide();
    _this.afterUpload(resp);
    $("#" + _this.inputId + "-input").val("");
    }
    });
    };
    fileReader.readAsDataURL(fileShard);
    },

    getFileShard(shardIndex, shardSize) {
    let _this = this;
    let file = _this.$refs.file.files[0];
    let start = (shardIndex - 1) * shardSize; //當前分片起始位置
    let end = Math.min(file.size, start + shardSize); //當前分片結束位置
    let fileShard = file.slice(start, end); //從文件中截取當前的分片數據
    return fileShard;
    },

    selectFile() {
    let _this = this;
    $("#" + _this.inputId + "-input").trigger("click");
    }
    }
    }
    </script>
    二、阿里云OSS

    官網:??https://www.aliyun.com??

    2.1. 注冊阿里云

    ??https://account.aliyun.com/register/register.htm??

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_vue.js_02

    2.2. 開通OSS

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_vue.js_03


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_04

    2.3. 進入管控臺

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_05


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_ide_06

    2.4. 創建 Bucket

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_07

    讀寫權限選擇【公共讀】,意思是都可以或者有權限看,沒其他特殊請求,其他的保持默認,點擊確定即可

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_08


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_vue.js_09

    2.5. 創建OSS用戶

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_ide_10


    或者

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_11


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_ide_12


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_13

    2.6. OSS權限

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_vue.js_14


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_15

    三、OSS Client 開發文檔

    ??https://www.aliyun.com/product/oss??

    3.1. OSS Client SDK

    開發語言java 追加上傳(斷點續傳已實現)

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_ide_16


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_17

    3.2. 限制

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_bootstrap_18

    3.3. SDK Client

    這里就是官網提供的java語言的SDK Client

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_19

    四、后端部分

    ??https://help.aliyun.com/document_detail/32009.html??

    4.1.依賴引入

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_20

    <!-- OSS Java SDK -->
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
    </dependency>
    4.2. 配置
    # 應用名稱
    spring.application.name=file
    # 應用端口
    server.port=9003
    # 注冊到eureka
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka

    # 請求訪問前綴
    server.servlet.context-path=/file

    # 本地存儲靜態文件路徑
    file.path=D:/file/imooc/course/
    # 訪問靜態文件路徑(用于文件回顯或者文件下載)
    file.domain=http://127.0.0.1:9000/file/f/

    # 文件大?。ㄈ绻罱ù笮〕^此配置的大小或拋出異常)
    spring.servlet.multipart.max-file-size=50MB
    # 請求大小
    spring.servlet.multipart.max-request-size=50MB


    # OSS 配置
    oss.accessKeyId=xxx
    oss.accessKeySecret=xxx
    oss.endpoint=http://oss-cn-beijing.aliyuncs.com
    oss.ossDomain=http://bucket名稱.oss-cn-beijing.aliyuncs.com/
    oss.bucket=xxx
    • oss.endpoint 和oss.ossDomain獲取方式
    • Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_ide_21

    • bucket 獲取方式
    • Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_22

    • oss.accessKeyId和oss.accessKeySecret獲取方式

    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_vue.js_23


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_java_24


    Vue Bootstrap OSS 實現文件追加上傳、斷點續傳、極速秒傳_上傳_25

    4.3. api接口
    package com.course.file.controller.admin;

    import com.alibaba.fastjson.JSON;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.model.AppendObjectRequest;
    import com.aliyun.oss.model.AppendObjectResult;
    import com.aliyun.oss.model.ObjectMetadata;
    import com.aliyun.oss.model.PutObjectRequest;
    import com.aliyuncs.DefaultAcsClient;
    import com.aliyuncs.vod.model.v20170321.GetMezzanineInfoResponse;
    import com.course.server.dto.FileDto;
    import com.course.server.dto.ResponseDto;
    import com.course.server.enums.FileUseEnum;
    import com.course.server.service.FileService;
    import com.course.server.util.Base64ToMultipartFile;
    import com.course.server.util.UuidUtil;
    import com.course.server.util.VodUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;

    import javax.annotation.Resource;
    import java.io.ByteArrayInputStream;

    @RequestMapping("/admin")
    @RestController
    public class OssController {
    public static final Logger LOG = LoggerFactory.getLogger(OssController.class);
    public static final String BUSINESS_NAME = "文件上傳";

    @Value("${oss.accessKeyId}")
    private String accessKeyId;

    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;

    @Value("${oss.endpoint}")
    private String endpoint;

    @Value("${oss.bucket}")
    private String bucket;

    @Value("${oss.ossDomain}")
    private String ossDomain;

    @Resource
    private FileService fileService;

    /**
    * oss追加上傳
    *
    * @param fileDto
    * @return
    * @throws Exception
    */
    @PostMapping("/oss-append")
    public ResponseDto fileUpload(@RequestBody FileDto fileDto) throws Exception {

    LOG.info("上傳文件開始");
    //接收前端的歸屬文件類型 COURSE("C", "課程"), TEACHER("T", "講師");
    String use = fileDto.getUse();
    // 為了支持一個文件上傳多次,展示歷史的不同版本,因此上傳文件前,統一添加文件前綴,下載時,統一截取文件沒那個前8位處理
    String key = fileDto.getKey();
    //分片索引,1表示第1個分片
    Integer shardIndex = fileDto.getShardIndex();
    // 文件分片大小 shardSize = 10 * 1024 * 1024;
    // 以10MB為一個分片
    Integer shardSize = fileDto.getShardSize();
    // 具體的文件 由于為了統一使用FileDto對象接收,默認接收類型是MultipartFile,這里現在接收類型是String ,前端將文件提前轉成了Base64
    String shardBase64 = fileDto.getShard();
    // 將具體的文件在由Base64轉成MultipartFile類型
    MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);

    //接收前端的歸屬文件類型 COURSE("C", "課程"), TEACHER("T", "講師");
    FileUseEnum useEnum = FileUseEnum.getByCode(use);

    //文件全名
    String filename = shard.getOriginalFilename();
    //如果文件夾不存在,則創建
    String dir = useEnum.name().toLowerCase();
    String path = new StringBuffer(dir)
    .append("/")
    .append(key)
    .append(".")
    .append(filename)
    .toString();// course6sfSqfOwzmik4A4icMYuUe.mp4

    // 創建OSSClient實例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    ObjectMetadata meta = new ObjectMetadata();
    // 指定上傳的內容類型。
    meta.setContentType("text/plain");

    // 通過AppendObjectRequest設置多個參數。
    AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucket, path,
    new ByteArrayInputStream(shard.getBytes()), meta);

    // 通過AppendObjectRequest設置單個參數。
    // 設置Bucket名稱。
    //appendObjectRequest.setBucketName(bucketName);
    // 設置Object名稱。即不包含Bucket名稱在內的Object的完整路徑,例如example/test.txt。
    //appendObjectRequest.setKey(objectName);
    // 設置待追加的內容??蛇x類型包括InputStream類型和File類型。此處為InputStream類型。
    //appendObjectRequest.setInputStream(new ByteArrayInputStream(content1.getBytes()));
    // 設置待追加的內容??蛇x類型包括InputStream類型和File類型。此處為File類型。
    //appendObjectRequest.setFile(new File("D:\localpath\examplefile.txt"));
    // 指定文件的元信息,第一次追加時有效。
    //appendObjectRequest.setMetadata(meta);

    // 第一次追加。
    // 設置文件的追加位置。
    // appendObjectRequest.setPosition(0L);
    appendObjectRequest.setPosition((long) (shardIndex - 1) * shardSize);
    AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
    // 文件的64位CRC值。此值根據ECMA-182標準計算得出
    System.out.println(appendObjectResult.getObjectCRC());

    // 關閉OSSClient。
    ossClient.shutdown();

    LOG.info("保存文件記錄開始");
    fileDto.setPath(path);
    fileService.save(fileDto);

    ResponseDto responseDto = new ResponseDto();
    // 文件OSS地址存儲到fileDto,統一返回前端
    fileDto.setPath(ossDomain + path);
    responseDto.setContent(fileDto);

    return responseDto;
    }

    /**
    * 斷點續傳檢查
    *
    * @param key
    * @return
    * @throws Exception
    */
    @GetMapping("/check/oss/{key}")
    public ResponseDto check(@PathVariable String key) throws Exception {
    LOG.info("檢查上傳分片開始:{}", key);
    ResponseDto responseDto = new ResponseDto();
    FileDto fileDto = fileService.findByKey(key);
    if (fileDto != null) {
    fileDto.setPath(ossDomain + fileDto.getPath());
    }
    responseDto.setContent(fileDto);
    return responseDto;
    }
    }


    本文摘自 :https://blog.51cto.com/g


    更多科技新聞 ......

    日本成人三级A片
  • <dd id="weawc"></dd>