Springboot使用webupload大文件分片上传(包含前后端源码)

Springboot使用webupload大文件分片上传(包含源码)

  • 1. 实现效果
    • 1.1 分片上传效果图
    • 1.2 分片上传技术介绍
  • 2. 分片上传前端实现
    • 2.1 什么是WebUploader?
      • 功能特点
      • 接口说明
      • 事件API
      • Hook 机制
    • 2.2 前端代码实现
      • 2.2.1(不推荐)使用官方压缩文件方式引入
      • 2.2.2 (推荐)模块引入
      • 2.2.3 核心代码
      • 2.2.4 项目结构和运行效果
    • 2.3 分片上传后端实现
      • 2.3.1 项目结构和技术介绍
      • 2.3.2 核心代码
  • 3. 项目运行测试
  • 4. 技术选型考量
  • 5. 项目源码
  • 参考链接

1. 实现效果

1.1 分片上传效果图

如下上传过程的效果图,可以看到文件上传进度和浏览器控制台中打印的请求信息

效果图描述如下:

  1. 选择文件:这里我选择需要上传了1.09GB的pdf大文件
  2. 分片上传: 文件被切分为多个小片段(分片),每个分片独立上传,以提高上传效率和稳定性
  3. 进度条显示: 上传过程中显示文件上传进度条,实时反映上传进度
  4. 请求日志: 浏览器控制台打印每个分片上传的 HTTP 请求详情,包括请求头、请求体和服务器响应信息

录制_2024_06_08_19_19_36_376

1.2 分片上传技术介绍

本文使用技术栈:springboot、vue、webupload、mysql等

在项目开发中需要上传一个非常大的文件时,单次上传整个文件往往会遇到网络不稳定、带宽限制、上传失败等问题。为了解决这些问题,文件分片上传(也称为断点续传)应运而生。分片上传的核心思想是将一个大文件分成若干份大小相等的多个小块数据块(我们称之为 Part),等所有小块文件上传成功后,再将文件进行合并成完整的原始文件

文件分片上传的优点主要有以下几点:

  1. 断点续传:在网络中断或其他错误导致上传失败时,只需重新上传失败的部分,而不必从头开始上传整个文件,从而提高上传的可靠性和效率。
  2. 降低网络压力:分片上传可以控制每个片段的大小,避免一次性传输大量数据导致的网络拥堵,提高网络资源的利用率。
  3. 并行上传:多个分片可以同时上传,加快整体上传速度。
  4. 灵活处理:服务器可以更灵活地处理和存储文件分片,减少内存和带宽的占用。

本文使用 WebUploader 实现文件的分片上传。WebUploader 是一个由百度开发的强大而灵活的文件上传工具,支持文件分片上传、断点续传等功能。本文详细讲解并实现 WebUploader 的安装与配置,如何实现文件分片上传,以及如何在服务器端合并文件分片。通过这篇博客,你将学会:安装和配置 WebUploader实现文件分片上传

2. 分片上传前端实现

技术栈或技术点:vue、webuploader、elmentui

2.1 什么是WebUploader?

WebUploader 是由百度公司开发的一个现代文件上传组件,主要基于 HTML5,同时辅以 Flash 技术。它支持大文件的分片上传,提高了上传效率,并且兼容主流浏览器。

官网地址: [Web Uploader - Web Uploader (fex-team.github.io)](http://fex.baidu.com/webuploader/)

image-20240608212651303

功能特点

  1. 分片、并发上传: WebUploader 支持将大文件分割成小片段并行上传,极大地提高了上传效率。
  2. 预览、压缩: 支持常用图片格式(如 jpg、jpeg、gif、bmp、png)的预览和压缩,节省了网络传输数据量。
  3. 多途径添加文件: 支持文件多选、类型过滤、拖拽(文件和文件夹)以及图片粘贴功能。
  4. HTML5 & FLASH: 兼容所有主流浏览器,接口一致,不需要担心内部实现细节。
  5. MD5 秒传: 通过 MD5 值验证,避免重复上传相同文件。
  6. 易扩展、可拆分: 采用模块化设计,各功能独立成小组件,可自由组合搭配。

接口说明

WebUploader 提供了丰富的接口和钩子函数,以下是几个关键的接口:

  • before-send-file: 在文件发送之前执行。
  • before-file: 在文件分片后、上传之前执行。
  • after-send-file: 在所有文件分片上传完毕且无错误时执行。

WebUploader 的所有代码都在一个闭包中,对外只暴露了一个变量 WebUploader,避免与其他框架冲突。所有内部类和功能都通过 WebUploader 命名空间进行访问。

事件API

Uploader 实例拥有类似 Backbone 的事件 API,可以通过 onoffoncetrigger 进行事件绑定和触发。

uploader.on('fileQueued', function(file) {// 处理文件加入队列的事件
});this.uploader.on('uploadSuccess', (file, response) => {// 上传成功事件
});

除了通过 on 绑定事件外,还可以直接在 Uploader 实例上添加事件处理函数:

uploader.onFileQueued = function(file) {// 处理文件加入队列的事件
};

Hook 机制

关于hook机制的个人理解:Hook机制就像是在程序中的特定事件或时刻(比如做地锅鸡的时候)设定一些“钩子”。当这些事件发生时,程序会去“钩子”上找有没有要执行的额外功能,然后把这些功能执行一下。这就好比在做地锅鸡的过程中,你可以在某个步骤(比如炖鸡的时候)加上自己的调料或额外的配菜,来调整和丰富最终的味道,而不需要改动整体的食谱。

Uploader 内部功能被拆分成多个小组件,通过命令机制进行通信。例如,当用户选择文件后,filepicker 组件会发送一个添加文件的请求,负责队列的组件会根据配置项处理文件并决定是否加入队列。

webUploader.Uploader.register({'before-send-file': 'beforeSendFile','before-send': 'beforeSend','after-send-file': 'afterSendFile'},{// 时间点1:所有分块进行上传之前调用此函数beforeSendFile: function(file) {// 利用 md5File() 方法计算文件的唯一标记符// 创建一个 deferred 对象var deferred = webUploader.Deferred();// 计算文件的唯一标记,用于断点续传和秒传// 请求后台检查文件是否已存在,实现秒传功能return deferred.promise();},// 时间点2:如果有分块上传,则每个分块上传之前调用此函数beforeSend: function(block) {// 向后台发送当前文件的唯一标记// 请求后台检查当前分块是否已存在,实现断点续传功能var deferred = webUploader.Deferred();return deferred.promise();},// 时间点3:所有分块上传成功之后调用此函数afterSendFile: function(file) {// 前台通知后台合并文件// 请求后台合并所有分块文件}}
);

2.2 前端代码实现

2.2.1(不推荐)使用官方压缩文件方式引入

首先我们需要下载官方文件,下载地址:Releases · fex-team/webuploader (github.com)

实现方式:快速开始 - Web Uploader (fex-team.github.io)

image-20240608213613872

下载文件webuploader-0.1.5.zip并解压后的文件内容如下:

image-20240608214247152

2.2.2 (推荐)模块引入

在已有项目或者新的空vue项目中先执行下列命令

# 引入分片需要
npm install webuploader
npm install jquery@1.12.4

image-20240608223139745

image-20240608223551207

2.2.3 核心代码

WebUpload.vue

<template><div><div class="container"><div class="handle-box"><el-button type="primary" id="picker" style="padding: 0px 14px" icon="el-icon-upload2">选择文件</el-button></div><el-table :data="internalFileListData" style="width: 100%"><el-table-column prop="fileName" label="文件名称"  align="center"></el-table-column><el-table-column prop="fileSize" align="center" label="文件大小" width="150"></el-table-column><el-table-column label="进度" align="center" width="300"><template slot-scope="scope"><div class="progress-container"><el-progress :text-inside="true" :stroke-width="15" :percentage="scope.row.percentage"></el-progress></div></template></el-table-column><el-table-column prop="speed" label="上传速度" align="center" width="150"><template slot-scope="scope"><div>{{ scope.row.speed }}</div></template></el-table-column><el-table-column label="操作" width="150" align="center"><template slot-scope="scope"><el-button type="text" icon="el-icon-close" class="red" @click="removeRow(scope.$index, scope.row)">移除</el-button></template></el-table-column></el-table></div></div>
</template><script>
import webUploader from 'webuploader' // 引入WebUploader库export default {name: 'WebFileUpload',props: {headers: {type: String,default: ''},fileNumLimit: {type: Number,default: 100},fileSize: {type: Number,default: 1 * 1024 * 1024 * 1024 * 1024 // 1gb},chunkSize: {type: Number,default: 5 * 1024 * 1024 // 5mb},uploadSuffixUrl: {type: String,default: 'http://localhost:5590'},multiple: {type: Boolean,default: false // 是否支持多文件上传},options: {type: Object,default: () => ({fileType: 'doc,docx,pdf,xls,xlsx,jpg,jpeg,png,mp4,avi', // 允许上传的文件类型fileUploadUrl: '/v1/upload/zone/zoneUploadSE', // 分片上传接口headers: {}})},fileListData: {type: Array,default: () => []}},data() {return {uploader: null,percentage: 0, // 上传进度internalFileListData: [], // 使用内部数据属性来保存文件列表数据uploadStatus: '', // 上传状态fList: [],fileTimestamps: {} // 用于存储每个文件的时间戳}},watch: {fileListData(newValue) {// 当parentData变化时,执行相应逻辑this.internalFileListData = newValueconsole.log(this.internalFileListData)}},mounted() {this.internalFileListData = [...this.fileListData]this.initUploader()this.initEvents()},methods: {/*** 初始化上传组件*/initUploader() {this.uploader = webUploader.create({auto: true, // 选完文件后,是否自动上传。resize: false, // 不压缩imageswf: '../../../assets/Uploader.swf', // swf文件路径server: this.uploadSuffixUrl + this.options.fileUploadUrl, // 默认文件接收服务端。pick: {id: '#picker', // 上传按钮multiple: this.multiple // 是否开启文件多选,},accept: [{title: 'file',extensions: this.options.fileType,mimeTypes: this.buildFileType(this.options.fileType)}],// 单位字节,如果图片大小小于此值,不会采用压缩。512k  512*1024,如果设置为0,原图尺寸大于设置的尺寸就会压缩;如果大于0,只有在原图尺寸大于设置的尺寸,并且图片大小大于此值,才会压缩compressSize: 0,fileNumLimit: this.fileNumLimit, //验证文件总数量, 超出则不允许加入队列,默认值:undefined,如果不配置,则不限制数量fileSizeLimit: 2 * 1024 * 1024 * 1024 * 1024, // 1kb=1024*1024,验证文件总大小是否超出限制, 超出则不允许加入队列。fileSingleSizeLimit: this.fileSize, //单个文件大小是否超出限制, 超出则不允许加入队列。chunkSize: this.chunkSize, // 单个分片大小为5MB,1024 * 1024 * 5表示5MBchunked: true, //是否开启分片上传threads: 8, // 并发上传数chunkRetry: 8, // 网络错误重试次数prepareNextFile: false, //在上传当前文件时是否准备好下一个文件// 上传时添加的请求头,例如需要传送token等// headers: {//   Authorization: 'Bearer ' + getToken()// }})},initEvents() {// 文件添加到队列this.uploader.on('fileQueued', file => {if (!this.multiple) {// 清空现有文件列表,实现只上传单个文件this.internalFileListData = []}// 生成唯一的时间戳并存储在 fileTimestamps 对象中const timestamp = Date.now().toString()this.fileTimestamps[file.id] = timestampconst fileSize = this.formatFileSize(file.size)this.internalFileListData.push({fileId: file.id,fileName: file.name,fileSize: fileSize,percentage: 0, // 初始化进度为0speed: '0KB/s', // 初始化速度state: '就绪'})this.uploadToServer() // 选择文件后直接开始上传})/*** 监听上传成功事件* @param file: 文件对象* @param : 服务器返回的数据*/this.uploader.on('uploadSuccess', (file, response) => {this.fList = []// 如果code等于30000,表示上传成功if (response.code === 30000) {response.data.fileName = response.data.originalNameresponse.data.percentage = this.internalFileListData[0].percentageresponse.data.fileSize = this.internalFileListData[0].fileSizeresponse.data.speed = this.internalFileListData[0].speedthis.fList.push(response.data)this.$emit('getFileList', this.fList)this.$message.success('上传完成')} else {this.$message.error('上传失败')}})/*** 监听上传错误事件* @param file: 文件对象* @param : 服务器返回的数据*/this.uploader.on('uploadError', () => {this.$message.error('上传出错')})// 监听上传进度this.uploader.on('uploadProgress', (file, percentage) => {// 找到对应文件并更新进度let targetFile = this.internalFileListData.find(item => item.fileId === file.id)if (targetFile) {const currentTime = new Date().getTime()const elapsedTime = (currentTime - (targetFile.startTime || currentTime)) / 1000 // 秒const uploadedSize = percentage * file.sizeconst speed = this.formatFileSize(uploadedSize / elapsedTime) + '/s'targetFile.percentage = parseFloat((percentage * 100).toFixed(2))targetFile.speed = speedtargetFile.startTime = targetFile.startTime || currentTime}})// 上传之前发送的数据this.uploader.on('uploadBeforeSend', (block, data, headers) => {const fileTimestamp = this.fileTimestamps[block.file.id]data.fileMd5 = block.file.fileMd5data.contentType = block.file.typedata.chunks = block.file.chunksdata.zoneTotalMd5 = block.file.fileMd5data.zoneMd5 = block.zoneMd5data.zoneTotalCount = block.chunksdata.zoneNowIndex = block.chunkdata.zoneTotalSize = block.totaldata.zoneStartSize = block.startdata.zoneEndSize = block.enddata.fileUUID = fileTimestampheaders.Authorization = this.options.headers.Authorization})// 所有文件上传完成this.uploader.on('uploadFinished', () => {this.uploadBtnDisabled = falsethis.uploadStatus = 'el-icon-upload'// this.$message.success('文件上传完毕')})// 错误信息监听this.uploader.on('error', handler => {let errorMessage = ''if (handler === 'F_EXCEED_SIZE') {errorMessage ='上传的单个文件太大! 最大支持' +this.formatFileSize(this.fileSize) +'! 操作无法进行, 如有需求请联系管理员'} else if (handler === 'Q_TYPE_DENIED') {errorMessage = '不允许上传此类文件! 操作无法进行, 如有需求请联系管理员'}if (errorMessage) {this.$message.error({showClose: true,message: errorMessage})}})},uploadToServer() {if (this.internalFileListData.length <= 0) {this.$message.error({showClose: true,message: '没有上传的文件'})return}this.uploadBtnDisabled = truethis.uploadStatus = 'el-icon-loading'this.uploader.upload()},/*** 格式化文件大小* @param {Number} size 文件大小* @return {String} 格式化后的文件大小*/formatFileSize(size) {const units = ['KB', 'MB', 'GB']let unitIndex = -1do {size /= 1024unitIndex++} while (size >= 1024 && unitIndex < units.length - 1)return size.toFixed(2) + units[unitIndex]},/*** 构建文件类型字符串,以便在文件选择对话框中使用* @param {string} fileType - 用逗号分隔的文件扩展名字符串,例如 "jpg,png,gif"* @return {string} - 以逗号分隔的文件类型字符串,每个扩展名前加一个点,例如 ".jpg,.png,.gif"*/buildFileType(fileType) {const fileTypes = fileType.split(',')return fileTypes.map(type => `.${type}`).join(',')},/*** 操作中的移除* @param {Number} index - 文件列表索引* @param {Object} row - 文件对象*/removeRow(index, row) {this.internalFileListData.splice(index, 1)const files = this.uploader.getFiles()for (let i = 0; i < files.length; i++) {if (files[i].id === row.fileId) {this.uploader.removeFile(files[i], true)break}}this.$emit('removeRow', index)}}
}
</script><style>
.container {margin-left: 50px;width: 100%;padding: 30px;background: #fff;border: 1px solid #ddd;border-radius: 5px;
}.handle-box {margin-bottom: 20px;
}#picker div:nth-child(2) {width: 100% !important;height: 100% !important;
}.webuploader-container {position: relative;
}.webuploader-element-invisible {position: absolute !important;clip: rect(1px, 1px, 1px, 1px);
}.webuploader-pick {line-height: 39px;margin-right: 20px;
}.webuploader-pick-hover {background: #409eff;
}.progress-container {width: 200px; /* 设置进度条容器的宽度 */margin: 0 auto;
}
</style>

App.vue

<template><div id="app"><main><el-form :span="20"><el-col :span="20"><el-form-item><!-- 分片上传组件 --><WebUpload></WebUpload></el-form-item></el-col></el-form></main></div>
</template><script>
import WebUpload from './components/WebUpload.vue'export default {name: 'App',components: {WebUpload}
}
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

同时使用了样式,因此需要引入element-ui

npm install element-ui -S# main.js中内容
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';Vue.use(ElementUI);new Vue({el: '#app',render: h => h(App)
});

2.2.4 项目结构和运行效果

执行npm run sever运行后页面效果和最终项目代码结构

image-20240609150500553

2.3 分片上传后端实现

2.3.1 项目结构和技术介绍

后端使用技术栈主要是springboot,引入了mybatis-plus,数据库使用mysql

image-20240609151213829

2.3.2 核心代码

控制类:FileUploadController.java

package com.example.zhou.controller;import com.example.zhou.common.Result;
import com.example.zhou.service.IFileZoneRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;/*** @author ZhouQuan* @desciption 文件上传操作录控制类* @date 2024/5/4 17:09*/
@Slf4j
@RestController
@RequestMapping("/v1/upload/zone")
public class FileUploadController {@Resourceprivate IFileZoneRecordService iFileZoneRecordService;/*** 单个大文件分片上传-不使用md5** @param file           分片的文件* @param zoneTotalCount 分片总数* @param zoneTotalSize  文件总大小* @param zoneNowIndex   当前分片编号* @param fileUUID       每个文件上传时文件唯一标识* @return code: 30000 文件上传成功* @return code: 30002 分片上传成功*/@PostMapping("/zoneUploadSE")public Result zoneUploadSE(MultipartFile file,Integer zoneNowIndex,Integer zoneTotalCount,Integer zoneTotalSize,String fileUUID) {return iFileZoneRecordService.zoneUploadSE(file, zoneNowIndex, zoneTotalCount, zoneTotalSize, fileUUID);}
}

核心实现方法:FileZoneRecordServiceImpl.java

package com.example.zhou.service.impl;import com.example.zhou.common.Result;
import com.example.zhou.common.ResultCode;
import com.example.zhou.config.FileUploadConfig;
import com.example.zhou.entity.Archive;
import com.example.zhou.mapper.ArchiveMapper;
import com.example.zhou.service.IFileZoneRecordService;
import com.example.zhou.utils.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;@Slf4j
@Service
public class FileZoneRecordServiceImpl  implements IFileZoneRecordService {@Resourceprivate ArchiveMapper archiveMapper;@Resourceprivate FileUploadConfig fileUploadConfig;public Result zoneUploadSE(MultipartFile multipartFile,Integer currentChunk,Integer zoneTotalCount,Integer zoneTotalSize,String fileUUID) {try {// 获取上传文件的原始文件名和扩展名String originalName = multipartFile.getOriginalFilename();String extension = FilenameUtils.getExtension(originalName);// 构建上传路径String uploadPath = Paths.get(fileUploadConfig.getUploadFolder(), extension).toString();FileUtils.forceMkdir(new File(uploadPath)); // 创建目录(如果不存在)// 写入临时文件String tempFileName = (currentChunk != null) ? currentChunk + "_" + fileUUID + "_" + originalName :fileUUID + "_" + originalName;File tempFile = new File(uploadPath, tempFileName);multipartFile.transferTo(tempFile);// 如果是最后一个分片或者只有一个分片,进行合并操作if (currentChunk == null || (currentChunk == zoneTotalCount - 1)) {// 获取最终文件路径String finalFileName = fileUUID + "_" + originalName;File finalFile = new File(uploadPath, finalFileName);// 合并分片文件mergeChunkFiles(uploadPath, fileUUID, originalName, zoneTotalCount, finalFile);// 移动文件到指定目录 示例:pdf/2024/24/uuid.pdfPath filePath = Paths.get(extension,  DateFormatUtils.format(new Date(), "yyyy/MM/dd"),IdUtils.fastUUID() + "." + extension);// 移动文件位置到指定文件夹下FileUtils.moveFile(finalFile,new File(Paths.get(fileUploadConfig.getUploadFolder(), filePath.toString()).toString()));// 保存附件信息到数据库Archive archive = new Archive();archive.setSid(IdUtils.fastUUID());archive.setFileName(filePath.getFileName().toString());archive.setOriginalName(originalName);archive.setPath(filePath.toString());archive.setSize(zoneTotalSize != null ? zoneTotalSize : (int) tempFile.length());archive.setFileType(extension);// 插入数据库int result = archiveMapper.insert(archive);return new Result(ResultCode.FILEUPLOADED, archive);}} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}return new Result(ResultCode.ZONEUPLOADED, "分片上传成功");}private void mergeChunkFiles(String uploadPath, String fileUUID, String fileName, Integer zoneTotalCount,File finalFile) throws IOException {long start = System.currentTimeMillis();try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(finalFile))) {for (int i = 0; i < zoneTotalCount; i++) {File chunkFile = new File(uploadPath, i + "_" + fileUUID + "_" + fileName);while (!chunkFile.exists()) {try {Thread.sleep(100); // 休眠100毫秒后重新判断} catch (InterruptedException e) {throw new RuntimeException(e);}}log.info("正在合并分片文件:" + chunkFile.getName());// 读入分片数据并写入最终文件try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(chunkFile))) {byte[] buffer = new byte[8192]; // 8KB缓冲区int bytesRead;while ((bytesRead = bis.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}}// 删除已合并的分片文件long deleteStart = System.currentTimeMillis();if (!chunkFile.delete()) {log.warn("删除分片文件失败:" + chunkFile.getName());} else {log.info("删除分片耗时:" + (System.currentTimeMillis() - deleteStart) + "毫秒");}}}long end = System.currentTimeMillis();log.info("文件合并完成,耗时:" + (end - start) + "毫秒");}
}

3. 项目运行测试

测试效果如下:

录制_2024_06_09_15_32_22_335

后端返回结果中会返回文件信息给前端,可根据业务存储文件sid或者是路径信息

image-20240609153437119

4. 技术选型考量

本文主要是使用了分片上传,其实并未使用计算文件md5来实现断点续传和文件秒传,主要考量如下:

  • MD5 性能开销大且校验耗时:

    计算大文件的 MD5 哈希值是一个耗时的操作,特别是对于数 GB 的大文件。这个过程会占用大量的 CPU 资源,并增加上传前的等待时间,从而降低用户体验。

  • 实现复杂度增加:

    引入 MD5 校验需要在客户端和服务器端进行额外的处理逻辑,包括计算文件的 MD5 值、校验分片的完整性等。这会增加开发和维护的复杂度。

  • 实际应用场景需求:

    • 在某些应用场景中,断点续传和秒传功能并不是必需的。比如用户可以在一次会话中完成大文件上传,或者文件上传失败的概率较低时,不使用 MD5 校验也能满足需求。

基于以上考虑选择了更为简洁和高效的实现方案,不使用 MD5 校验。这种方案可以显著减少上传前的准备时间和计算开销,简化系统的实现和维护,同时在大多数情况下也能满足实际需求。

5. 项目源码

image-20240609155255125

https://zhouquanquan.lanzn.com/b00g2crzsh
密码:h5iu

参考链接

  1. 官方地址 https://github.com/fex-team/webuploader

  2. 基于SpringBoot和WebUploader实现大文件分块上传.断点续传.秒传-阿里云开发者社区 (aliyun.com)

  3. 在Vue项目中使用WebUploader实现文件上传_vue webuploader-CSDN博客

  4. vue中大文件上传webuploader前端用法_vue webuploader 大文件上传-CSDN博客

  5. SpringBoot实现大文件上传/下载(分片、断点续传) - helloliyh - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/25239.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理之计算机系统层次结构

目录 计算机系统层次结构 复习提示 1.计算机系统的组成 2.计算机硬件 2.1冯诺依曼机基本思想 2.1.1冯诺依曼计算机的特点 2.2计算机的功能部件 2.2.1MAR 和 MDR 位数的概念和计算 3.计算机软件 3.1系统软件和应用软件 3.2三个级别的语言 3.2.1三种机器语言的特点 3…

★pwn 24.04环境搭建保姆级教程★

★pwn 24.04环境搭建保姆级教程★ &#x1f338;前言&#x1f33a;Ubuntu 24.04虚拟机&#x1f337;VM&#x1f337;Ubuntu 24.04镜像 &#x1f33a;工具&#x1f337;可能出现的git clone错误&#x1f337;复制粘贴问题&#x1f337;攻击&#x1f337;编题 &#x1f33a;美化&…

【AI大模型】Transformers大模型库(五):AutoModel、Model Head及查看模型结构

目录​​​​​​​ 一、引言 二、自动模型类&#xff08;AutoModel&#xff09; 2.1 概述 2.2 Model Head&#xff08;模型头&#xff09; 2.3 代码示例 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#xff0c;为huggingface上数以万计的预…

使用 Keras 的 Stable Diffusion 实现高性能文生图

前言 在本文中&#xff0c;我们将使用基于 KerasCV 实现的 [Stable Diffusion] 模型进行图像生成&#xff0c;这是由 stable.ai 开发的文本生成图像的多模态模型。 Stable Diffusion 是一种功能强大的开源的文本到图像生成模型。虽然市场上存在多种开源实现可以让用户根据文本…

【会议征稿,IEEE出版】第三届能源与电力系统国际学术会议 (ICEEPS 2024,7月14-16)

如今&#xff0c;全球能源行业正面临着前所未有的挑战。一方面&#xff0c;加快向清洁、可再生能源转型是遏制能源环境污染问题的最佳途径之一&#xff1b;另一方面&#xff0c;电力系统中新能源发电、人工智能技术、电力电子装备等被广泛应用和期待&#xff0c;以提高能源可持…

transformer - 注意力机制

Transformer 的注意力机制 Transformer 是一种用于自然语言处理任务的模型架构&#xff0c;依赖于注意力机制来实现高效的序列建模。注意力机制允许模型在处理一个位置的表示时&#xff0c;考虑输入序列中所有其他位置的信息&#xff0c;而不仅仅是前面的几个位置。这种机制能…

ATTCK红队评估(五)

环境搭建 靶场拓扑图&#xff1a; 靶机下载地址: 漏洞详情 外网信息收集 确定目标靶机地址&#xff1a; 发现主机192.168.135.150主机是本次攻击的目标地址。探测靶机开放的端口信息&#xff1a; 目标靶机开放了两个端口&#xff1a;80、3306&#xff0c;那没什么意外的话就是…

每天壁纸不重样~下载必应每日图片

下载必应每日图片 必应不知道你用过没有你下载过必应的图片没有你又没搜索过桌面图片你是不是安装过桌面图片软件你是不是为找一个好看的图片下载过很多桌面软件 必应每日图片 必应每天都会有一张不同的风景图片&#xff0c;画质清晰&#xff0c;而且不收费可以下载使用 但…

重生之我要精通JAVA--第八周笔记

文章目录 多线程线程的状态线程池自定义线程池最大并行数多线程小练习 网络编程BS架构优缺点CS架构优缺点三要素IP特殊IP常用的CMD命令 InetAddress类端口号协议UDP协议&#xff08;重点&#xff09;UDP三种通信方式 TCP协议&#xff08;重点&#xff09;三次握手四次挥手 反射…

sqlmap直接嗦 dnslog注入 sqllibs第8关

dnslog注入是解决注入的时候没有回显的情况&#xff0c;通过dns外带来进行得到我们想要的数据。 我们是用了dns解析的时候会留下记录&#xff0c;这时候就可以看见我们想要的内容。 这个时候我们还要了解unc路径以及一个函数load_file()以及concat来进行注入。看看我的笔记 unc…

sqli-labs 靶场 less-8、9、10 第八关到第十关详解:布尔注入,时间注入

SQLi-Labs是一个用于学习和练习SQL注入漏洞的开源应用程序。通过它&#xff0c;我们可以学习如何识别和利用不同类型的SQL注入漏洞&#xff0c;并了解如何修复和防范这些漏洞。Less 8 SQLI DUMB SERIES-8判断注入点 当输入id为1时正常显示&#xff1a; 加上单引号就报错了 …

零基础非科班也能掌握的C语言知识19 动态内存管理

动态内存管理 1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free 3.calloc和realloc3.1 calloc3.2realloc 4.常见的动态内存的错误4.1对NULL指针的解引用操作4.2对动态开辟空间的越界访问4.3对非动态内存开辟的空间free4.4使用free释放⼀块动态开辟内存的⼀部分4.5对同…

在Anaconda中安装keras-contrib库

文章目录 1. 有git2. 无git2.1 步骤12.2 步骤22.3 步骤3 1. 有git 如果环境里有git&#xff0c;直接运行以下命令&#xff1a; pip install githttps://www.github.com/farizrahman4u/keras-contrib.git2. 无git 2.1 步骤1 打开网址&#xff1a;https://github.com/keras-tea…

Vue3【十四】watchEffect自动监视多个数据实现,不用明确指出监视哪个数据

Vue3【十四】watchEffect自动监视多个数据实现&#xff0c;不用明确指出监视哪个数据 Vue3【十四】watchEffect自动监视多个数据实现&#xff0c;不用明确指出监视哪个数据 进入立即执行一次&#xff0c;并监视数据变化 案例截图 目录结构 代码 Person.vue <template>&…

Java----抽象类和接口

欢迎大家来这次博客-----抽象类和接口。 1.抽象类 1.1 抽象类概念 在Java中我们都是通过类来描述对象&#xff0c;但反过来并不是所有的类都是用来描述对象的。当一个类中没有足够的信息来描述一个具体对象&#xff0c;我们就将该类称为抽象类。 如上图中的Shape类&#xff…

通用Mapper基础学习

一、引入 二、快速入门 1.创建测试数据 2.搭建MyBatis+Spring 开发环境 3.集成Mapper 4.第一个操作 Mapper接口源码介绍: 创建测试类: 三、常见操作

统计信号处理基础 习题解答10-9

题目 某质检员的工作是监控制造出来的电阻阻值。为此他从一批电阻中选取一个并用一个欧姆表来测量它。他知道欧姆表质量较差&#xff0c;它给测量带来了误差&#xff0c;这个误差可以看成是一个的随机变量。为此&#xff0c;质检员取N个独立的测量。另外&#xff0c;他知道阻值…

FreeRTOS基础(十三):队列集

队列集&#xff08;Queue Set&#xff09;通常指的是一组队列&#xff0c;它们可以用于处理不同的任务或数据流。每个队列可以独立地处理自己的元素&#xff0c;但作为一个集群&#xff0c;它们可以协同工作来完成更复杂的任务。下面进行介绍。 目录 一、队列集简介 二、队列…

详解 Flink 的 ProcessFunction API

一、Flink 不同级别的 API Flink 拥有易于使用的不同级别分层 API 使得它是一个非常易于开发的框架最底层的 API 仅仅提供了有状态流处理&#xff0c;它将处理函数&#xff08;Process Function &#xff09;嵌入到了 DataStream API 中。底层处理函数&#xff08;Process Func…

HarmonyOS开发-鸿蒙UiAbility 组件间跳转

前言 随着春节假期结束各行各业复产复工&#xff0c;一年一度的春招也持续火热起来。最近&#xff0c;有招聘平台发布了《2024年春招市场行情周报&#xff08;第一期&#xff09;》。总体来说今年的就业市场还是人才饱和的状态&#xff0c;竞争会比较激烈。 但是&#xff0c;…