公司实现文件上传技术选型采用后端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. 小节页面
    • 2. js部分
    • 3. 大文件上传组件
  • 二、后端部分
    • 2.1. 配置
    • 2.2. 配置
    • 2.3. api接口
一、前端部分
1. 小节页面

小节页面作为文件上传父页面

      <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.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 (中国标准时间) {}webkitRelativePath: ""size: 37415970type: "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()));/*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为一个分片// 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>
二、后端部分
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. 配置
# 应用名称
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
2.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();// 为了支持一个文件上传多次,展示历史的不同版本,因此上传文件前,统一添加文件前缀,下载时,统一截取文件没那个前8位处理String key = fileDto.getKey();// 具体的文件 由于为了统一使用FileDto对象接收,默认接收类型是MultipartFile,这里现在接收类型是String ,前端将文件提前转成了Base64String 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();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);// 判断当前分片数是否等于总片数,如果相等就进行文件合并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个分片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流关闭");} catch (Exception e) {LOG.error("IO流关闭", e);}}LOG.info("合并分片结束");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("删除分片结束");}/*** 断点续传检查** @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;}
}

Vue Bootstrap 静态服务器 实现文件追加上传、断点续传、极速秒传相关推荐

  1. vue加载服务器json文件,Vue加载json文件的方法简单示例

    本文实例讲述了Vue加载json文件的方法.分享给大家供大家参考,具体如下: 一.在build/dev-server.js文件里 var app = express() 这句代码后面添加如下(旧版): ...

  2. 服务器设置文件夹权限代码,服务器设置文件夹权限

    服务器设置文件夹权限 内容精选 换一换 媒资管理中显示的音视频文件同音视频管理中是一致的,除了有音视频管理页面的相关功能,您还可以在媒资管理中创建图片组并上传图片,以及创建文件夹.媒资管理暂只支持&q ...

  3. 使用scp上传文件到服务器或从服务器下载文件(支持跨越跳板机)

    转载 原文链接 原贴写的很好,我转载懒得排版了 scp是什么? 使用man scp可以看到scp的解释『scp - secure copy (remote file copy program)』,sc ...

  4. vue生成静态js文件_如何立即使用Vue.js生成静态网站

    vue生成静态js文件 by Ondřej Polesný 通过OndřejPolesný 如何立即使用Vue.js生成静态网站 (How to generate a static website w ...

  5. Vue Bootstrap OSS 实现文件追加上传、断点续传、极速秒传

    公司实现文件上传技术选型采用后端SpringBoot/Cloud,前端vue Bootstrap ,阿里云OSS作为文件存储,大文件上传功能单独抽取封装大文件上传组件,可供所有的大文件的操作. 后端框 ...

  6. Vue Bootstrap OSS 实现文件上传

    公司实现文件上传技术选型采用后端SpringBoot/Cloud,前端vue Bootstrap ,阿里云OSS作为文件存储,文件上传功能单独抽取封装文件上传组件,可供所有的文件的操作. 后端框架 版 ...

  7. vue打包静态文件名称不加hash值和不修改文件夹结构

    背景 使用vue脚手架开发前端项目引用了大量的图片,导致每次打包后都有好几百兆的大小,即使改动不涉及一些静态的图片和音频,打包的结果依然会修改静态文件名.如果只更新js文件等,会报引用无效的错误.这是 ...

  8. vue中 静态文件引用注意事项

    (一)assets文件夹与static文件夹的区别 区别一:assets文件是src下的,所以最后运行时需要进行打包,而static文件不需要打包就直接放在最终的文件中了 区别二:assets中的文件 ...

  9. golang 根据基础的url下载静态服务器上所有的文件

    功能 根据静态服务器上基础的url,获取所有的文件 运用到的知识点 日志处理 能在控制台打印,又能写入文件 向服务端发送get请求 http.Get(url) 递归获取文件夹和创建文件夹 packag ...

最新文章

  1. android contentresolver权限,求助关于getcontentresolver().query()
  2. c语言spi发送12位数据,【51单片机】普通I/O口模拟SPI口C语言程序
  3. UML图系列——用例图
  4. Unity 2018.3.1 SyncVar没有同步服务器变量
  5. (1-e^(-j5w))/(1-e^(-jw))=e^(-j2w)*sin(5w/2)/sin(w/2)的证明过程
  6. html5游戏 dice掷骰子,使用jQuery实现掷骰子游戏
  7. 将PowerShell连接到SQL Server
  8. csv导入sqlite(python)
  9. eclipse如何设置成保护眼的背景色
  10. 乐优商城(15)--订单服务
  11. 【QT】信号和槽机制
  12. Quartz时间表达式详解
  13. Java错误:找不到或无法加载主类
  14. 蒟蒻朱的 CSP2020 J/S 游记
  15. ZOJ:1003 Crashing Balloon(DFS)
  16. 给IT新人的15个建议:苦逼程序员的辛酸反省与总结 - 博客 - 伯乐在线
  17. 让老电脑焕发青春:Acer(宏碁)ASPIRE 4710G安装UbuntuKylin(优麒麟),使用Gparted调整分区大小
  18. Cadence Allegro 17.4学习记录开始06-PCB Editor 17.4快捷键的说明和中英文的切换和操作界面放大缩小设置
  19. Cocos2dx-lua触摸事件处理
  20. Word,Excel未保存,突然断电?找回死机后未储存的Word,Excel文件

热门文章

  1. AI前沿线上大会,ALBERT一作、京东AI科学家等大咖亲临现场,限时免费,名额有限!...
  2. 降级!调离!取消研究生导师资格!西南交大发布最新通报,多人被问责
  3. SCI期刊拒稿看看什么原因?
  4. 张景中:把数学变容易大有可为
  5. 想转行人工智能?哈佛博士后有话说...
  6. 华为荣耀电脑第三方linux,【第三方Linux版】荣耀MagicBook Pro 16.1英寸全面屏如何?某东入手评测...
  7. linux查看mysql表空间使用率_Oracle查看数据库表空间使用情况sql语句
  8. FileBeat + Pipeline 解析日志 保存至ElasticSearch(实战)
  9. opencv Mat常用操作
  10. 爆测一周,22年必看最细致代码托管工具测评