提交 | 用户 | 时间
|
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 |
} |