mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-17 12:35:07 +08:00
使用 minio client 替代 amazon 客户端,进行 S3 的对接
This commit is contained in:
@ -20,15 +20,17 @@ public interface FileClient {
|
||||
* @param content 文件流
|
||||
* @param path 相对路径
|
||||
* @return 完整路径,即 HTTP 访问地址
|
||||
* @throws Exception 上传文件时,抛出 Exception 异常
|
||||
*/
|
||||
String upload(byte[] content, String path);
|
||||
String upload(byte[] content, String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param path 相对路径
|
||||
* @throws Exception 删除文件时,抛出 Exception 异常
|
||||
*/
|
||||
void delete(String path);
|
||||
void delete(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得文件的内容
|
||||
@ -36,6 +38,6 @@ public interface FileClient {
|
||||
* @param path 相对路径
|
||||
* @return 文件的内容
|
||||
*/
|
||||
byte[] getContent(String path);
|
||||
byte[] getContent(String path) throws Exception;
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,14 @@
|
||||
package cn.iocoder.yudao.framework.file.core.client.s3;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
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.model.DeleteObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
import io.minio.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_QINIU;
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
|
||||
|
||||
/**
|
||||
* 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||
@ -24,7 +19,7 @@ import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.
|
||||
*/
|
||||
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||
|
||||
private S3Client client;
|
||||
private MinioClient client;
|
||||
|
||||
public S3FileClient(Long id, S3FileClientConfig config) {
|
||||
super(id, config);
|
||||
@ -34,34 +29,27 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||
protected void doInit() {
|
||||
// 补全 domain
|
||||
if (StrUtil.isEmpty(config.getDomain())) {
|
||||
config.setDomain(createDomain());
|
||||
config.setDomain(buildDomain());
|
||||
}
|
||||
// 初始化客户端
|
||||
client = S3Client.builder()
|
||||
.serviceConfiguration(sb -> sb.pathStyleAccessEnabled(false) // 关闭路径风格
|
||||
.chunkedEncodingEnabled(false)) // 禁用 chunk
|
||||
.endpointOverride(createURI()) // 上传地址
|
||||
.region(Region.of(config.getRegion())) // Region
|
||||
.credentialsProvider(StaticCredentialsProvider.create( // 认证密钥
|
||||
AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())))
|
||||
.overrideConfiguration(cb -> cb.addExecutionInterceptor(new S3ModifyPathInterceptor(config.getBucket())))
|
||||
client = MinioClient.builder()
|
||||
.endpoint(buildEndpointURL()) // Endpoint URL
|
||||
.region(buildRegion()) // Region
|
||||
.credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 endpoint 构建调用云服务的 URI 地址
|
||||
* 基于 endpoint 构建调用云服务的 URL 地址
|
||||
*
|
||||
* @return URI 地址
|
||||
*/
|
||||
private URI createURI() {
|
||||
String uri;
|
||||
// 如果是七牛,无需拼接 bucket
|
||||
if (config.getEndpoint().contains(ENDPOINT_QINIU)) {
|
||||
uri = StrUtil.format("https://{}", config.getEndpoint());
|
||||
} else {
|
||||
uri = StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||
private String buildEndpointURL() {
|
||||
// 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
|
||||
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
|
||||
return config.getEndpoint();
|
||||
}
|
||||
return URI.create(uri);
|
||||
return StrUtil.format("https://{}", config.getEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,35 +57,56 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||
*
|
||||
* @return Domain 地址
|
||||
*/
|
||||
private String createDomain() {
|
||||
private String buildDomain() {
|
||||
// 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
|
||||
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
|
||||
return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
|
||||
}
|
||||
// 阿里云、腾讯云、华为云都适合。七牛云比较特殊,必须有自定义域名
|
||||
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 bucket 构建 region 地区
|
||||
*
|
||||
* @return region 地区
|
||||
*/
|
||||
private String buildRegion() {
|
||||
// 阿里云必须有 region,否则会报错
|
||||
if (config.getEndpoint().contains(ENDPOINT_ALIYUN)) {
|
||||
return StrUtil.subBefore(config.getEndpoint(), '.', false)
|
||||
.replaceAll("-internal", ""); // 去除内网 Endpoint 的后缀
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path) {
|
||||
public String upload(byte[] content, String path) throws Exception {
|
||||
// 执行上传
|
||||
PutObjectRequest.Builder request = PutObjectRequest.builder()
|
||||
client.putObject(PutObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
client.putObject(request.build(), RequestBody.fromBytes(content));
|
||||
.object(path) // 相对路径作为 key
|
||||
.stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
|
||||
.build());
|
||||
// 拼接返回路径
|
||||
return config.getDomain() + "/" + path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
DeleteObjectRequest.Builder request = DeleteObjectRequest.builder()
|
||||
public void delete(String path) throws Exception {
|
||||
client.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
client.deleteObject(request.build());
|
||||
.object(path) // 相对路径作为 key
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
GetObjectRequest.Builder request = GetObjectRequest.builder()
|
||||
public byte[] getContent(String path) throws Exception {
|
||||
GetObjectResponse response = client.getObject(GetObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
return client.getObjectAsBytes(request.build()).asByteArray();
|
||||
.object(path) // 相对路径作为 key
|
||||
.build());
|
||||
return IoUtil.readBytes(response);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,14 +18,15 @@ import javax.validation.constraints.NotNull;
|
||||
public class S3FileClientConfig implements FileClientConfig {
|
||||
|
||||
public static final String ENDPOINT_QINIU = "qiniucs.com";
|
||||
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
|
||||
|
||||
/**
|
||||
* 节点地址
|
||||
* 1. MinIO:
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31837.html
|
||||
* 3. 腾讯云:
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/6224
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://developer.huaweicloud.com/endpoint?OBS
|
||||
*/
|
||||
@NotNull(message = "endpoint 不能为空")
|
||||
private String endpoint;
|
||||
@ -35,19 +36,15 @@ public class S3FileClientConfig implements FileClientConfig {
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31836.html
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html
|
||||
*/
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
/**
|
||||
* 区域
|
||||
* 1. MinIO:
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31837.html
|
||||
* 3. 腾讯云:
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname
|
||||
* 5. 华为云:
|
||||
*/
|
||||
@NotNull(message = "region 不能为空")
|
||||
// @NotNull(message = "region 不能为空")
|
||||
@Deprecated
|
||||
private String region;
|
||||
/**
|
||||
* 存储 Bucket
|
||||
@ -58,10 +55,10 @@ public class S3FileClientConfig implements FileClientConfig {
|
||||
/**
|
||||
* 访问 Key
|
||||
* 1. MinIO:
|
||||
* 2. 阿里云:
|
||||
* 2. 阿里云:https://ram.console.aliyun.com/manage/ak
|
||||
* 3. 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||
* 4. 七牛云:https://portal.qiniu.com/user/key
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://support.huaweicloud.com/qs-obs/obs_qs_0005.html
|
||||
*/
|
||||
@NotNull(message = "accessKey 不能为空")
|
||||
private String accessKey;
|
||||
|
@ -1,36 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.file.core.client.s3;
|
||||
|
||||
import software.amazon.awssdk.core.interceptor.Context;
|
||||
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
|
||||
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
|
||||
import software.amazon.awssdk.http.SdkHttpRequest;
|
||||
|
||||
/**
|
||||
* S3 修改路径的拦截器,移除多余的 Bucket 前缀。
|
||||
* 如果不使用该拦截器,希望上传的路径是 /tudou.jpg 时,会被添加成 /bucket/tudou.jpg
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class S3ModifyPathInterceptor implements ExecutionInterceptor {
|
||||
|
||||
private final String bucket;
|
||||
|
||||
public S3ModifyPathInterceptor(String bucket) {
|
||||
this.bucket = "/" + bucket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
|
||||
SdkHttpRequest request = context.httpRequest();
|
||||
SdkHttpRequest.Builder rb = SdkHttpRequest.builder().protocol(request.protocol()).host(request.host()).port(request.port())
|
||||
.method(request.method()).headers(request.headers()).rawQueryParameters(request.rawQueryParameters());
|
||||
// 移除 path 前的 bucket 路径
|
||||
if (request.encodedPath().startsWith(bucket)) {
|
||||
rb.encodedPath(request.encodedPath().substring(bucket.length()));
|
||||
} else {
|
||||
rb.encodedPath(request.encodedPath());
|
||||
}
|
||||
return rb.build();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user