网上关于minio分片上传的资料不太详细,缺斤少两,所以我基于他们的代码做了一些修改,demo能够正常运行起来,但是偶尔也会发生一些小bug,不过这些都无伤大雅,最终目的是理解代码背后的逻辑和流程
流程:
流程图:
1.vue前端
2. minio文件桶
项目中使用到的类库:spark-md5、axios、element-ui;
spark-md5 主要用来计算文件MD5,安装命令:
npm install spark-md5 --S npm install axios --S
上传示例
文件上传任务数:{{ taskQueueRunningNum }}
选择文件 上传 清空 {{ isPaused ? '继续' : '暂停' }} {{ item.name }} {{ transformByte(item.size) || item.size }} {{ `${item.uploadSpeed ? item.uploadSpeed : 0} M/s` }} 等待上传 校验MD5 正在创建序列 正在上传 上传完成 上传错误
import request from '@/utils/request' //上传信息 export function uploadFileInfo(data){ return request({ url:'upload/multipart/uploadFileInfo', method:'post', data }) } // 上传校验 export function checkUpload(MD5) { return request({ url: `upload/multipart/check?md5=${MD5}`, method: 'get', }) }; // 初始化上传 export function initUpload(data) { return request({ url: `upload/multipart/init`, method: 'post', data }) }; // 文件合并 export function mergeUpload(data) { return request({ url: `upload/multipart/merge`, method: 'post', data }) }; //判断文件是否存在 export function fileIsExits(data) { return request({ url: `upload/multipart/fileIsExits`, method: 'post', data }) };
import axios from 'axios' // 创建 axios 实例 const service = axios.create({ baseURL: "/api", // 环境的不同,对应不同的baseURL // transformRequest: [function(data) { // return Qs.stringify(data) // }], //timeout: 5000 // 请求超时时间 }) //request请求拦截 service.interceptors.request.use( config => { // var token=getToken() // if (token) { // config.headers.token = token // 让每个请求携带自定义token 请根据实际情况自行修改 // } return config; }, error => { // do something with request error return Promise.reject(error) } ) //响应拦截 service.interceptors.response.use( response => { const res = response return res }, error => { //这里还可以根据实际情况增加一些功能 return Promise.reject(error) } ) export default service
后端使用的是springboot ,使用之前要启动minio,redis,否则文件上传会出现异常。这里我都是使用windows版的
package com.xy.fileservice.controller; import com.xy.fileservice.entity.FileUploadInfo; import com.xy.fileservice.service.UploadService; import com.xy.fileservice.util.MinioUtils; import com.xy.fileservice.util.ResponseResult; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; /** * minio上传流程 * * 1.检查数据库中是否存在上传文件 * * 2.根据文件信息初始化,获取分片预签名url地址,前端根据url地址上传文件 * * 3.上传完成后,将分片上传的文件进行合并 * * 4.保存文件信息到数据库 */ @Slf4j @RestController @RequestMapping("/upload") public class FileMinioController { @Resource private UploadService uploadService; @Resource private MinioUtils minioUtils; /** * 校验文件是否存在 * * @param md5 String * @return ResponseResult
package com.xy.fileservice.service; import com.xy.fileservice.entity.FileUploadInfo; import com.xy.fileservice.util.ResponseResult; import org.springframework.web.multipart.MultipartFile; public interface UploadService { /** * 分片上传初始化 * * @param fileUploadInfo * @return Map*/ ResponseResult initMultiPartUpload(FileUploadInfo fileUploadInfo); /** * 完成分片上传 * * @param fileUploadInfo * @return boolean */ ResponseResult mergeMultipartUpload(FileUploadInfo fileUploadInfo); /** * 通过 sha256 获取已上传的数据 * @param sha256 String * @return Mono
package com.xy.fileservice.service.impl; import com.alibaba.fastjson.JSONObject; import com.xy.fileservice.entity.FileUploadInfo; import com.xy.fileservice.service.UploadService; import com.xy.fileservice.util.MinioUtils; import com.xy.fileservice.util.RedisRepo; import com.xy.fileservice.util.ResponseResult; import com.xy.fileservice.util.ResultCode; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.util.Objects; import static com.xy.fileservice.util.ResultCode.ACCESS_PARAMETER_INVALID; @Slf4j @Service public class UploadServiceImpl implements UploadService { @Resource private MinioUtils fileService; @Resource private RedisRepo redisRepo; /** * 通过 md5 获取已上传的数据(断点续传) * * @param md5 String * @return Mono
package com.xy.fileservice.util; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.HashMultimap; import com.xy.fileservice.config.CustomMinioClient; import com.xy.fileservice.entity.FileUploadInfo; import io.minio.*; import io.minio.errors.*; import io.minio.http.Method; import io.minio.messages.Part; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.xy.fileservice.util.ResultCode.DATA_NOT_EXISTS; import static com.xy.fileservice.util.ResultCode.UPLOAD_FILE_FAILED; @Slf4j @Component public class MinioUtils { @Value(value = "${minio.endpoint}") private String endpoint; @Value(value = "${minio.accesskey}") private String accesskey; @Value(value = "${minio.secretkey}") private String secretkey; @Resource private RedisRepo redisRepo; private CustomMinioClient customMinioClient; /** * 用spring的自动注入会注入失败 */ @PostConstruct public void init() { MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accesskey, secretkey) .build(); customMinioClient = new CustomMinioClient(minioClient); } /** * 单文件签名上传 * * @param objectName 文件全路径名称 * @param bucketName 桶名称 * @return / */ public ResponseResultgetUploadObjectUrl(String objectName, String bucketName) { log.info("tip message: 通过 <{}-{}> 开始单文件上传 ", objectName, bucketName); try { String url = getPresidedObjectUrl(bucketName, objectName); Map resMap = new HashMap<>(); resMap.put("uploadId", "SingleFileUpload"); resMap.put("urlList", Collections.singletonList(url)); return ResponseResult.success(resMap); } catch (Exception e) { log.error("error message: 初始化分片上传失败、原因:", e); // 返回 文件上传失败 return ResponseResult.error(UPLOAD_FILE_FAILED); } } /** * 文件分片上传 * * @param fileUploadInfo * @param objectName 文件全路径名称 * @param partCount 分片数量 * @param contentType 类型,如果类型使用默认流会导致无法预览 * @param bucketName 桶名称 * @return Mono > */ public ResponseResult initMultiPartUpload(FileUploadInfo fileUploadInfo, String objectName, int partCount, String contentType, String bucketName) { log.info("tip message: 通过 <{}-{}-{}-{}> 开始初始化<分片上传>数据", objectName, partCount, contentType, bucketName); try { String uploadId = getUploadId(bucketName, objectName, contentType); fileUploadInfo.setUploadId(uploadId); //redis保存文件信息 redisRepo.saveTimeout(fileUploadInfo.getFileMd5(), JSONObject.toJSONString(fileUploadInfo), 30, TimeUnit.MINUTES); List partList = getPartUploadUrls(uploadId, partCount, bucketName, objectName); Map resMap = new HashMap<>(); resMap.put("uploadId", uploadId); resMap.put("urlList", partList); log.info("tip message: 文件初始化<分片上传>、成功"); return ResponseResult.success(resMap); } catch (Exception e) { log.error("error message: 初始化分片上传失败、原因:", e); // 返回 文件上传失败 return ResponseResult.error(UPLOAD_FILE_FAILED); } } /** * 分片上传完后合并 * * @param objectName 文件全路径名称 * @param uploadId 返回的uploadId * @param bucketName 桶名称 * @return boolean */ public boolean mergeMultipartUpload(String objectName, String uploadId, String bucketName) { try { log.info("tip message: 通过 <{}-{}-{}> 合并<分片上传>数据", objectName, uploadId, bucketName); //目前仅做了最大1000分片 Part[] parts = new Part[1000]; // 查询上传后的分片数据 ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null); int partNumber = 1; for (Part part : partResult.result().partList()) { parts[partNumber - 1] = new Part(partNumber, part.etag()); partNumber++; } // 合并分片 customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null); } catch (Exception e) { log.error("error message: 合并失败、原因:", e); return false; } return true; } /** * 通过 sha256 获取上传中的分片信息 * * @param objectName 文件全路径名称 * @param uploadId 返回的uploadId * @param bucketName 桶名称 * @return Mono > */ public ResponseResult getByFileMd5(String objectName, String uploadId, String bucketName) { log.info("通过 <{}-{}-{}> 查询 上传分片数据", objectName, uploadId, bucketName); try { // 查询上传后的分片数据 ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null); List collect = partResult.result().partList().stream().map(Part::partNumber).collect(Collectors.toList()); return ResponseResult.uploading(collect); } catch (Exception e) { log.error("error message: 查询上传后的分片信息失败、原因:", e); return ResponseResult.error(DATA_NOT_EXISTS); } } /** * 获取文件下载地址 * * @param bucketName 桶名称 * @param fileName 文件名 * @return */ public String getFilePath(String bucketName, String fileName) { return StrUtil.format("{}/{}/{}", endpoint, bucketName, fileName);//文件访问路径 } /** * 创建一个桶 * * @return */ public String createBucket(String bucketName) { try { BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build(); //如果桶存在 if (customMinioClient.bucketExists(bucketExistsArgs)) { return bucketName; } // 如果不存在则创建新文件桶 MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build(); customMinioClient.makeBucket(makeBucketArgs); return bucketName; } catch (Exception e) { log.error("创建桶失败:{}", e.getMessage()); throw new RuntimeException(e); } } /** * 根据文件类型获取minio桶名称 * * @param fileType * @return */ public String getBucketName(String fileType) { try { if (StringUtils.isNotEmpty(fileType)) { //判断桶是否存在 String bucketName = createBucket(fileType.toLowerCase()); if (StringUtils.isNotEmpty(bucketName)) { return bucketName; } else { return fileType; } } } catch (Exception e) { log.error("Error reading bucket name "); } return fileType; } /** * 单文件获取上传url * @param bucketName * @param objectName * @return * @throws ServerException * @throws InsufficientDataException * @throws ErrorResponseException * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws InvalidResponseException * @throws XmlParserException * @throws InternalException */ private String getPresidedObjectUrl(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { return customMinioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.PUT) .bucket(bucketName) .object(objectName) .expiry(1, TimeUnit.DAYS) .build()); } /** * 获取合并id * @param bucketName * @param objectName * @param contentType * @return * @throws ServerException * @throws InsufficientDataException * @throws ErrorResponseException * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws XmlParserException * @throws InvalidResponseException * @throws InternalException */ private String getUploadId(String bucketName, String objectName, String contentType) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException { if (CharSequenceUtil.isBlank(contentType)) { contentType = "application/octet-stream"; } HashMultimap headers = HashMultimap.create(); headers.put("Content-Type", contentType); return customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null); } /** * 获取文件分片urls * @param uploadId * @param partCount * @param bucketName * @param objectName * @return * @throws ServerException * @throws InsufficientDataException * @throws ErrorResponseException * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws InvalidResponseException * @throws XmlParserException * @throws InternalException */ private List getPartUploadUrls(String uploadId, int partCount, String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { List partList = new ArrayList<>(); for (int i = 1; i <= partCount; i++) { Map reqParams = new HashMap<>(); reqParams.put("uploadId", uploadId); reqParams.put("partNumber", String.valueOf(i)); String uploadUrl = customMinioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.PUT) .bucket(bucketName) .object(objectName) .expiry(1, TimeUnit.DAYS) .extraQueryParams(reqParams) .build()); partList.add(uploadUrl); } return partList; } /** * 判断文件是否存在 * * @param bucketName 存储桶 * @param objectName 对象 * @return true:存在 */ public boolean doesObjectExist(String bucketName, String objectName) { boolean exist = true; try { customMinioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); } catch (Exception e) { exist = false; } return exist; } /** * 文件上传 * * @param file 文件 * @return Boolean */ public String upload(MultipartFile file, String bucketName) { String originalFilename = file.getOriginalFilename(); if (StringUtils.isBlank(originalFilename)) { throw new RuntimeException(); } String objectName = file.getName(); try { PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName) .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build(); //文件名称相同会覆盖 customMinioClient.putObject(objectArgs); } catch (Exception e) { e.printStackTrace(); return null; } // 查看文件地址 GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(objectName).method(Method.GET).build(); String url = null; try { url = customMinioClient.getPresignedObjectUrl(build); } catch (ErrorResponseException e) { e.printStackTrace(); } catch (InsufficientDataException e) { e.printStackTrace(); } catch (InternalException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidResponseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (XmlParserException e) { e.printStackTrace(); } catch (ServerException e) { e.printStackTrace(); } return url; } }
package com.xy.config; import com.google.common.collect.Multimap; import io.minio.CreateMultipartUploadResponse; import io.minio.ListPartsResponse; import io.minio.MinioClient; import io.minio.ObjectWriteResponse; import io.minio.errors.*; import io.minio.messages.Part; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class CustomMinioClient extends MinioClient { /** * 继承父类 * @param client */ public CustomMinioClient(MinioClient client) { super(client); } /** * 初始化分片上传、获取 uploadId * * @param bucket String 存储桶名称 * @param region String * @param object String 文件名称 * @param headers Multimap请求头 * @param extraQueryParams Multimap * @return String */ public String initMultiPartUpload(String bucket, String region, String object, Multimap headers, Multimap extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException { CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams); return response.result().uploadId(); } /** * 合并分片 * * @param bucketName String 桶名称 * @param region String * @param objectName String 文件名称 * @param uploadId String 上传的 uploadId * @param parts Part[] 分片集合 * @param extraHeaders Multimap * @param extraQueryParams Multimap * @return ObjectWriteResponse */ public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap extraHeaders, Multimap extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException { return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams); } /** * 查询当前上传后的分片信息 * * @param bucketName String 桶名称 * @param region String * @param objectName String 文件名称 * @param maxParts Integer 分片数量 * @param partNumberMarker Integer 分片起始值 * @param uploadId String 上传的 uploadId * @param extraHeaders Multimap * @param extraQueryParams Multimap * @return ListPartsResponse */ public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap extraHeaders, Multimap extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException { return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams); } }
package com.xy.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; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 全局跨域处理 * @author CV */ @Configuration public class CorsConfig implements WebMvcConfigurer { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setMaxAge(3600L); corsConfiguration.setAllowCredentials(true); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } }
接下来是返回信息工具类
package com.xy.util; import lombok.Data; @Data public class ResponseResult{ private int code; private String enMessage; private String zhMessage; private T data; public ResponseResult() { } public ResponseResult(int code, String enMessage, String zhMessage) { this.code = code; this.enMessage = enMessage; this.zhMessage = zhMessage; } /** * 成功 */ public static ResponseResult success() { ResponseResult result = new ResponseResult (); result.setCode(ResultCode.SUCCESS.getCode()); result.setEnMessage(ResultCode.SUCCESS.getEnMessage()); result.setZhMessage(ResultCode.SUCCESS.getZhMessage()); return result; } /** * 成功 */ public static ResponseResult success(T data) { ResponseResult result = new ResponseResult (); result.setCode(ResultCode.SUCCESS.getCode()); result.setEnMessage(ResultCode.SUCCESS.getEnMessage()); result.setZhMessage(ResultCode.SUCCESS.getZhMessage()); result.setData(data); return result; } /** * 失败 */ public static ResponseResult error() { ResponseResult result = new ResponseResult (); result.setCode(ResultCode.FAIL.getCode()); result.setEnMessage(ResultCode.FAIL.getEnMessage()); result.setZhMessage(ResultCode.FAIL.getZhMessage()); return result; } /** * 失败 */ public static ResponseResult error(T data) { ResponseResult result = new ResponseResult (); result.setCode(ResultCode.FAIL.getCode()); result.setEnMessage(ResultCode.FAIL.getEnMessage()); result.setZhMessage(ResultCode.FAIL.getZhMessage()); result.setData(data); return result; } /** * * @param data 数据 * @param * @return */ public static ResponseResult uploading(T data) { ResponseResult result = new ResponseResult (); result.setCode(ResultCode.UPLOADING.getCode()); result.setEnMessage(ResultCode.UPLOADING.getEnMessage()); result.setZhMessage(ResultCode.UPLOADING.getZhMessage()); result.setData(data); return result; } /** * 成功 */ public static ResponseResult success(int code, String enMessage, String zhMessage) { return new ResponseResult(code, enMessage, zhMessage); } /** * 失败 */ public static ResponseResult error(int code, String enMessage, String zhMessage) { return new ResponseResult(code, enMessage, zhMessage); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getEnMessage() { return enMessage; } public void setEnMessage(String enMessage) { this.enMessage = enMessage; } public String getZhMessage() { return zhMessage; } public void setZhMessage(String zhMessage) { this.zhMessage = zhMessage; } public T getData() { return data; } public void setData(T data) { this.data = data; } // public static ResponseResult SUCCESS = new ResponseResult<>(200,"成功"); // public static ResponseResult INTEVER_ERROR = new ResponseResult<>(500,"服务器错误"); // public static ResponseResult NOT_FOUND = new ResponseResult<>(404,"未找到"); }
package com.xy.util; /** * http状态码枚举类 */ public enum ResultCode { SUCCESS(1, "Success", "成功"), UPLOADING(2, "Uploading", "上传中"), FAIL(-1, "Err", "失败"), DATABASE_OPERATION_FAILED(504, "数据库操作失败"), CONTINUE(100, "Continue", "请继续发送请求的剩余部分"), SWITCHING_PROTOCOLS(101, "Switching Protocols", "协议切换"), PROCESSING(102, "Processing", "请求将继续执行"), CHECKPOINT(103, "Checkpoint", "可以预加载"), OK(200, "OK", "请求已经成功处理"), CREATED(201, "Created", "请求已经成功处理,并创建了资源"), ACCEPTED(202, "Accepted", "请求已经接受,等待执行"), NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information", "请求已经成功处理,但是信息不是原始的"), NO_CONTENT(204, "No Content", "请求已经成功处理,没有内容需要返回"), RESET_CONTENT(205, "Reset Content", "请求已经成功处理,请重置视图"), PARTIAL_CONTENT(206, "Partial Content", "部分Get请求已经成功处理"), MULTI_STATUS(207, "Multi-Status", "请求已经成功处理,将返回XML消息体"), ALREADY_REPORTED(208, "Already Reported", "请求已经成功处理,一个DAV的绑定成员被前一个请求枚举,并且没有被再一次包括"), IM_USED(226, "IM Used", "请求已经成功处理,将响应一个或者多个实例"), MULTIPLE_CHOICES(300, "Multiple Choices", "提供可供选择的回馈"), MOVED_PERMANENTLY(301, "Moved Permanently", "请求的资源已经永久转移"), FOUND(302, "Found", "请重新发送请求"), SEE_OTHER(303, "See Other", "请以Get方式请求另一个URI"), NOT_MODIFIED(304, "Not Modified", "资源未改变"), USE_PROXY(305, "Use Proxy", "请通过Location域中的代理进行访问"), TEMPORARY_REDIRECT(307, "Temporary Redirect", "请求的资源临时从不同的URI响应请求"), RESUME_INCOMPLETE(308, "Resume Incomplete", "请求的资源已经永久转移"), BAD_REQUEST(400, "Bad Request", "请求错误,请修正请求"), UNAUTHORIZED(401, "Unauthorized", "没有被授权或者授权已经失效"), PAYMENT_REQUIRED(402, "Payment Required", "预留状态"), FORBIDDEN(403, "Forbidden", "请求被理解,但是拒绝执行"), NOT_FOUND(404, "Not Found", "资源未找到"), METHOD_NOT_ALLOWED(405, "Method Not Allowed", "请求方法不允许被执行"), NOT_ACCEPTABLE(406, "Not Acceptable", "请求的资源不满足请求者要求"), PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required", "请通过代理进行身份验证"), REQUEST_TIMEOUT(408, "Request Timeout", "请求超时"), CONFLICT(409, "Conflict", "请求冲突"), GONE(410, "Gone", "请求的资源不可用"), LENGTH_REQUIRED(411, "Length Required", "Content-Length未定义"), PRECONDITION_FAILED(412, "Precondition Failed", "不满足请求的先决条件"), REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large", "请求发送的实体太大"), REQUEST_URI_TOO_LONG(414, "Request-URI Too Long", "请求的URI超长"), UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type", "请求发送的实体类型不受支持"), REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable", "Range指定的范围与当前资源可用范围不一致"), EXPECTATION_FAILED(417, "Expectation Failed", "请求头Expect中指定的预期内容无法被服务器满足"), UNPROCESSABLE_ENTITY(422, "Unprocessable Entity", "请求格式正确,但是由于含有语义错误,无法响应"), LOCKED(423, "Locked", "当前资源被锁定"), FAILED_DEPENDENCY(424, "Failed Dependency", "由于之前的请求发生错误,导致当前请求失败"), UPGRADE_REQUIRED(426, "Upgrade Required", "客户端需要切换到TLS1.0"), PRECONDITION_REQUIRED(428, "Precondition Required", "请求需要提供前置条件"), TOO_MANY_REQUESTS(429, "Too Many Requests", "请求过多"), REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large", "请求头超大,拒绝请求"), INTERNAL_SERVER_ERROR(500, "Internal Server Error", "服务器内部错误"), NOT_IMPLEMENTED(501, "Not Implemented", "服务器不支持当前请求的部分功能"), BAD_GATEWAY(502, "Bad Gateway", "响应无效"), SERVICE_UNAVAILABLE(503, "Service Unavailable", "服务器维护或者过载,拒绝服务"), GATEWAY_TIMEOUT(504, "Gateway Timeout", "上游服务器超时"), HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported", "不支持的HTTP版本"), VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates", "服务器内部配置错误"), INSUFFICIENT_STORAGE(507, "Insufficient Storage", "服务器无法完成存储请求所需的内容"), LOOP_DETECTED(508, "Loop Detected", "服务器处理请求时发现死循环"), BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded", "服务器达到带宽限制"), NOT_EXTENDED(510, "Not Extended", "获取资源所需的策略没有被满足"), NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required", "需要进行网络授权"), ACCESS_PARAMETER_INVALID(1001,"Invalid access parameter","访问参数无效"), UPLOAD_FILE_FAILED(1002,"File upload failure","文件上传失败"), DATA_NOT_EXISTS(1003,"Data does not exist","数据不存在"), ; private int code; private String enMessage; private String zhMessage; ResultCode(int code, String enMessage, String zhMessage) { this.code = code; this.enMessage = enMessage; this.zhMessage = zhMessage; } ResultCode(int code, String message) { } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getEnMessage() { return enMessage; } public void setEnMessage(String enMessage) { this.enMessage = enMessage; } public String getZhMessage() { return zhMessage; } public void setZhMessage(String zhMessage) { this.zhMessage = zhMessage; } }
package com.xy.entity; import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class FileUploadInfo { //@NotBlank(message = "文件名不能为空") private String fileName; // @NotNull(message = "文件大小不能为空") private Double fileSize; // @NotBlank(message = "Content-Type不能为空") private String contentType; // @NotNull(message = "分片数量不能为空") private Integer partCount; // @NotBlank(message = "uploadId 不能为空") private String uploadId; // 桶名称 //private String bucketName; //md5 private String fileMd5; //文件类型 private String fileType; public FileUploadInfo() { } }
package com.xy.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundValueOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class RedisRepo { @Autowired private StringRedisTemplate redisTemplate; public String get(String key) { BoundValueOperationsops = redisTemplate.boundValueOps(key); return ops.get(); } public void save(String key,String str){ BoundValueOperations ops = redisTemplate.boundValueOps(key); ops.set(str); } public void saveTimeout(String key, String value, long timeout, TimeUnit unit ){ redisTemplate.boundValueOps(key).setIfAbsent(value,timeout,unit); } public void delete(String key){ redisTemplate.delete(key); } public long expire(String key){ return redisTemplate.opsForValue().getOperations().getExpire(key); } }
minio: endpoint: http://localhost:9000 accesskey: minioadmin secretkey: minioadmin spring: redis: host: localhost port: 6379
io.minio minio8.3.1 com.squareup.okhttp3 okhttp4.9.2
为了方便中间件皆采用docker,使用时注意替换自己的本地目录
redis
地址:localhost:6379
docker run --name redis01 -p 6379:6379 -v D:\Docker-vm\folder\redis\data\redis.conf:/usr/local/etc/redis/redis.conf -d redis:5.0.14
minio
地址:localhost:9000
账号:minioadmin
密码:minioadmin
docker run -p 9000:9000 -p 9090:9090 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minioadmin" -e "MINIO_SECRET_KEY=minioadmin" -v D:\Docker-vm\folder\minio\data:/data -v D:\Docker-vm\folder\minio\config:/root/.minio minio/minio server /data --console-address ":9090" -address ":9000"
2023.03.01
本文仅介绍上传流程的简单实现,很多功能未完善,如文件夹上传、上传暂停、停止等功能。代码有何异常或者不完整欢迎在评论区留言
2023.12.20
前端升级到了 vite + vue + element plus 后端升级到了 jdk17 + springboot3.0
前端升级了框架, 增加了暂停上传功能, 做了大量的代码优化.
tip: 暂停上传功能在文件体积较小的情况下可能会因为文件上传速度太快出现功能异常
2023.12.23
前端功能优化, 计算md5 采用分片抽样计算,优化了上传逻辑,增加了任务队列实现多文件同时上传,暂停功能也进行优化,修复了功能 . 同时增加了网速显示功能
tip: 网速显示不太准确,参考意义不大,网上没有较好的网速计算方式,大家如果看到欢迎留言
项目传送门: gitee
转载请注明出处