Преглед изворни кода

Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
YunaiV пре 1 година
родитељ
комит
2da16930ae

+ 3 - 2
yudao-module-infra/yudao-module-infra-biz/pom.xml

@@ -115,9 +115,10 @@
             <groupId>com.jcraft</groupId>
             <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
         </dependency>
+        <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
         <dependency>
-            <groupId>com.amazonaws</groupId>
-            <artifactId>aws-java-sdk-s3</artifactId><!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3</artifactId>
         </dependency>
 
         <dependency>

+ 91 - 65
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java

@@ -4,29 +4,31 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
-import com.amazonaws.HttpMethod;
-import com.amazonaws.auth.AWSStaticCredentialsProvider;
-import com.amazonaws.auth.BasicAWSCredentials;
-import com.amazonaws.client.builder.AwsClientBuilder;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.AmazonS3ClientBuilder;
-import com.amazonaws.services.s3.model.ObjectMetadata;
-import com.amazonaws.services.s3.model.S3Object;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
 
-import java.io.ByteArrayInputStream;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
+import java.net.URI;
+import java.time.Duration;
 
 /**
  * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
- * <p>
- * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
  *
  * @author 芋道源码
  */
 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
 
-    private AmazonS3Client client;
+    private S3Client client;
+    private S3Presigner presigner;
 
     public S3FileClient(Long id, S3FileClientConfig config) {
         super(id, config);
@@ -38,31 +40,78 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
         if (StrUtil.isEmpty(config.getDomain())) {
             config.setDomain(buildDomain());
         }
-        // 初始化客户端
-        client = (AmazonS3Client)AmazonS3ClientBuilder.standard()
-                .withCredentials(buildCredentials())
-                .withEndpointConfiguration(buildEndpointConfiguration())
+        // 初始化 S3 客户端
+        Region region = Region.of("us-east-1"); // 必须填,但填什么都行,常见的值有 "us-east-1",不填会报错
+        AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
+                AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
+        URI endpoint = URI.create(buildEndpoint());
+        S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
+                .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess())).build();
+        client = S3Client.builder()
+                .credentialsProvider(credentialsProvider)
+                .region(region)
+                .endpointOverride(endpoint)
+                .serviceConfiguration(serviceConfiguration)
+                .build();
+        presigner = S3Presigner.builder()
+                .credentialsProvider(credentialsProvider)
+                .region(region)
+                .endpointOverride(endpoint)
+                .serviceConfiguration(serviceConfiguration)
                 .build();
     }
 
-    /**
-     * 基于 config 秘钥,构建 S3 客户端的认证信息
-     *
-     * @return S3 客户端的认证信息
-     */
-    private AWSStaticCredentialsProvider buildCredentials() {
-        return new AWSStaticCredentialsProvider(
-                new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret()));
+    @Override
+    public String upload(byte[] content, String path, String type) {
+        // 构造 PutObjectRequest
+        PutObjectRequest putRequest = PutObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .contentType(type)
+                .contentLength((long) content.length)
+                .build();
+        // 上传文件
+        client.putObject(putRequest, RequestBody.fromBytes(content));
+        // 拼接返回路径
+        return config.getDomain() + "/" + path;
+    }
+
+    @Override
+    public void delete(String path) {
+        DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .build();
+        client.deleteObject(deleteRequest);
+    }
+
+    @Override
+    public byte[] getContent(String path) {
+        GetObjectRequest getRequest = GetObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .build();
+        return IoUtil.readBytes(client.getObject(getRequest));
+    }
+
+    @Override
+    public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) {
+        Duration expiration = Duration.ofHours(24);
+        return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path);
     }
 
     /**
-     * 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint
+     * 生成动态的预签名上传 URL
      *
-     * @return  S3 客户端的 EndpointConfiguration 配置
+     * @param path     相对路径
+     * @param expiration 过期时间
+     * @return 生成的上传 URL
      */
-    private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() {
-        return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(),
-                null); // 无需设置 region
+    private String getPresignedUrl(String path, Duration expiration) {
+        return presigner.presignPutObject(PutObjectPresignRequest.builder()
+                .signatureDuration(expiration)
+                .putObjectRequest(b -> b.bucket(config.getBucket()).key(path))
+                .build()).url().toString();
     }
 
     /**
@@ -79,40 +128,17 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
         return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
     }
 
-    @Override
-    public String upload(byte[] content, String path, String type) throws Exception {
-        // 元数据,主要用于设置文件类型
-        ObjectMetadata objectMetadata = new ObjectMetadata();
-        objectMetadata.setContentType(type);
-        objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志
-        // 执行上传
-        client.putObject(config.getBucket(),
-                path, // 相对路径
-                new ByteArrayInputStream(content), // 文件内容
-                objectMetadata);
-
-        // 拼接返回路径
-        return config.getDomain() + "/" + path;
-    }
-
-    @Override
-    public void delete(String path) throws Exception {
-        client.deleteObject(config.getBucket(), path);
-    }
-
-    @Override
-    public byte[] getContent(String path) throws Exception {
-        S3Object tempS3Object = client.getObject(config.getBucket(), path);
-        return IoUtil.readBytes(tempS3Object.getObjectContent());
-    }
-
-    @Override
-    public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
-        // 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天
-        Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
-        // 生成上传 URL
-        String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT));
-        return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
+    /**
+     * 节点地址补全协议头
+     *
+     * @return 节点地址
+     */
+    private String buildEndpoint() {
+        // 如果已经是 http 或者 https,则不进行拼接
+        if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
+            return config.getEndpoint();
+        }
+        return StrUtil.format("https://{}", config.getEndpoint());
     }
 
 }

+ 6 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java

@@ -67,6 +67,12 @@ public class S3FileClientConfig implements FileClientConfig {
     @NotNull(message = "accessSecret 不能为空")
     private String accessSecret;
 
+    /**
+     * 是否启用 PathStyle 访问
+     */
+    @NotNull(message = "enablePathStyleAccess 不能为空")
+    private Boolean enablePathStyleAccess;
+
     @SuppressWarnings("RedundantIfStatement")
     @AssertTrue(message = "domain 不能为空")
     @JsonIgnore