Springboot+vue实现大文件上传
背景:为了实现大文件上传的功能
1新建数据表sql
file_chunk
CREATE TABLE `file_chunk` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件名',`chunk_number` int NULL DEFAULT NULL COMMENT '当前分片,从1开始',`chunk_size` bigint NULL DEFAULT NULL COMMENT '分片大小',`current_chunk_size` bigint NULL DEFAULT NULL COMMENT '当前分片大小',`total_size` bigint NULL DEFAULT NULL COMMENT '文件总大小',`total_chunk` int NULL DEFAULT NULL COMMENT '总分片数',`identifier` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件校验码,md5',`relative_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '相对路径',`create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '创建者',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '更新人',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1865947819987177473 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_520_ci COMMENT = '文件块存储' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
file_storage
CREATE TABLE `file_storage` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',`real_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件真实姓名',`file_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件名',`suffix` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件后缀',`file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件路径',`file_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '文件类型',`size` bigint NULL DEFAULT NULL COMMENT '文件大小',`identifier` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '检验码 md5',`create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '创建者',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT '更新人',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1865947820054286338 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_520_ci COMMENT = '文件存储表' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
2创建实体类:
FileChunk
lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/**** @Description:* @date 2024年12月05日 11:20* @Version: 1.0*/
@Data
public class FileChunk implements Serializable {/**主键**/private Long id;/**文件名**/private String fileName;/**当前分片,从1开始**/private Integer chunkNumber;/**分片大小**/private Long chunkSize;/**当前分片大小**/private Long currentChunkSize;/**文件总大小**/private Long totalSize;/**总分片数**/private Integer totalChunk;/**文件标识 md5校验码**/private String identifier;/**相对路径**/private String relativePath;/**创建者**/private String createBy;/**创建时间**/private LocalDateTime createTime;/**更新人**/private String updateBy;/**更新时间**/private LocalDateTime updateTime;}
FileStorage:
import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/**** @Description:* @date 2024年12月05日 11:23* @Version: 1.0*/
@Data
public class FileStorage implements Serializable {/**主键**/private Long id;/**文件真实姓名**/private String realName;/**文件名**/private String fileName;/**文件后缀**/private String suffix;/**文件路径**/private String filePath;/**文件类型**/private String fileType;/**文件大小**/private Long size;/**检验码 md5**/private String identifier;/**创建者**/private String createBy;/**创建时间**/private LocalDateTime createTime;/**更新人**/private String updateBy;/**更新时间**/private LocalDateTime updateTime;}
2service
package com.jx.springbootbigfileupload.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.jx.springbootbigfileupload.dto.CheckResultVo;
import com.jx.springbootbigfileupload.dto.FileChunkDto;
import com.jx.springbootbigfileupload.entity.FileChunk;/*** @author* @Description:* @date 2024年12月05日 11:48* @Version: 1.0*/
public interface FileChunkService extends IService<FileChunk> {/*** 校验文件* @param dto 入参* @return obj*/CheckResultVo checkUpload(FileChunkDto dto);
}
package com.jx.springbootbigfileupload.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.jx.springbootbigfileupload.dto.FileChunkDto;
import com.jx.springbootbigfileupload.entity.FileStorage;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public interface FileStorageService extends IService<FileStorage> {/*** 文件上传* @date 2024/12/5 16:51* @param dto* @return java.lang.Boolean*/Boolean uploadFile(FileChunkDto dto);/*** 文件下载** @date 2024/12/5 16:52* @param request* @param response* @param identifier*/void downLoadByIndentifier(HttpServletRequest request, HttpServletResponse response, String identifier);
}
package com.jx.springbootbigfileupload.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jx.springbootbigfileupload.dto.CheckResultVo;
import com.jx.springbootbigfileupload.dto.FileChunkDto;
import com.jx.springbootbigfileupload.entity.FileChunk;
import com.jx.springbootbigfileupload.mapper.FileChunkMapper;
import com.jx.springbootbigfileupload.service.FileChunkService;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** @author* @Description:* @date 2024年12月05日 11:52* @Version: 1.0*/
@Service
public class FileChunkServiceImpl extends ServiceImpl<FileChunkMapper, FileChunk> implements FileChunkService {@Overridepublic CheckResultVo checkUpload(FileChunkDto dto) {CheckResultVo checkResultVo = new CheckResultVo();List<FileChunk> list = this.list(new LambdaQueryWrapper<FileChunk>().eq(FileChunk::getIdentifier, dto.getIdentifier()).orderByDesc(FileChunk::getChunkNumber));if (list.size() ==0){checkResultVo.setUploaded(false);return checkResultVo;}FileChunk fileChunk = list.get(0);if (fileChunk.getTotalChunk() == 1){checkResultVo.setUploaded(true);return checkResultVo;}List<Integer> uploadedFiles=new ArrayList<>();for (FileChunk chunk : list) {uploadedFiles.add(chunk.getChunkNumber());}checkResultVo.setUploadFiles(uploadedFiles);return checkResultVo;}}
package com.jx.springbootbigfileupload.service.impl;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jx.springbootbigfileupload.dto.FileChunkDto;
import com.jx.springbootbigfileupload.entity.FileChunk;
import com.jx.springbootbigfileupload.entity.FileStorage;
import com.jx.springbootbigfileupload.mapper.FileStorageMapper;
import com.jx.springbootbigfileupload.service.FileChunkService;
import com.jx.springbootbigfileupload.service.FileStorageService;
import com.jx.springbootbigfileupload.util.BulkFileUtil;
import com.jx.springbootbigfileupload.util.FileUtil;
import com.jx.springbootbigfileupload.util.RedisCache;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.stream.IntStream;import static cn.hutool.core.bean.BeanUtil.copyProperties;
import static cn.hutool.core.bean.BeanUtil.findEditor;/*** @author* @Description:* @date 2024年12月05日 11:49* @Version: 1.0*/
@Slf4j
@Service
public class FileStorageServiceImpl extends ServiceImpl<FileStorageMapper, FileStorage> implements FileStorageService {@Resourceprivate RedisCache redisCache;@Value("${file.chunk-size}")private Long defaultChunkSize;@Value("${file.path}")private String baseFileSavePath;@Resourceprivate FileChunkService fileChunkService;@Overridepublic Boolean uploadFile(FileChunkDto dto) {if (dto.getFile()==null){throw new RuntimeException("文件不能为空");}String fileRouteName=baseFileSavePath+ File.separator+dto.getFilename();Boolean uploadFlag;if (dto.getTotalChunks()==1){//单片上传uploadFlag=this.uploadSingFile(fileRouteName,dto);}else{//分片上传uploadFlag=this.uploadSharding(fileRouteName,dto);}if (uploadFlag){this.saveFile(dto,fileRouteName);}return uploadFlag;}@SneakyThrows@Overridepublic void downLoadByIndentifier(HttpServletRequest request, HttpServletResponse response, String identifier) {FileStorage one = this.getOne(new LambdaQueryWrapper<FileStorage>().eq(FileStorage::getIdentifier, identifier));if (BeanUtil.isNotEmpty(one)){File file = new File(baseFileSavePath + File.separator + one.getFilePath());BulkFileUtil.downlodFile(request,response,file);}else {throw new RuntimeException("文件不存在");}}/*** 保存文件** @date 2024/12/5 16:15* @param dto* @param fileRouteName*/private void saveFile(FileChunkDto dto, String fileRouteName) {FileChunk fileChunk= BeanUtil.copyProperties(dto, FileChunk.class);fileChunk.setFileName(dto.getFilename());fileChunk.setTotalChunk(dto.getTotalChunks());fileChunkService.save(fileChunk);//重新给缓存赋值redisCache.setCacheListByOne(dto.getIdentifier(),dto.getChunkNumber());//如果所有的分片快都上传完成,那么在redis存储List<Integer> cacheList = redisCache.getCacheList(dto.getIdentifier());Integer totalChunks=dto.getTotalChunks();int[] chunks= IntStream.rangeClosed(1,totalChunks).toArray();if (IntStream.rangeClosed(1,totalChunks).allMatch(cacheList::contains)){//所有的分片上传完成,组合分片并保存到数据库中String name=dto.getFilename();MultipartFile file = dto.getFile();FileStorage fileStorage = new FileStorage();fileStorage.setRealName(file.getOriginalFilename());fileStorage.setFileName(fileRouteName);fileStorage.setSize(dto.getTotalSize());fileStorage.setIdentifier(dto.getIdentifier());fileStorage.setFilePath(dto.getRelativePath());fileStorage.setFileType(file.getContentType());fileStorage.setSuffix(FileUtil.getFileSuffix(name));this.save(fileStorage);}}/*** 分片上传方法* 这里使用RandomAccessFile 方法,也可以使用MapperByteBuffer方法上传* 可以省去文件合并的过程** @date 2024/12/5 16:03* @param fileRouteName 文件名* @param dto 文件dto* @return java.lang.Boolean*/private Boolean uploadSharding(String fileRouteName, FileChunkDto dto) {//try 自动资源管理try(RandomAccessFile randomAccessFile=new RandomAccessFile(fileRouteName,"rw")){//分片大小必须和前端匹配,否则会出现文件损坏long chunkSize= dto.getChunkSize()==0L?defaultChunkSize:dto.getChunkSize();//偏移量,就是从第一个位置往文件写入,每一片的大小*已经存的快数long offset=chunkSize*(dto.getChunkNumber()-1);randomAccessFile.seek(offset);//写入randomAccessFile.write(dto.getFile().getBytes());}catch(IOException e){log.info("文件上传失败:",e);return Boolean.FALSE;}return Boolean.TRUE;}/*** 单片上传** @date 2024/12/5 15:57* @param fileRouteName* @param dto* @return java.lang.Boolean*/private Boolean uploadSingFile(String fileRouteName, FileChunkDto dto) {try {File localPath = new File(fileRouteName);dto.getFile().transferTo(localPath);return Boolean.TRUE;} catch (IOException e) {throw new RuntimeException(e);}}
}
4mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jx.springbootbigfileupload.entity.FileChunk;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface FileChunkMapper extends BaseMapper<FileChunk> {}
package com.jx.springbootbigfileupload.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jx.springbootbigfileupload.entity.FileStorage;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface FileStorageMapper extends BaseMapper<FileStorage> {}
5
import lombok.Data;import java.util.List;/*** @author* @Description:* @date 2024年12月05日 13:32* @Version: 1.0*/
@Data
public class CheckResultVo {private Boolean uploaded;private List<Integer> uploadFiles;
}
package com.jx.springbootbigfileupload.dto;import lombok.Data;
import org.springframework.web.multipart.MultipartFile;/*** @author* @Description:* @date 2024年12月05日 11:29* @Version: 1.0*/
@Data
public class FileChunkDto {/*** 当前块的次序,第一个块是 1,注意不是从 0 开始的*/private Integer chunkNumber;/*** 文件被分成块的总数。*/private Integer totalChunks;/*** 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大*/private Long chunkSize;/*** 当前要上传块的大小,实际大小*/private Long currentChunkSize;/*** 文件总大小*/private Long totalSize;/*** 这个就是每个文件的唯一标示*/private String identifier;/*** 文件名*/private String filename;/*** 文件夹上传的时候文件的相对路径属性*/private String relativePath;/*** 文件*/private MultipartFile file;}
6controller
package com.jx.springbootbigfileupload.controller;import com.jx.springbootbigfileupload.dto.CheckResultVo;
import com.jx.springbootbigfileupload.dto.FileChunkDto;
import com.jx.springbootbigfileupload.response.Result;
import com.jx.springbootbigfileupload.service.FileChunkService;
import com.jx.springbootbigfileupload.service.FileStorageService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author* @Description:* @date 2024年12月05日 13:09* @Version: 1.0*/
@RestController
@RequestMapping("fileStorage")
public class FileStorageController {@Resourceprivate FileStorageService fileStorageService;@Resourceprivate FileChunkService fileChunkService;@GetMapping("/upload")public Result<CheckResultVo> checkUpload(FileChunkDto dto){return Result.ok(fileChunkService.checkUpload(dto));}@PostMapping("/upload")public Result<?> uploadFile(FileChunkDto dto, HttpServletResponse response){try {Boolean status=fileStorageService.uploadFile(dto);if (status){return Result.ok("文件上传成功");}else {response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);return Result.error("文件上传失败");}} catch (Exception e) {response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);return Result.error("文件上传失败");}}@GetMapping("/download/{identifier}")public void downLoadByIndentifier(HttpServletRequest request, HttpServletResponse response, @PathVariable("identifier")String identifier){try {fileStorageService.downLoadByIndentifier(request,response,identifier);} catch (Exception e) {throw new RuntimeException(e);}}}
CorsConfig
package com.jx.springbootbigfileupload.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;/*** @author* @Description:* @date 2024年12月09日 9:58* @Version: 1.0*/
@Configuration
public class CorsConfig {private static final long MAX_AGE = 24 * 60 * 60;@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowCredentials(true);corsConfiguration.addAllowedOriginPattern("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.setMaxAge(MAX_AGE);source.registerCorsConfiguration("/**", corsConfiguration);return new CorsFilter(source);}}
package com.jx.springbootbigfileupload.response;import lombok.Data;import java.io.Serializable;/*** @author* @Description:* @date 2024年12月05日 13:23* @Version: 1.0*/@Data
public class Result<T> implements Serializable {private Boolean success;private Integer code;private String msg;private T data;public Result(){this.success=true;this.code=200;this.msg="成功";}public Result(Integer code,T data){this.code=code;this.data=data;}public Result(Integer code,String msg,T data){this.code=code;this.msg=msg;this.data=data;}public Result(Integer code,String msg){this.code=code;this.msg=msg;}public Result<?> error(Integer code,String msg){this.code=code;this.msg=msg;this.success=false;return this;}public static Result error(String msg){return new Result(500,msg);}public static Result ok(Object data) {Result result = new Result();result.setData(data);return result;}public static Result ok(String msg) {Result result = new Result();result.setData(null);result.setCode(200);result.setMsg(msg);return result;}}
前端安装的依赖;
npm install axios core-js element-ui jquery simple-uploader.js spark-md5 vue-simple-uploader vue-router
main.js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';// 在main.js中:
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
Vue.use(ElementUI);// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({el: '#app',router,components: { App },template: '<App/>'
})
Upload.vue
<template><div><uploaderref="uploader":options="options":autoStart="false":file-status-text="fileStatusText"@file-added="onFileAdded"@file-success="onFileSuccess"@file-error="onFileError"@file-progress="onFileProgress"class="uploader-example"><uploader-drop><p>拖动文件到这里上传</p><uploader-btn>选择文件</uploader-btn></uploader-drop><uploader-list><el-collapse v-model="activeName" accordion><el-collapse-item title="文件列表" name="1"><ul class="file-list"><li v-for="file in uploadFileList" :key="file.id"><uploader-file :file="file" :list="true" ref="uploaderFile"><div slot-scope="props" style="display: flex;align-items: center;height: 100%;"><el-progressstyle="width: 85%":stroke-width="18":show-text="true":text-inside="true":format="e=> showDetail(e,props)":percentage="percentage(props)":color="e=>progressColor(e,props)"></el-progress><el-button :icon="icon" circle v-if="props.paused || props.isUploading"@click="pause(file)" size="mini"></el-button><el-button icon="el-icon-close" circle @click="remove(file)"size="mini"></el-button><el-button icon="el-icon-download" circle v-if="props.isComplete"@click="download(file)"size="mini"></el-button></div></uploader-file></li><div class="no-file" v-if="!uploadFileList.length"><i class="icon icon-empty-file"></i> 暂无待上传文件</div></ul></el-collapse-item></el-collapse></uploader-list></uploader></div>
</template>
<script>const CHUNK_SIZE = 1024 * 1024 * 20import SparkMD5 from 'spark-md5';
export default {name: 'Upload',data () {return {options: {target: 'http://192.168.1.87:9001/fileStorage/upload',testChunks: true,uploadMethod: 'POST',chunkSize: CHUNK_SIZE,simultaneousUploads: 3,/*** 判断分片是否上传,**/checkChunkUploadedByResponse: (chunk, message) => {console.log("message", message)let dataObj=JSON.parse(message);if (dataObj.uploaded!=null){return dataObj.uploaded;}return (dataObj.uploadedChunks||[]).indexOf(chunk.offset+1)>=0;},parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) {return parsedTimeRemaining.replace(/\syears?/, '年').replace(/\days?/, '天').replace(/\shours?/, '小时').replace(/\sminutes?/, '分钟').replace(/\sseconds?/, '秒')}},// 修改上传状态fileStatusTextObj: {success: "上传成功",error: "上传错误",uploading: "正在上传",paused: "停止上传",waiting: "等待中",},uploadFileList: [],collapse: true,activeName: 1,icon: `el-icon-video-pause`}},methods:{onFileProgress(rootfile,file,chunk){console.log(`当前进度:${Math.ceil(file._prevProgress*100)}`);},onFileError(rootfile,file,message,chunk){console.log("上传出错:"+rootfile,file,message,chunk);},onFileSuccess(rootfile,file,response,chunk){console.log("上传成功",rootfile,file,response,chunk);},// 点击下载download(file, id) {console.log("file:>> ", file);window.location.href = `http://192.168.1.87:9001/fileStorage/download/${file.uniqueIdentifier}`;},//控制下进度条的颜色,异常的情况下显示红色progressColor(e,props){if(props.error){return "#f56c62"}if (e>0){return "#1989fa"}},pause(file,id){console.log("file:>>",file);console.log("id:>>",id);if (file.paused){file.resume();this.icon=`el-icon-video-pause`}else{file.pause();this.icon=`el-icon-video-play`}},//点击删除remove(file){this.uploadFileList.findIndex((item,index)=>{if (item.id === file.id){this.$nextTick(()=>{this.uploadFileList.splice(index,1);})}})},//展示详情showDetail(e,props){let flieName=props.file.name;let isComplete=props.isComplete;let formatUpload=this.formatFileSize(props.uploadedSize,2);let fileSize=`${props.formatedSize}`;let timeRemaining=!isComplete?`剩余时间:${props.formatedTimeRemaining}`:"";let uploaded=!isComplete?`已上传:${formatUpload}/${fileSize}`:`大小:${fileSize}`;let speed=!isComplete ?`速度:${props.formatedSpeed}/s`:"";if (props.error){return `${flieName}上传失败`}else{return `${flieName}\n${uploaded}\n${speed}\n${timeRemaining} \n 进度:${e}`}},//显示进度percentage(props){let progress=props.progress.toFixed(2)*100;return progress-1 <0?0:progress;},formatFileSize(bytes,decimalPoint=2){if (bytes==0) return "0 Bytes";let k=1000,sizes = ['Bytes','KB','MB','GB','TB','PB','EB','ZB','YB'],i = Math.floor(Math.log(bytes) / Math.log(k));return (parseFloat((bytes/Math.pow(k,i)).toFixed(decimalPoint)+""+sizes[i]));},onFileAdded(file,event){console.log("eeeee",event)// event.preventDefault();this.uploadFileList.push(file);console.log("file :>> ", file);// 有时 fileType为空,需截取字符console.log("文件类型:" + file.fileType + "文件大小:" + file.size + "B");// 1. todo 判断文件类型是否允许上传// 2. 计算文件 MD5 并请求后台判断是否已上传,是则取消上传console.log("校验MD5");this.getFileMD5(file, (md5) => {if (md5 !== "") {// 修改文件唯一标识file.uniqueIdentifier = md5;// 请求后台判断是否上传// 恢复上传file.resume();}});},getFileMD5(file,callback){let spark = new SparkMD5.ArrayBuffer();let fileReader = new FileReader();//获取文件分片对象(注意它的兼容性,在不同浏览器的写法不同)let blobSlice =File.prototype.slice ||File.prototype.mozSlice ||File.prototype.webkitSlice;// 当前分片下标let currentChunk = 0;// 分片总数(向下取整)let chunks = Math.ceil(file.size / CHUNK_SIZE);// MD5加密开始时间let startTime = new Date().getTime();// 暂停上传file.pause();loadNext();// fileReader.readAsArrayBuffer操作会触发onload事件fileReader.onload = function (e) {// console.log("currentChunk :>> ", currentChunk);// 通过 e.target.result 获取到当前分片的内容,并将其追加到 MD5 计算实例 spark 中,以便后续计算整个文件的 MD5 值。spark.append(e.target.result);// 通过比较当前分片的索引 currentChunk 是否小于总分片数 chunks 判断是否还有下一个分片需要读取。if (currentChunk < chunks) {// 如果存在下一个分片,则将当前分片索引递增并调用 loadNext() 函数加载下一个分片;currentChunk++;loadNext();} else {// 否则,表示所有分片已经读取完毕,可以进行最后的 MD5 计算。// 该文件的md5值let md5 = spark.end();console.log(`MD5计算完毕:${md5},耗时:${new Date().getTime() - startTime} ms.`);// 回调传值md5callback(md5);}};fileReader.onerror = function () {this.$message.error("文件读取错误");file.cancel();};// 加载下一个分片function loadNext() {// start 的计算方式为当前分片的索引乘以分片大小 CHUNK_SIZEconst start = currentChunk * CHUNK_SIZE;// end 的计算方式为 start 加上 CHUNK_SIZE,但如果超过了文件的总大小,则取文件的大小作为结束位置。const end = start + CHUNK_SIZE >= file.size ? file.size : start + CHUNK_SIZE;// 文件分片操作,读取下一分片(fileReader.readAsArrayBuffer操作会触发onload事件)// 通过调用 blobSlice.call(file.file, start, end) 方法获取当前分片的 Blob 对象,即指定开始和结束位置的文件分片。// 接着,使用 fileReader.readAsArrayBuffer() 方法读取该 Blob 对象的内容,从而触发 onload 事件,继续进行文件的处理fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));}},fileStatusText(status,response){if (status === "md5") {return "校验MD5";} else {return this.fileStatusTextObj[status];}}}
}
</script>
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Upload from '@/view/Upload'
import Index from '@/view/Index'Vue.use(Router)export default new Router({routes: [{path: '/',name: 'Index',component: Index},{path: '/upload',name: 'Upload',component: Upload}]
})
相关文章:
Springboot+vue实现大文件上传
背景:为了实现大文件上传的功能 1新建数据表sql file_chunk CREATE TABLE file_chunk (id bigint UNSIGNED NOT NULL AUTO_INCREMENT,file_name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NULL DEFAULT NULL COMMENT 文件名,chunk_nu…...
Linux笔记
常用的基本命令 查询某个安装包有没有安装某个软件 使用的命令是rpm -qa |grep 软件名字 卸载软件 rpm -e --nodeps 软件名称 查看已经启动的服务 netstat -tunlp 一般我们在Linux系统中上传文件一般上传到 /usr/local/src的目录下 查看防火墙的命令 firewall-cmd --sta…...
相机(Camera)成像原理详解
简介:个人学习分享,如有错误,欢迎批评指正。 成像流程 1、光学相机的定义 顾名思义,光学相机就是利用光学原理进行成像的相机,而且市面上的相机几乎都是光学相机,只不过随着时代的发展,胶卷式…...
计算机网络知识点全梳理(一.TCP/IP网络模型)
目录 TCP/IP网络模型概述 应用层 什么是应用层 应用层功能 应用层协议 传输层 什么是传输层 传输层功能 传输层协议 网络层 什么是网络层 网络层功能 网络层协议 数据链路层 什么是数据链路层 数据链路层功能 物理层 物理层的概念和功能 写在前面 本系列文…...
后端接受前端传递数组进行批量删除
问题描述:当我们需要做批量删除功能的时候,我们循环单次删除的接口也能进行批量删除,但要删除100条数据就要调用100次接口,或者执行100次sql,这样系统开销是比较大的,那么我们直接采用接收的数组格式数据sq…...
理解数据结构 hashtable的简易理解思路
结构图 为了方便演示,下图中分区算法为下标取模 private int hashFun(int id) {//使用 hash并取模return id % size;}Hashtable的结构如图所示:是一个数组(元素为各个链表的表头) 多个链表组成,也就是说 hashtable 结…...
大数据面试题--企业面试真题
大数据面试题--企业面试真题 PlanHub 点击访问获取: 大数据面试体系专栏_酷兜科技www.kudoumh.top/hlwai/85.html 点击访问获取: 大数据面试体系专栏_酷兜科技www.kudoumh.top/hlwai/85.html 大数据面试题汇总 HDFS 1、 HDFS 读写流程。 2、HDF…...
数据结构(C语言版)-6.查找
1. 查找的基本概念 2. 静态查找 2.1 顺序查找 typedef int KeyType; typedef int InfoType; typedef struct {KeyType key;InfoType otherdata; }SeqList; // 顺序表类型 // 顺序查找int SeqSearch(SeqList R[], int n, int k) {int i n;R[0].key k; // R[0].key为查找不成…...
RabbitMQ消息队列的笔记
Rabbit与Java相结合 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> 在配置文件中编写关于rabbitmq的配置 rabbitmq:host: 192.168.190.132 /…...
linux不同发行版中的主要差异
一、初始化系统 Linux不同发行版中的系统初始化系统(如 System V init、Upstart 或 systemd) System V init: 历史:System V init 是最传统的 Linux 系统初始化系统,起源于 Unix System V 操作系统。运行级别ÿ…...
Elasticsearch+Kibana分布式存储引擎
1.ElaticSearch介绍 ElaticSearch ,简称为 ES , ES 是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检 索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。 ES 也使用 …...
spark 分布式 原理
Apache Spark 是一个快速且通用的大数据处理引擎,它支持分布式计算。Spark 的设计旨在通过高效的内存内计算和对多种数据源的支持来简化大规模数据集的处理。以下是关于 Spark 分布式原理的详细介绍: 1. 架构概述 Driver Program(驱动程序&…...
Hadoop学习笔记(包括hadoop3.4.0集群安装)(黑马)
Hadoop学习笔记 0-前置章节-环境准备 0.1 环境介绍 配置环境:hadoop-3.4.0,jdk-8u171-linux-x64 0.2 VMware准备Linux虚拟机 0.2.1主机名、IP、SSH免密登录 1.配置固定IP地址(root权限) 开启master,修改主机名为…...
thinkphp:try-catch捕获异常
使用简单的例子,实现了一个简单的try-catch捕获异常的实例 //开始事务Db::startTrans(); try{ //有异常抛出异常 if(存在错误){ throw new \Exception("异常信息"); } // 提交事务 Db::commit(); // 返回成功信息 ... } catch (\…...
如何使用 uni-app 构建直播应用程序?
使用uni-app构建直播应用程序涉及前端和后端的开发,以及音视频处理技术的选择。下面我将概述一个典型的直播应用架构,并详细说明如何在uni-app中实现关键功能。 直播应用架构 前端(uni-app):负责用户界面展示、互动逻…...
正则表达式入门教程
正则表达式入门教程 1. 引言 正则表达式(Regular Expression,简称Regex)是一种用于处理字符串的强大工具,它允许用户通过特定的模式(pattern)来搜索、匹配、查找和替换文本中的数据。正则表达式在文本处理、数据验证、数据提取等领域有着广泛的应用。本教程将带你了解正…...
uniapp 常用的指令语句
uniapp 是一个使用 Vue.js 开发的跨平台应用框架,因此,它继承了 Vue.js 的大部分指令。以下是一些在 uniapp 中常用的 Vue 指令语句及其用途: v-if / v-else-if / v-else 条件渲染。v-if 有条件地渲染元素,v-else-if 和 v-else 用…...
【Figma_01】Figma软件初始与使用
Figma初识与学习准备 背景介绍软件使用1.1 切换主题1.2 官方社区 设计界面2.1 创建一个项目2.2 修改文件名2.3 四种模式2.4 新增界面2.5 图层2.6 工具栏2.7 属性栏section透明度和圆角改变多边形的边数渐变效果描边设置阴影等特效拖拽相同的图形 背景介绍 Ul设计:User Interfa…...
AI工具如何深刻改变我们的工作与生活
在当今这个科技日新月异的时代,人工智能(AI)已经从科幻小说中的概念变成了我们日常生活中不可或缺的一部分。从智能家居到自动驾驶汽车,从医疗诊断到金融服务,AI正以惊人的速度重塑着我们的世界。 一、工作方式的革新…...
信息安全实训室网络攻防靶场实战核心平台解决方案
一、引言 网络安全靶场,作为一种融合了虚拟与现实环境的综合性平台,专为基础设施、应用程序及物理系统等目标设计,旨在向系统用户提供全方位的安全服务,涵盖教学、研究、训练及测试等多个维度。随着网络空间对抗态势的日益复杂化…...
平方根无迹卡尔曼滤波(SR-UKF)的MATLAB例程,使用三维非线性的系统
本MATLAB 代码实现了平方根无迹卡尔曼滤波(SR-UKF)算法,用于处理三维非线性状态估计问题 文章目录 运行结果代码概述代码 运行结果 三轴状态曲线对比: 三轴误差曲线对比: 误差统计特性输出(命令行截图&…...
【从零开始入门unity游戏开发之——C#篇04】栈(Stack)和堆(Heap),值类型和引用类型,以及特殊的引用类型string,垃圾回收( GC)
文章目录 知识回顾一、栈(Stack)和堆(Heap)1、什么是栈和堆2、为什么要分栈和堆3、栈和堆的区别栈堆 4、总结 二、值类型和引用类型1、那么值类型和引用类型到底有什么区别呢?值类型引用类型 2、总结 三、特殊的引用类…...
人员离岗监测摄像机智能人员睡岗、逃岗监测 Python 语言结合 OpenCV
在安全生产领域,人员的在岗状态直接关系到生产流程的顺利进行和工作环境的安全稳定。人员离岗监测摄像机的出现,为智能人员睡岗、逃岗监测提供了高效精准的解决方案,而其中的核心技术如AI识别睡岗脱岗以及相关的算法盒子和常见的安全生产AI算…...
Linux-ubuntu点LED灯C语言版
一,C语言点灯 1.寄存器配置 设置为SVC模式,复用寄存器设置GPIO1-IO003,设置电气属性,设置为输出模式。 2.软件 汇编语言对模式设置,并且将堆栈指针指向主程序: .global _start_start: /*设置为svr模式 */mrs …...
第P3周:Pytorch实现天气识别
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 目标 读取天气图片,按文件夹分类搭建CNN网络,保存网络模型并加载模型使用保存的模型预测真实天气 具体实现 (一…...
代理IP与生成式AI:携手共创未来
目录 代理IP:网络世界的“隐形斗篷” 1. 隐藏真实IP,保护隐私 2. 突破网络限制,访问更多资源 生成式AI:创意与效率的“超级大脑” 1. 提高创作效率 2. 个性化定制 代理IP与生成式AI的协同作用 1. 网络安全 2. 内容创作与…...
函数式编程
Lambda表达式 1、什么时候可以使用Lambda表达式呢? 一般都是在简化匿名内部类,当这个函数是一个接口,并且接口中只要一个方法时,就可以使用Lambda表达式 2、格式 (参数列表)->{方法体} 其中形参也不需要传,只需要传实参 只关注参数列表和方法体不关注方法啥的东西…...
抖音SEO短视频矩阵源码系统开发分享
在数字营销的前沿阵地,抖音短视频平台凭借其独特的魅力和庞大的用户基础,已成为社交媒体领域一股不可小觑的力量。随着平台影响力的持续扩大,如何有效提升视频内容的可见度与流量成为了内容创作者关注的焦点。在此背景下,一套专为…...
常见的锁与线程安全
目录 一、STL,智能指针和线程安全 STL中的容器是否是线程安全的? 智能指针是否是线程安全的? 二、其他常见的各种锁 三、自旋锁 四、读者写者问题 读写锁接口 读者优先伪代码 一、STL,智能指针和线程安全 STL中的容器是否是线程安全的? 不是 . 原因是 , STL 的设…...
java中的List、数组和set
在Java中,List、数组(Array)和Set 是三种常用的数据结构,它们各自有不同的特性、用途和实现方式。下面我们将深入探讨这三者的特点、区别以及它们在 Java 中的常见使用场景。 1. 数组(Array) 特性&#x…...
NLP-Huggingface基本使用方法
NLP的网络结构大同小异,只不过训练策略可能会不同。因为与图像cv不同,文本训练数据非常的多,cv可以使用10几张就可以获得特征向量,而文本做不到学几句话就能让计算机听得懂话。因此,我们都需要使用预训练模型ÿ…...
Liquibase结合SpringBoot使用实现数据库管理
Liquibase概述 Liquibase 是一个开源的数据库变更管理工具,用于跟踪、版本化、和管理数据库结构(如表、字段、索引等)的变更。它的目的是使数据库变更的过程更加透明、可控制、自动化,避免开发团队在多个环境中手动执行相同的数据…...
高防CDN 如何防止DDoS和CC攻击?
在数字化时代,网络安全威胁日益严重,尤其是DDoS(分布式拒绝服务)攻击和CC(Challenge Collapsar)攻击,已成为企业网站和网络服务最常见且最具破坏力的攻击手段。为了有效抵御这些攻击,…...
15.初始接口1.0 C#
这是一个用于实验接口的代码 适合初认识接口的人 【CSDN开头介绍】(文心一言AI生成) 在C#编程世界中,接口(Interface)扮演着至关重要的角色,它定义了一组方法,但不提供这些方法的实现。接口作为…...
数据结构day5:单向循环链表 代码作业
一、loopLink.h #ifndef __LOOPLINK_H__ #define __LOOPLINK_H__#include <stdio.h> #include <stdlib.h>typedef int DataType;typedef struct node {union{int len;DataType data;};struct node* next; }loopLink, *loopLinkPtr;//创建 loopLinkPtr create();//…...
利用CNN与多尺度特征、注意力机制的融合实现低分辨率人脸表情识别,并给出模型介绍与代码实现
大家好,我是微学AI,今天给大家介绍一下利用CNN与多尺度特征、注意力机制的融合实现低分辨率人脸表情识别,并给出模型介绍与代码实现。在当今社会,人脸识别技术已广泛应用,但特定场景下的低质量图像仍是一大挑战。 低分…...
spring RestTemplate使用说明
rest-template是spring对httpclient的逻辑封装,它底层还是基于httpclient,所以一些配置其实跟httpclient是强相关的。 基本配置 rest-template可以不带参数,使用默认配置,也可以指定ClientHttpRequestFactory参数,Cl…...
设置HP条UI
概述 设置常见的生命值条, 实现过程 设置UI/image作为形状 设置UI/Image作为背景 设置UI/image(healthfill)作为填充图片,层数低于背景 设置heathfill的imagetype为filled fillmethod为horizontal [SerializeField] private Im…...
常见排序算法总结 (五) - 堆排序与堆操作
堆排序(借助 API) 算法思想 利用堆能够维护数组中最大值的性质,根据数组元素建立最大堆,依次弹出元素并维护堆结构,直到堆为空。 稳定性分析 堆排序是不稳定的,因为堆本质上是完全二叉树,排…...
Linux 本地编译安装 gcc9
这里演示非sudo权限的本地linux 用户安装 gcc9 下载源代码: 可以从GCC官方网站或其镜像站点下载GCC 9的源代码压缩包。使用wget或curl命令,这通常不需要额外权限 wget https://ftp.gnu.org/gnu/gcc/gcc-9.5.0/gcc-9.5.0.tar.gz tar -xf gcc-9.5.0.tar…...
开源FreeSWITCH大模型智能客服系统的最佳实践
开源 FreeSWITCH 大模型智能客服系统的最佳实践 原作者:开源呼叫中心FreeIPCC,其Github:https://github.com/lihaiya/freeipcc 引言 开源 FreeSWITCH 大模型智能客服系统因其灵活性、成本效益和技术先进性,成为众多企业提升客户…...
大数据技术与应用——数据可视化(山东省大数据职称考试)
大数据分析应用-初级 第一部分 基础知识 一、大数据法律法规、政策文件、相关标准 二、计算机基础知识 三、信息化基础知识 四、密码学 五、大数据安全 六、数据库系统 七、数据仓库. 第二部分 专业知识 一、大数据技术与应用 二、大数据分析模型 三、数据科学 数据可视化 大…...
大数据之Hbase环境安装
Hbase软件版本下载地址: http://mirror.bit.edu.cn/apache/hbase/ 1. 集群环境 Master 172.16.11.97 Slave1 172.16.11.98 Slave2 172.16.11.99 2. 下载软件包 #Master wget http://archive.apache.org/dist/hbase/0.98.24/hbase-0.98.24-hadoop1-bin.tar.gz…...
Node.js day-01
01.Node.js 讲解 什么是 Node.js,有什么用,为何能独立执行 JS 代码,演示安装和执行 JS 文件内代码 Node.js 是一个独立的 JavaScript 运行环境,能独立执行 JS 代码,因为这个特点,它可以用来编写服务器后端…...
OpenCV相机标定与3D重建(25)计算两个三维点集之间的最优仿射变换矩阵(3x4)函数estimateAffine3D()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 计算两个3D点集之间的最优仿射变换。 它计算 [ x y z ] [ a 11 a 12 a 13 a 21 a 22 a 23 a 31 a 32 a 33 ] [ X Y Z ] [ b 1 b 2 b 3 ] \beg…...
SQL 中 INNER JOIN 和 LEFT JOIN 的区别和用法
在数据库语言 SQL 中,连接 (也称进行表结合操作)是一种常见的操作,用于将多个数据表格核实关联进行查询。常见的连接类型中, INNER JOIN 和 LEFT JOIN 是最基本且最常用的。下面将给出完整的区别和用法说明。 1. 基本概念 INNER JOIN (内连…...
【计算机网络】lab2 Ethernet(链路层Ethernet frame结构细节)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀各种软件安装与配置_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. …...
2024 年 MySQL 8.0.40 安装配置、Workbench汉化教程最简易(保姆级)
首先到官网上下载安装包:http://www.mysql.com 点击下载,拉到最下面,点击社区版下载 windows用户点击下面适用于windows的安装程序 点击下载,网络条件好可以点第一个,怕下着下着断了点第二个离线下载 双击下载好的安装…...
提升PHP技能:18个实用高级特性
掌握PHP基础知识只是第一步。 深入了解这18个强大的PHP特性,将显著提升您的开发效率和代码质量。 1、超越 __construct() 的魔法方法 虽然 __construct() 为大多数开发者所熟知,PHP 却提供了更多强大的魔术方法,例如: class Da…...
QT数据库(三):QSqlQuery使用
QSqlQuery 简介 QSqlQuery 是能运行任何 SQL 语句的类,如 SELECT、INSERT、UPDATE、DELETE 等 SQL 语句。所以使用 QSqlQuery 几乎能进行任何操作,例如创建数据表、修改数据表的字段定义、进行数据统计等。如果运行的是 SELECT 语句,它查询…...