潘志宝
4 天以前 af7bd200a95b9fc6b8b3f3fc603d612221e21fc7
提交 | 用户 | 时间
e7c126 1 package com.iailab.module.infra.framework.file.core.client.s3;
H 2
3 import cn.hutool.core.io.IoUtil;
4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.http.HttpUtil;
6 import com.iailab.module.infra.framework.file.core.client.AbstractFileClient;
7 import io.minio.*;
8 import io.minio.http.Method;
9
10 import java.io.ByteArrayInputStream;
11 import java.util.concurrent.TimeUnit;
12
13 /**
14  * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
15  * <p>
16  * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
17  *
18  * @author iailab
19  */
20 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
21
22     private MinioClient client;
23
24     public S3FileClient(Long id, S3FileClientConfig config) {
25         super(id, config);
26     }
27
28     @Override
29     protected void doInit() {
30         // 补全 domain
31         if (StrUtil.isEmpty(config.getDomain())) {
32             config.setDomain(buildDomain());
33         }
34         // 初始化客户端
35         client = MinioClient.builder()
36                 .endpoint(buildEndpointURL()) // Endpoint URL
37                 .region(buildRegion()) // Region
38                 .credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
39                 .build();
4a47e4 40         enableVirtualStyleEndpoint();
e7c126 41     }
H 42
43     /**
44      * 基于 endpoint 构建调用云服务的 URL 地址
45      *
46      * @return URI 地址
47      */
48     private String buildEndpointURL() {
49         // 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
50         if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
51             return config.getEndpoint();
52         }
53         return StrUtil.format("https://{}", config.getEndpoint());
54     }
55
56     /**
57      * 基于 bucket + endpoint 构建访问的 Domain 地址
58      *
59      * @return Domain 地址
60      */
61     private String buildDomain() {
62         // 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
63         if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
64             return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
65         }
66         // 阿里云、腾讯云、华为云都适合。七牛云比较特殊,必须有自定义域名
67         return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
68     }
69
70     /**
71      * 基于 bucket 构建 region 地区
72      *
73      * @return region 地区
74      */
75     private String buildRegion() {
76         // 阿里云必须有 region,否则会报错
77         if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_ALIYUN)) {
78             return StrUtil.subBefore(config.getEndpoint(), '.', false)
79                     .replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
80                     .replaceAll("https://", "");
81         }
82         // 腾讯云必须有 region,否则会报错
83         if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_TENCENT)) {
84             return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
85                     .replaceAll("." + S3FileClientConfig.ENDPOINT_TENCENT, ""); // 去除 Endpoint
86         }
87         return null;
88     }
89
4a47e4 90     /**
H 91      * 开启 VirtualStyle 模式
92      */
93     private void enableVirtualStyleEndpoint() {
94         if (StrUtil.containsAll(config.getEndpoint(),
95                 S3FileClientConfig.ENDPOINT_TENCENT, // 腾讯云 https://cloud.tencent.com/document/product/436/41284
96                 S3FileClientConfig.ENDPOINT_VOLCES)) { // 火山云 https://www.volcengine.com/docs/6349/1288493
97             client.enableVirtualStyleEndpoint();
98         }
99     }
100
101
e7c126 102     @Override
H 103     public String upload(byte[] content, String path, String type) throws Exception {
104         // 执行上传
105         client.putObject(PutObjectArgs.builder()
106                 .bucket(config.getBucket()) // bucket 必须传递
107                 .contentType(type)
108                 .object(path) // 相对路径作为 key
109                 .stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
110                 .build());
111         // 拼接返回路径
112         return config.getDomain() + "/" + path;
113     }
114
115     @Override
116     public void delete(String path) throws Exception {
117         client.removeObject(RemoveObjectArgs.builder()
118                 .bucket(config.getBucket()) // bucket 必须传递
119                 .object(path) // 相对路径作为 key
120                 .build());
121     }
122
123     @Override
124     public byte[] getContent(String path) throws Exception {
125         GetObjectResponse response = client.getObject(GetObjectArgs.builder()
126                 .bucket(config.getBucket()) // bucket 必须传递
127                 .object(path) // 相对路径作为 key
128                 .build());
129         return IoUtil.readBytes(response);
130     }
131
132     @Override
133     public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
134         String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
135                 .method(Method.PUT)
136                 .bucket(config.getBucket())
137                 .object(path)
138                 .expiry(10, TimeUnit.MINUTES) // 过期时间(秒数)取值范围:1 秒 ~ 7 天
139                 .build()
140         );
141         return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
142     }
143
144 }