minio spring boot 秒传、分片上传、断点续传文件实现

此处后端使用的是前期封装的自定义starter,具体链接可参考:minio对象存储spring boot starter封装组件

最新版本更新日志查询:版本日志

这里主要针对前期封装的组件,做一个简单的应用,前端直传可查看之前的文章

秒传

秒传的逻辑比较简单,在前传上传之前,先获取到对应文件的md5,传给后端,后端校验md5是否已经存在,存在则直接提示上传成功,否则,发起文件上传请求

获取文件MD5,前端可以使用spark-md5或者crypto-js

<el-upload class="upload-demo" drag action="#" :http-request="sparkUploadHandle" :show-file-list="false">
	<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
	<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>

对应的处理逻辑

<script setup lang="ts">
import { ElMessage } from 'element-plus'
import SparkMD5 from 'spark-md5'
import { checkMd5 } from '@/api/file'
import { UploadFilled } from '@element-plus/icons-vue'

const sparkUploadHandle = (param: any) => {
	const file = param.file
	const fileReader = new FileReader()
	const Spark = new SparkMD5.ArrayBuffer()
	fileReader.readAsArrayBuffer(file)
	fileReader.onload = function (e) {
		Spark.append(e.target.result)
		const md5 = Spark.end()
		ElMessage.success('文件MD5:' + md5)
		//上传逻辑处理
		const data = {
			md5: md5,
			fileName: file.name
		}
		checkMd5(data).then(resp => {
			if (resp.code == 201) {
				ElMessage.success('秒传成功')
			} else {
				ElMessage.info('后台无数据,正在上传中....')
				//请求上传接口
			}
		})
	}
}
</script>

checkMd5就是请求后端接口,查看是否存在

分片上传

这里使用的是前端直传方式的分片上传,处理逻辑:前端根据指定的大小对文件进行分片,分片完成后,根据文件名和分片数量去请求后端,获得对应的分片上传地址集合,再根据返回的地址集合,进行前端直传,传完后,调用后端接口,合并分片

后端接口

后端拿到对应的文件名称,分片大小和文件类型,然后返回给前端对应的put直传地址集合,每个直传地址默认10分钟的有效期

getPresignedMultipartUploadUrls 一次返回所有分片预签名地址
getPresignedMultipartUploadUrlsByPartNumbers 返回指定分片预签名地址

    @Autowired
    private MinioService minioService;

    @GetMapping("/part-url")
    public RestResult<MultiPartUploadInfo> partUrl(@RequestParam String fileName,
                                                   @RequestParam int partSize, @RequestParam String contentType) throws MinioException {
        MultiPartUploadInfo uploadInfo = minioService.getPresignedMultipartUploadUrls("bucketName", fileName, partSize, contentType);
        return RestResult.ok(uploadInfo);
    }

    @GetMapping("/merge-part")
    public RestResult<String> mergePart(@RequestParam String fileName, @RequestParam String uploadId) throws MinioException {
        String merge = minioService.mergeMultiPartUpload("bucketName", fileName, uploadId);
        return RestResult.ok(merge);
    }

前端

<el-upload class="upload-demo" drag action="#" :http-request="partUploadHandle" :show-file-list="false">
	<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
	<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
import { reactive } from 'vue'
import service from '@/utils/request'
import { getPartUrl, mergePart } from '@/api/file'
import { UploadFilled } from '@element-plus/icons-vue'

const state = reactive({
	uploadId: ''
})
//分片大小
const chunkSize = 50 * 1024 * 1024

const partUploadHandle = (param: any) => {
	let file = param.file
	// 正在创建分片
	let fileChunks = createFileChunk(file)

	let data = {
		fileName: file.name,
		partSize: fileChunks.length,
		contentType: file.type
	}
	//获得上传的url
	getPartUrl(data).then(resp => {
		state.uploadId = resp.data.uploadId
		let uploadUrls = resp.data.uploadUrls
		if (fileChunks.length !== uploadUrls.length) {
			ElMessage.error('文件分片上传地址获取错误')
			return
		}
		let chunkList = []
		fileChunks.map((chunkItem, index) => {
			chunkList.push({
				chunkNumber: index + 1,
				chunk: chunkItem,
				uploadUrl: uploadUrls[index],
				progress: 0,
				status: '—'
			})
		})
		//上传分片
		uploadChunkBase(chunkList, file.type).then(resp => {
			console.log('分片上传完成')
			let par = {
				fileName: file.name,
				uploadId: state.uploadId
			}
			//请求后端合并分片
			mergePart(par).then(resp => {
				ElMessage.info('上传成功,访问地址:' + resp.data)
			})
		})
	})
}

/**
 * 文件分片
 */
const createFileChunk = (file, size = chunkSize) => {
	const fileChunkList = []
	let count = 0
	while (count < file.size) {
		fileChunkList.push({
			file: file.slice(count, count + size)
		})
		count += size
	}
	return fileChunkList
}
//分片上传
const uploadChunkBase = (chunkList, contentType = 'application/octet-stream') => {
	let successCount = 0
	let totalChunks = chunkList.length
	return new Promise<void>((resolve, reject) => {
		const handler = () => {
			if (chunkList.length) {
				const chunkItem = chunkList.shift()
				// 直接上传二进制,不需要构造 FormData,否则上传后文件损坏
				service
					.put(chunkItem.uploadUrl, chunkItem.chunk.file, {
						headers: {
							'Content-Type': contentType
						}
					})
					.then(response => {
						if (response.status === 200) {
							console.log('分片:' + chunkItem.chunkNumber + ' 上传成功')
							successCount++
							// 继续上传下一个分片
							handler()
						} else {
							// 注意:这里没有针对失败做处理,请根据自己需求修改
							console.log('上传失败:' + response.status + ',' + response.statusText)
						}
					})
					.catch(error => {
						// 更新状态
						console.log('分片:' + chunkItem.chunkNumber + ' 上传失败,' + error)
						// 重新添加到队列中
						chunkList.push(chunkItem)
						handler()
					})
			}
			if (successCount >= totalChunks) {
				resolve()
			}
		}
		// 支持10个并发
		for (let i = 0; i < 10; i++) {
			handler()
		}
	})
}

utils/request是对axios的封装,比如超时,返回体错误码判断等等

对应的 api/file.ts

import service from '@/utils/request'

/** MD5校验 */
export const checkMd5 = (params?: object) => {
	return service.get('/oss/check', { params: params })
}

/** 获取分片上传地址 */
export const getPartUrl = (params?: object) => {
	return service.get('/oss/part-url', { params: params })
}

/** 分片合并 */
export const mergePart = (params?: object) => {
	return service.get('/oss/merge-part', { params: params, timeout: 10000 })
}

至此,分片上传即可使用

断点续传

这个其实就是在分片上传的基础做一个改进,将上传完成的分片反馈给后端做记录,再次续传时,只传对应的未上传分片即可,上传完成,再请求后端合并。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
MinIO通过S3协议来实现分片上传断点续传,具体步骤如下: 1. 初始化Multipart上传上传文件之前,需要使用 `InitiateMultipartUpload` API 方法来初始化一个Multipart上传会话,并获取一个上传ID。上传ID用于标识一个Multipart上传会话。 ```python from minio import Minio from minio.error import ResponseError # 初始化MinIO客户端 minio_client = Minio( endpoint="minio.example.com", access_key="ACCESS_KEY", secret_key="SECRET_KEY", secure=False ) # 初始化Multipart上传会话 try: upload_id = minio_client.initiate_multipart_upload("my_bucket", "my_large_file").upload_id print("Upload ID:", upload_id) except ResponseError as err: print(err) ``` 2. 上传文件块 将大文件分成多个小块,每个小块的大小可以根据需求自定义。使用 `UploadPart` API 方法将每个小块独立上传上传时需要指定上传文件名、块编号、块大小以及上传ID等信息。 ```python # 定义块大小(5MB) part_size = 5 * 1024 * 1024 # 上传文件块 try: # 打开待上传文件 with open("my_large_file", "rb") as file_data: # 上传文件块 part_number = 1 parts = [] while True: # 读取文件块数据 part_data = file_data.read(part_size) if not part_data: break # 上传文件块 etag = minio_client.upload_part( "my_bucket", "my_large_file", part_number, upload_id, part_data).etag parts.append({"PartNumber": part_number, "ETag": etag}) part_number += 1 except ResponseError as err: print(err) ``` 3. 完成Multipart上传 上传所有文件块后,需要使用 `CompleteMultipartUpload` API 方法将它们合并成一个完整的文件,最终得到上传文件。 ```python # 完成Multipart上传 try: minio_client.complete_multipart_upload( "my_bucket", "my_large_file", upload_id, parts) print("Upload complete!") except ResponseError as err: print(err) ``` 4. 断点续传 如果上传中断,可以使用 `ListParts` API 方法获取已上传文件块信息,然后从中断处继续上传。 ```python # 断点续传 try: # 获取已上传文件块信息 parts = minio_client.list_parts("my_bucket", "my_large_file", upload_id) # 继续上传文件块 for part in parts: if part.part_number < part_number: continue # 读取文件块数据 part_data = file_data.read(part_size) if not part_data: break # 上传文件块 etag = minio_client.upload_part( "my_bucket", "my_large_file", part_number, upload_id, part_data).etag parts.append({"PartNumber": part_number, "ETag": etag}) part_number += 1 # 完成Multipart上传 minio_client.complete_multipart_upload( "my_bucket", "my_large_file", upload_id, parts) print("Upload complete!") except ResponseError as err: print(err) ``` 以上是使用Python SDK实现MinIO文件服务器的分片上传断点续传的基本步骤,具体实现还需要根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值