Vue Bootstrap 静态服务器 实现文件追加上传、断点续传、极速秒传
生活随笔
收集整理的這篇文章主要介紹了
Vue Bootstrap 静态服务器 实现文件追加上传、断点续传、极速秒传
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
公司實(shí)現(xiàn)文件上傳技術(shù)選型采用后端SpringBoot/Cloud,前端vue Bootstrap ,阿里云OSS作為文件存儲(chǔ),大文件上傳功能單獨(dú)抽取封裝大文件上傳組件,可供所有的大文件的操作。
| 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. 小節(jié)頁面
- 2. js部分
- 3. 大文件上傳組件
- 二、后端部分
- 2.1. 配置
- 2.2. 配置
- 2.3. api接口
一、前端部分
1. 小節(jié)頁面
小節(jié)頁面作為文件上傳父頁面
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: {/*** 點(diǎn)擊【新增】*/add() {let _this = this_this.section = {}$("#form-modal").modal("show")},/*** 點(diǎn)擊【編輯】*/edit(section) {let _this = this_this.section = $.extend({}, section)$("#form-modal").modal("show")},/*** 點(diǎn)擊【保存】*/save() {let _this = this_this.section.video = "";// 保存校驗(yàn)if (1 != 1|| !Validator.require(_this.section.title, "標(biāo)題")|| !Validator.length(_this.section.title, "標(biāo)題", 1, 50)|| !Validator.length(_this.section.video, "視頻", 1, 200)) {return;}_this.section.courseId = _this.course.id_this.section.chapterId = _this.chapter.idLoading.show()_this.$api.post(process.env.VUE_APP_SERVER + '/business/admin/section/save', _this.section).then((res) => {Loading.hide()let resp = res.dataif (resp.success) {$("#form-modal").modal("hide")_this.list(1)Toast.success("保存成功!")} else {Toast.warning(resp.message)}})},afterUpload(resp) {let _this = thislet 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: "upload"},saveType: {default: "local/"},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: 1901173357457lastModifiedDate: Tue May 27 2099 14:49:17 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間) {}webkitRelativePath: ""size: 37415970type: "video/mp4"*/// 生成文件標(biāo)識(shí),標(biāo)識(shí)多次上傳的是不是同一個(gè)文件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()));/*d41d8cd98f00b204e9800998ecf8427e2.8194976848941264e+386sfSqfOwzmik4A4icMYuUe*/// 判斷文件格式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為一個(gè)分片// let shardSize = 50 * 1024; //以50KB為一個(gè)分片let shardSize = _this.shardSize;let shardIndex = 1; //分片索引,1表示第1個(gè)分片let size = file.size;let shardTotal = Math.ceil(size / shardSize); //總片數(shù)let param = {'shardIndex': shardIndex,'shardSize': shardSize,'shardTotal': shardTotal,'use': _this.use,'name': file.name,'suffix': suffix,'size': file.size,'key': key62};_this.check(param);},/*** 檢查文件狀態(tài),是否已上傳過?傳到第幾個(gè)分片?*/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) {// 已上傳分片 = 分片總數(shù),說明已全部上傳完,不需要再上傳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("");}})},/*** 將分片數(shù)據(jù)轉(zhuǎn)成base64進(jìn)行上傳*/upload(param) {let _this = this;let shardIndex = param.shardIndex;let shardTotal = param.shardTotal;let shardSize = param.shardSize;let fileShard = _this.getFileShard(shardIndex, shardSize);// 將圖片轉(zhuǎn)為base64進(jìn)行傳輸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) {// 上傳下一個(gè)分片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; //當(dāng)前分片起始位置let end = Math.min(file.size, start + shardSize); //當(dāng)前分片結(jié)束位置let fileShard = file.slice(start, end); //從文件中截取當(dāng)前的分片數(shù)據(jù)return fileShard;},selectFile() {let _this = this;$("#" + _this.inputId + "-input").trigger("click");}} } </script>二、后端部分
2.1. 配置
package com.course.file.config;import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class SpingMvConfig implements WebMvcConfigurer {@Value("${file.path}")private String FILE_PATH;@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/f/**").addResourceLocations("file:" + FILE_PATH);} }2.2. 配置
# 應(yīng)用名稱 spring.application.name=file # 應(yīng)用端口 server.port=9003 # 注冊(cè)到eureka eureka.client.service-url.defaultZone=http://localhost:8761/eureka# 請(qǐng)求訪問前綴 server.servlet.context-path=/file# 本地存儲(chǔ)靜態(tài)文件路徑 file.path=D:/file/imooc/course/ # 訪問靜態(tài)文件路徑(用于文件回顯或者文件下載) file.domain=http://127.0.0.1:9000/file/f/# 文件大小(如果搭建大小超過此配置的大小或拋出異常) spring.servlet.multipart.max-file-size=50MB # 請(qǐng)求大小 spring.servlet.multipart.max-request-size=50MB2.3. api接口
package com.course.file.controller.admin;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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;@RequestMapping("/admin") @RestController public class UploadController {public static final Logger LOG = LoggerFactory.getLogger(UploadController.class);public static final String BUSINESS_NAME = "文件上傳";@Value("${file.domain}")private String FILE_DOMAIN;@Value("${file.path}")private String FILE_PATH;@Value("${vod.accessKeyId}")private String accessKeyId;@Value("${vod.accessKeySecret}")private String accessKeySecret;@Resourceprivate FileService fileService;@PostMapping("/upload")public ResponseDto upload(@RequestBody FileDto fileDto) throws Exception {LOG.info("上傳文件開始");//接收前端的歸屬文件類型 COURSE("C", "課程"), TEACHER("T", "講師");String use = fileDto.getUse();// 為了支持一個(gè)文件上傳多次,展示歷史的不同版本,因此上傳文件前,統(tǒng)一添加文件前綴,下載時(shí),統(tǒng)一截取文件沒那個(gè)前8位處理String key = fileDto.getKey();// 具體的文件 由于為了統(tǒng)一使用FileDto對(duì)象接收,默認(rèn)接收類型是MultipartFile,這里現(xiàn)在接收類型是String ,前端將文件提前轉(zhuǎn)成了Base64String shardBase64 = fileDto.getShard();// 將具體的文件在由Base64轉(zhuǎn)成MultipartFile類型MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);//接收前端的歸屬文件類型 COURSE("C", "課程"), TEACHER("T", "講師");FileUseEnum useEnum = FileUseEnum.getByCode(use);//文件全名String filename = shard.getOriginalFilename();//如果文件夾不存在,則創(chuàng)建String dir = useEnum.name().toLowerCase();File fullDir = new File(FILE_PATH + dir);if (!fullDir.exists()) {fullDir.mkdirs();}String path = new StringBuffer(dir).append(File.separator).append(key).append(".").append(filename).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4String localPath = new StringBuffer(path).append(".").append(fileDto.getShardIndex()).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1String fullPath = FILE_PATH + localPath;File dest = new File(fullPath);shard.transferTo(dest);LOG.info(dest.getAbsolutePath());LOG.info("保存文件記錄開始");fileDto.setPath(path);fileService.save(fileDto);ResponseDto responseDto = new ResponseDto();fileDto.setPath(FILE_DOMAIN + path);responseDto.setContent(fileDto);// 判斷當(dāng)前分片數(shù)是否等于總片數(shù),如果相等就進(jìn)行文件合并if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {this.merge(fileDto);}return responseDto;}public void merge(FileDto fileDto) throws Exception {LOG.info("合并分片開始");String path = fileDto.getPath(); //http://127.0.0.1:9000/file/f/course\6sfSqfOwzmik4A4icMYuUe.mp4path = path.replace(FILE_DOMAIN, ""); //course\6sfSqfOwzmik4A4icMYuUe.mp4Integer shardTotal = fileDto.getShardTotal();File newFile = new File(FILE_PATH + path);FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加寫入FileInputStream fileInputStream = null;//分片文件byte[] byt = new byte[10 * 1024 * 1024];int len;try {for (int i = 0; i < shardTotal; i++) {// 讀取第i個(gè)分片fileInputStream = new FileInputStream(new File(FILE_PATH + path + "." + (i + 1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1while ((len = fileInputStream.read(byt)) != -1) {outputStream.write(byt, 0, len);}}} catch (IOException e) {LOG.error("分片合并異常", e);} finally {try {if (fileInputStream != null) {fileInputStream.close();}outputStream.close();LOG.info("IO流關(guān)閉");} catch (Exception e) {LOG.error("IO流關(guān)閉", e);}}LOG.info("合并分片結(jié)束");System.gc();Thread.sleep(100);// 刪除分片LOG.info("刪除分片開始");for (int i = 0; i < shardTotal; i++) {String filePath = FILE_PATH + path + "." + (i + 1);File file = new File(filePath);boolean result = file.delete();LOG.info("刪除{},{}", filePath, result ? "成功" : "失敗");}LOG.info("刪除分片結(jié)束");}/*** 斷點(diǎn)續(xù)傳檢查** @param key* @return* @throws Exception*/@GetMapping("/check/local/{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(FILE_DOMAIN + fileDto.getPath());}responseDto.setContent(fileDto);return responseDto;} }總結(jié)
以上是生活随笔為你收集整理的Vue Bootstrap 静态服务器 实现文件追加上传、断点续传、极速秒传的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nexus3下载地址
- 下一篇: Vue v-if,v-else-if,v