From c2ab755cf3cb4dad966a25ec8ceddbc2706693dd Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Wed, 22 Jan 2025 11:56:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20MinIO=20=E4=B8=8A=E4=BC=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E8=BF=94=E5=9B=9E=E5=90=8D=E7=A7=B0=E4=B8=8D=E5=B8=A6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E5=92=8C=E5=85=A8=E5=B1=80=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E4=BF=A1=E6=81=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/BusinessException.java | 7 +++ .../exception/GlobalExceptionHandler.java | 8 +-- .../boot/common/result/IResultCode.java | 4 +- .../youlai/boot/common/result/ResultCode.java | 16 ++--- .../boot/shared/file/model/FileInfo.java | 7 +++ .../file/service/impl/MinioFileService.java | 58 ++++++++++--------- 6 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/youlai/boot/common/exception/BusinessException.java b/src/main/java/com/youlai/boot/common/exception/BusinessException.java index c74ae972..06467091 100644 --- a/src/main/java/com/youlai/boot/common/exception/BusinessException.java +++ b/src/main/java/com/youlai/boot/common/exception/BusinessException.java @@ -20,6 +20,13 @@ public class BusinessException extends RuntimeException { this.resultCode = errorCode; } + + public BusinessException(IResultCode errorCode,String message) { + super(message); + this.resultCode = errorCode; + } + + public BusinessException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/com/youlai/boot/common/exception/GlobalExceptionHandler.java b/src/main/java/com/youlai/boot/common/exception/GlobalExceptionHandler.java index 2021dbd9..cf7722a8 100644 --- a/src/main/java/com/youlai/boot/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/youlai/boot/common/exception/GlobalExceptionHandler.java @@ -30,7 +30,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; - /** * 全局系统异常处理器 *

@@ -219,9 +218,9 @@ public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result handleBizException(BusinessException e) { - log.error("biz exception: {}", e.getMessage()); + log.error("biz exception", e); if (e.getResultCode() != null) { - return Result.failed(e.getResultCode()); + return Result.failed(e.getResultCode(), e.getMessage()); } return Result.failed(e.getMessage()); } @@ -239,8 +238,7 @@ public class GlobalExceptionHandler { || e instanceof AuthenticationException) { throw e; } - log.error("unknown exception: {}", e.getMessage()); - e.printStackTrace(); + log.error("unknown exception", e); return Result.failed(e.getLocalizedMessage()); } diff --git a/src/main/java/com/youlai/boot/common/result/IResultCode.java b/src/main/java/com/youlai/boot/common/result/IResultCode.java index 479639e3..741d604a 100644 --- a/src/main/java/com/youlai/boot/common/result/IResultCode.java +++ b/src/main/java/com/youlai/boot/common/result/IResultCode.java @@ -3,8 +3,8 @@ package com.youlai.boot.common.result; /** * 响应码接口 * - * @author Ray - * @since 2022/2/18 + * @author Ray.Hao + * @since 1.0.0 **/ public interface IResultCode { diff --git a/src/main/java/com/youlai/boot/common/result/ResultCode.java b/src/main/java/com/youlai/boot/common/result/ResultCode.java index 73ab7752..026927ef 100644 --- a/src/main/java/com/youlai/boot/common/result/ResultCode.java +++ b/src/main/java/com/youlai/boot/common/result/ResultCode.java @@ -42,7 +42,7 @@ public enum ResultCode implements IResultCode, Serializable { VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"), USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"), - USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证��类型未选择"), + USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证件类型未选择"), MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"), USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"), @@ -127,12 +127,14 @@ public enum ResultCode implements IResultCode, Serializable { USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"), /** 二级宏观错误码 */ - USER_UPLOAD_FILE_EXCEPTION("A0700", "用户上传文件异常"), - USER_UPLOAD_FILE_TYPE_MISMATCH("A0701", "用户上传文件类型不匹配"), - USER_UPLOAD_FILE_TOO_LARGE("A0702", "用户上传文件太大"), - USER_UPLOAD_IMAGE_TOO_LARGE("A0703", "用户上传图片太大"), - USER_UPLOAD_VIDEO_TOO_LARGE("A0704", "用户上传视频太大"), - USER_UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "用户上传压缩文件太大"), + UPLOAD_FILE_EXCEPTION("A0700", "上传文件异常"), + UPLOAD_FILE_TYPE_MISMATCH("A0701", "上传文件类型不匹配"), + UPLOAD_FILE_TOO_LARGE("A0702", "上传文件太大"), + UPLOAD_IMAGE_TOO_LARGE("A0703", "上传图片太大"), + UPLOAD_VIDEO_TOO_LARGE("A0704", "上传视频太大"), + UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "上传压缩文件太大"), + + DELETE_FILE_EXCEPTION("A0710", "删除文件异常"), /** 二级宏观错误码 */ USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"), diff --git a/src/main/java/com/youlai/boot/shared/file/model/FileInfo.java b/src/main/java/com/youlai/boot/shared/file/model/FileInfo.java index f1251dc4..ec550a18 100644 --- a/src/main/java/com/youlai/boot/shared/file/model/FileInfo.java +++ b/src/main/java/com/youlai/boot/shared/file/model/FileInfo.java @@ -3,6 +3,13 @@ package com.youlai.boot.shared.file.model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; + +/** + * 文件信息对象 + * + * @author Ray.Hao + * @since 1.0.0 + */ @Schema(description = "文件对象") @Data public class FileInfo { diff --git a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java index db5fce55..59c7e576 100644 --- a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java +++ b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java @@ -5,30 +5,30 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.youlai.boot.common.exception.BusinessException; +import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.shared.file.service.FileService; import com.youlai.boot.shared.file.model.FileInfo; import io.minio.*; -import io.minio.errors.*; import io.minio.http.Method; import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; +import java.io.File; import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; /** * MinIO 文件上传服务类 * - * @author haoxr + * @author Ray.Hao * @since 2023/6/2 */ @Component @@ -36,6 +36,7 @@ import java.time.LocalDateTime; @ConfigurationProperties(prefix = "oss.minio") @RequiredArgsConstructor @Data +@Slf4j public class MinioFileService implements FileService { /** @@ -77,7 +78,7 @@ public class MinioFileService implements FileService { * 上传文件 * * @param file 表单文件对象 - * @return + * @return 文件信息 */ @Override public FileInfo uploadFile(MultipartFile file) { @@ -85,16 +86,19 @@ public class MinioFileService implements FileService { // 创建存储桶(存储桶不存在),如果有搭建好的minio服务,建议放在init方法中 createBucketIfAbsent(bucketName); - // 生成文件名(日期文件夹) + // 文件后缀 String suffix = FileUtil.getSuffix(file.getOriginalFilename()); - String uuid = IdUtil.simpleUUID(); - String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix; + // 文件夹名称 + String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd"); + // 文件名称 + String fileName = IdUtil.simpleUUID() + "." + suffix; + // try-with-resource 语法糖自动释放流 try (InputStream inputStream = file.getInputStream()) { // 文件上传 PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(bucketName) - .object(fileName) + .object(dateFolder + "/"+ fileName) .contentType(file.getContentType()) .stream(inputStream, inputStream.available(), -1) .build(); @@ -104,15 +108,18 @@ public class MinioFileService implements FileService { String fileUrl; // 未配置自定义域名 if (StrUtil.isBlank(customDomain)) { + // 获取文件URL GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder() - .bucket(bucketName).object(fileName) + .bucket(bucketName) + .object(dateFolder + "/"+ fileName) .method(Method.GET) .build(); fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); fileUrl = fileUrl.substring(0, fileUrl.indexOf("?")); - } else { // 配置自定义文件路径域名 - fileUrl = customDomain + '/' + bucketName + "/" + fileName; + } else { + // 配置自定义文件路径域名 + fileUrl = customDomain + "/"+ bucketName + "/"+ dateFolder + "/"+ fileName; } FileInfo fileInfo = new FileInfo(); @@ -120,7 +127,8 @@ public class MinioFileService implements FileService { fileInfo.setUrl(fileUrl); return fileInfo; } catch (Exception e) { - throw new RuntimeException("文件上传失败"); + log.error("上传文件失败", e); + throw new BusinessException(ResultCode.UPLOAD_FILE_EXCEPTION, e.getMessage()); } } @@ -128,9 +136,8 @@ public class MinioFileService implements FileService { /** * 删除文件 * - * @param filePath 文件路径 http://localhost:9000/default/20221120/test.jpg - * - * @return + * @param filePath 文件完整路径 + * @return 是否删除成功 */ @Override public boolean deleteFile(String filePath) { @@ -152,7 +159,8 @@ public class MinioFileService implements FileService { minioClient.removeObject(removeObjectArgs); return true; } catch (Exception e) { - throw new RuntimeException("文件删除失败", e); + log.error("删除文件失败", e); + throw new BusinessException(ResultCode.DELETE_FILE_EXCEPTION, e.getMessage()); } } @@ -161,17 +169,11 @@ public class MinioFileService implements FileService { * PUBLIC桶策略 * 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied * - * @param bucketName - * @return + * @param bucketName 存储桶名称 + * @return 存储桶策略 */ private static String publicBucketPolicy(String bucketName) { - /** - * AWS的S3存储桶策略 - * Principal: 生效用户对象 - * Resource: 指定存储桶 - * Action: 操作行为 - */ - + // AWS的S3存储桶策略 JSON 格式 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example-bucket-policies.html return "{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\"," + "\"Principal\":{\"AWS\":[\"*\"]}," @@ -185,7 +187,7 @@ public class MinioFileService implements FileService { /** * 创建存储桶(存储桶不存在) * - * @param bucketName + * @param bucketName 存储桶名称 */ @SneakyThrows private void createBucketIfAbsent(String bucketName) {