Просмотр исходного кода

Update 优化文件上传增加文件内容长度校验,优化OssClient 并发配置,上传请求的预签名URL

Yue 2 недель назад
Родитель
Сommit
e349186389

+ 87 - 58
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/core/OssClient.java

@@ -41,6 +41,7 @@ import java.nio.file.Path;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.time.Duration;
 import java.util.Date;
 import java.util.Date;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Random;
 import java.util.Random;
 import java.util.function.Consumer;
 import java.util.function.Consumer;
@@ -79,13 +80,13 @@ public class OssClient {
         S3TransferManager transferManager = null;
         S3TransferManager transferManager = null;
         S3Presigner presigner = null;
         S3Presigner presigner = null;
         try {
         try {
-//            if (isLocal() && StringUtils.isEmpty(ossProperties.getEndpoint())) {
-//                properties.setEndpoint("127.0.0.1:8080");
-//            }
+            // if (isLocal() && StringUtils.isEmpty(ossProperties.getEndpoint())) {
+            // properties.setEndpoint("127.0.0.1:8080");
+            // }
             if (!isLocal()) {
             if (!isLocal()) {
                 StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                 StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                         AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
                         AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
-                //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
+                // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
                 boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
                 boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
                 // 创建AWS基于 CRT 的 S3 客户端
                 // 创建AWS基于 CRT 的 S3 客户端
                 // 创建AWS基于 Netty 的 S3 客户端
                 // 创建AWS基于 Netty 的 S3 客户端
@@ -95,9 +96,13 @@ public class OssClient {
                         .region(of())
                         .region(of())
                         .forcePathStyle(isStyle)
                         .forcePathStyle(isStyle)
                         .httpClient(NettyNioAsyncHttpClient.builder()
                         .httpClient(NettyNioAsyncHttpClient.builder()
-                                .connectionTimeout(Duration.ofSeconds(60)).build())
+                                .connectionTimeout(Duration.ofSeconds(60))
+                                .connectionAcquisitionTimeout(Duration.ofSeconds(30))
+                                .maxConcurrency(100)
+                                .maxPendingConnectionAcquires(1000)
+                                .build())
                         .build();
                         .build();
-                //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
+                // AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
                 transferManager = S3TransferManager.builder().s3Client(client).build();
                 transferManager = S3TransferManager.builder().s3Client(client).build();
 
 
                 // 创建 S3 配置对象
                 // 创建 S3 配置对象
@@ -158,14 +163,14 @@ public class OssClient {
                 // 构建上传请求对象
                 // 构建上传请求对象
                 FileUpload fileUpload = transferManager.uploadFile(
                 FileUpload fileUpload = transferManager.uploadFile(
                         x -> x.putObjectRequest(
                         x -> x.putObjectRequest(
-                                        y -> y.bucket(properties.getBucketName())
-                                                .key(key)
-                                                .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
-                                                .contentType(contentType)
-                                                // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
-                                                // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
-                                                //.acl(getAccessPolicy().getObjectCannedACL())
-                                                .build())
+                                y -> y.bucket(properties.getBucketName())
+                                        .key(key)
+                                        .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
+                                        .contentType(contentType)
+                                        // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
+                                        // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
+                                        // .acl(getAccessPolicy().getObjectCannedACL())
+                                        .build())
                                 .addTransferListener(LoggingTransferListener.create())
                                 .addTransferListener(LoggingTransferListener.create())
                                 .source(filePath).build());
                                 .source(filePath).build());
 
 
@@ -223,7 +228,7 @@ public class OssClient {
                                                 .contentType(contentType)
                                                 .contentType(contentType)
                                                 // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                                                 // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                                                 // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
                                                 // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
-                                                //.acl(getAccessPolicy().getObjectCannedACL())
+                                                // .acl(getAccessPolicy().getObjectCannedACL())
                                                 .build())
                                                 .build())
                                 .build());
                                 .build());
 
 
@@ -322,7 +327,8 @@ public class OssClient {
      * @throws OssException 如果上传失败,抛出自定义异常
      * @throws OssException 如果上传失败,抛出自定义异常
      */
      */
     public UploadResult uploadSuffix(byte[] data, String suffix) {
     public UploadResult uploadSuffix(byte[] data, String suffix) {
-        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Convert.toLong(data.length), FileUtils.getMimeType(suffix));
+        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix),
+                Convert.toLong(data.length), FileUtils.getMimeType(suffix));
     }
     }
 
 
     /**
     /**
@@ -351,7 +357,8 @@ public class OssClient {
     }
     }
 
 
     public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
     public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
-        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Convert.toLong(data.length), contentType);
+        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix),
+                Convert.toLong(data.length), contentType);
     }
     }
 
 
     /**
     /**
@@ -367,9 +374,9 @@ public class OssClient {
         // 使用 S3TransferManager 下载文件
         // 使用 S3TransferManager 下载文件
         FileDownload downloadFile = transferManager.downloadFile(
         FileDownload downloadFile = transferManager.downloadFile(
                 x -> x.getObjectRequest(
                 x -> x.getObjectRequest(
-                                y -> y.bucket(properties.getBucketName())
-                                        .key(removeBaseUrl(path))
-                                        .build())
+                        y -> y.bucket(properties.getBucketName())
+                                .key(removeBaseUrl(path))
+                                .build())
                         .addTransferListener(LoggingTransferListener.create())
                         .addTransferListener(LoggingTransferListener.create())
                         .destination(tempFilePath)
                         .destination(tempFilePath)
                         .build());
                         .build());
@@ -415,7 +422,8 @@ public class OssClient {
                     .build();
                     .build();
 
 
             // 使用 S3TransferManager 下载文件
             // 使用 S3TransferManager 下载文件
-            Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
+            Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager
+                    .download(publisherDownloadRequest);
             // 获取下载发布订阅转换器
             // 获取下载发布订阅转换器
             ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
             ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
             // 执行文件大小消费者函数
             // 执行文件大小消费者函数
@@ -527,7 +535,7 @@ public class OssClient {
      * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
      * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
      */
      */
     public Region of() {
     public Region of() {
-        //AWS 区域字符串
+        // AWS 区域字符串
         String region = properties.getRegion();
         String region = properties.getRegion();
         // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
         // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
         return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
         return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
@@ -563,16 +571,16 @@ public class OssClient {
         return path.replace(getUrl() + StringUtils.SLASH, "");
         return path.replace(getUrl() + StringUtils.SLASH, "");
     }
     }
 
 
-//    /**
-//     * 获取文件元数据
-//     *
-//     * @param path 完整文件路径
-//     */
-//    public ObjectMetadata getObjectMetadata(String path) {
-//        path = path.replace(getUrl() + "/", "");
-//        S3Object object = client.getObject(properties.getBucketName(), path);
-//        return object.getObjectMetadata();
-//    }
+    // /**
+    // * 获取文件元数据
+    // *
+    // * @param path 完整文件路径
+    // */
+    // public ObjectMetadata getObjectMetadata(String path) {
+    // path = path.replace(getUrl() + "/", "");
+    // S3Object object = client.getObject(properties.getBucketName(), path);
+    // return object.getObjectMetadata();
+    // }
 
 
     public InputStream getObjectContent(String path) throws IOException {
     public InputStream getObjectContent(String path) throws IOException {
         // 下载文件到临时目录
         // 下载文件到临时目录
@@ -600,18 +608,18 @@ public class OssClient {
         }
         }
     }
     }
 
 
-//    private InputStream getObjectContent(String fileCode, String path) {
-//        String filePath = VbFile.getDirectory(path, fileCode);
-//        VbFile vbFile = new VbFile(filePath, fileCode);
-//        try {
-//            try (InputStream stream = new FileInputStream(vbFile)) {
-//                return stream;
-//            }
-//        } catch (Exception e) {
-//            log.error("获取文件流异常: {}", e.getMessage());
-//            throw new OssException("获取文件流异常: " + e.getMessage());
-//        }
-//    }
+    // private InputStream getObjectContent(String fileCode, String path) {
+    // String filePath = VbFile.getDirectory(path, fileCode);
+    // VbFile vbFile = new VbFile(filePath, fileCode);
+    // try {
+    // try (InputStream stream = new FileInputStream(vbFile)) {
+    // return stream;
+    // }
+    // } catch (Exception e) {
+    // log.error("获取文件流异常: {}", e.getMessage());
+    // throw new OssException("获取文件流异常: " + e.getMessage());
+    // }
+    // }
 
 
     /**
     /**
      * 获取云存储服务的URL
      * 获取云存储服务的URL
@@ -629,8 +637,9 @@ public class OssClient {
         // MinIO 单独处理
         // MinIO 单独处理
         if (StringUtils.isNotEmpty(domain)) {
         if (StringUtils.isNotEmpty(domain)) {
             // 如果 domain 以 "https://" 或 "http://" 开头
             // 如果 domain 以 "https://" 或 "http://" 开头
-            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
-                    domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
+            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP))
+                    ? domain + StringUtils.SLASH + properties.getBucketName()
+                    : header + domain + StringUtils.SLASH + properties.getBucketName();
         }
         }
         return header + endpoint + StringUtils.SLASH + properties.getBucketName();
         return header + endpoint + StringUtils.SLASH + properties.getBucketName();
     }
     }
@@ -656,8 +665,8 @@ public class OssClient {
             // 生成日期路径
             // 生成日期路径
             String datePath = DateUtils.datePath();
             String datePath = DateUtils.datePath();
             // 拼接路径
             // 拼接路径
-            path = StringUtils.isNotEmpty(prefix) ?
-                    prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
+            path = StringUtils.isNotEmpty(prefix) ? prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid
+                    : datePath + StringUtils.SLASH + uuid;
             return path + suffix;
             return path + suffix;
         }
         }
 
 
@@ -670,17 +679,38 @@ public class OssClient {
      * @param objectKey   对象KEY
      * @param objectKey   对象KEY
      * @param expiredTime 链接授权到期时间
      * @param expiredTime 链接授权到期时间
      */
      */
-    public String getPrivateUrl(String objectKey, Duration expiredTime) {
-        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
+    public String createPresignedGetUrl(String objectKey, Duration expiredTime) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取下载对象的预签名 URL
         URL url = presigner.presignGetObject(
         URL url = presigner.presignGetObject(
-                        x -> x.signatureDuration(expiredTime)
-                                .getObjectRequest(
-                                        y -> y.bucket(properties.getBucketName())
-                                                .key(objectKey)
-                                                .build())
-                                .build())
+                x -> x.signatureDuration(expiredTime)
+                        .getObjectRequest(
+                                y -> y.bucket(properties.getBucketName())
+                                        .key(objectKey)
+                                        .build())
+                        .build())
+                .url();
+        return url.toExternalForm();
+    }
+
+    /**
+     * 创建上传请求的预签名URL
+     *
+     * @param objectKey   对象KEY
+     * @param expiredTime 链接授权到期时间
+     * @param metadata    元数据
+     */
+    public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL
+        URL url = presigner.presignPutObject(
+                x -> x.signatureDuration(expiredTime)
+                        .putObjectRequest(
+                                y -> y.bucket(properties.getBucketName())
+                                        .key(objectKey)
+                                        .metadata(metadata)
+                                        .build())
+                        .build())
                 .url();
                 .url();
-        return url.toString();
+        return url.toExternalForm();
     }
     }
 
 
     /**
     /**
@@ -699,7 +729,6 @@ public class OssClient {
         return AccessPolicyType.getByType(properties.getAccessPolicy());
         return AccessPolicyType.getByType(properties.getAccessPolicy());
     }
     }
 
 
-
     public boolean isLocal() {
     public boolean isLocal() {
         return LOCAL_ACCESS_KEY.equals(configKey);
         return LOCAL_ACCESS_KEY.equals(configKey);
     }
     }

+ 0 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysOssController.java

@@ -68,9 +68,6 @@ public class SysOssController extends BaseController {
     @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
     @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
     @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
     public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
-        if (ObjectUtil.isNull(file)) {
-            return R.fail("上传文件不能为空");
-        }
         SysOssVo oss = ossService.upload(file);
         SysOssVo oss = ossService.upload(file);
         SysOssUploadVo uploadVo = new SysOssUploadVo();
         SysOssUploadVo uploadVo = new SysOssUploadVo();
         uploadVo.setUrl(oss.getUrl());
         uploadVo.setUrl(oss.getUrl());

+ 2 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysProfileController.java

@@ -2,6 +2,7 @@ package com.vber.system.controller.system;
 
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.digest.BCrypt;
 import cn.hutool.crypto.digest.BCrypt;
 import com.vber.common.core.domain.R;
 import com.vber.common.core.domain.R;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.core.utils.StringUtils;
@@ -116,7 +117,7 @@ public class SysProfileController extends BaseController {
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     public R<AvatarVo> avatar(@RequestPart("avatar") MultipartFile avatarfile) {
     public R<AvatarVo> avatar(@RequestPart("avatar") MultipartFile avatarfile) {
-        if (!avatarfile.isEmpty()) {
+        if (ObjectUtil.isNotNull(avatarfile) && !avatarfile.isEmpty()) {
             String extension = FileUtil.extName(avatarfile.getOriginalFilename());
             String extension = FileUtil.extName(avatarfile.getOriginalFilename());
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
                 return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
                 return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");

+ 8 - 4
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysOssServiceImpl.java

@@ -58,7 +58,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
 
 
     private final SysOssMapper baseMapper;
     private final SysOssMapper baseMapper;
 
 
-    private final String[] SysFileSuffix = new String[]{"avatar", "system"};
+    private final String[] SysFileSuffix = new String[] { "avatar", "system" };
 
 
     /**
     /**
      * 查询OSS对象存储列表
      * 查询OSS对象存储列表
@@ -314,13 +314,17 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
      */
      */
     @Override
     @Override
     public SysOssVo upload(File file) {
     public SysOssVo upload(File file) {
+        if (ObjectUtil.isNull(file) || !file.isFile() || file.length() <= 0) {
+            throw new ServiceException("上传文件不能为空");
+        }
         String originalFileName = file.getName();
         String originalFileName = file.getName();
         String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf(".") + 1,
         String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf(".") + 1,
                 originalFileName.length());
                 originalFileName.length());
         OssClient storage = OssFactory.instance();
         OssClient storage = OssFactory.instance();
+        long length = file.length();
         UploadResult uploadResult = storage.uploadSuffix(file, suffix);
         UploadResult uploadResult = storage.uploadSuffix(file, suffix);
         SysOssExt ext1 = new SysOssExt();
         SysOssExt ext1 = new SysOssExt();
-        ext1.setFileSize(file.length());
+        ext1.setFileSize(length);
         // 保存文件信息
         // 保存文件信息
         return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult, ext1);
         return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult, ext1);
     }
     }
@@ -349,7 +353,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
 
 
     @NotNull
     @NotNull
     private SysOssVo buildResultEntity(String originalFileName, String suffix, String configKey,
     private SysOssVo buildResultEntity(String originalFileName, String suffix, String configKey,
-                                       UploadResult uploadResult, SysOssExt ext1) {
+            UploadResult uploadResult, SysOssExt ext1) {
         SysOss oss = new SysOss();
         SysOss oss = new SysOss();
         oss.setUrl(uploadResult.getUrl());
         oss.setUrl(uploadResult.getUrl());
         oss.setFileSuffix(suffix);
         oss.setFileSuffix(suffix);
@@ -396,7 +400,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
         OssClient storage = OssFactory.instance(oss.getService());
         OssClient storage = OssFactory.instance(oss.getService());
         // 仅修改桶类型为 private 的URL,临时URL时长为120s
         // 仅修改桶类型为 private 的URL,临时URL时长为120s
         if (AccessPolicyType.PRIVATE == storage.getAccessPolicy() && !storage.isLocal()) {
         if (AccessPolicyType.PRIVATE == storage.getAccessPolicy() && !storage.isLocal()) {
-            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
+            oss.setUrl(storage.createPresignedGetUrl(oss.getFileName(), Duration.ofSeconds(120)));
         }
         }
         return oss;
         return oss;
     }
     }