封装 yudao-spring-boot-starter-file 组件,初步实现 S3 对接云存储的能力
This commit is contained in:
parent
446f601c8a
commit
ed53ca3de9
|
@ -16,6 +16,7 @@
|
||||||
<module>yudao-spring-boot-starter-web</module>
|
<module>yudao-spring-boot-starter-web</module>
|
||||||
<module>yudao-spring-boot-starter-security</module>
|
<module>yudao-spring-boot-starter-security</module>
|
||||||
|
|
||||||
|
<module>yudao-spring-boot-starter-file</module>
|
||||||
<module>yudao-spring-boot-starter-monitor</module>
|
<module>yudao-spring-boot-starter-monitor</module>
|
||||||
<module>yudao-spring-boot-starter-protection</module>
|
<module>yudao-spring-boot-starter-protection</module>
|
||||||
<module>yudao-spring-boot-starter-config</module>
|
<module>yudao-spring-boot-starter-config</module>
|
||||||
|
|
|
@ -39,8 +39,8 @@ public class ValidationUtils {
|
||||||
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void validate(Validator validator, Object reqVO, Class<?>... groups) {
|
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups);
|
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||||
if (CollUtil.isNotEmpty(constraintViolations)) {
|
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||||
throw new ConstraintViolationException(constraintViolations);
|
throw new ConstraintViolationException(constraintViolations);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||||
|
|
||||||
import cn.hutool.extra.validation.ValidationUtil;
|
import cn.hutool.extra.validation.ValidationUtil;
|
||||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||||
|
@ -11,7 +10,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +24,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||||
* 渠道编号
|
* 渠道编号
|
||||||
*/
|
*/
|
||||||
private final Long channelId;
|
private final Long channelId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道编码
|
* 渠道编码
|
||||||
*/
|
*/
|
||||||
|
@ -40,10 +37,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||||
*/
|
*/
|
||||||
protected Config config;
|
protected Config config;
|
||||||
|
|
||||||
protected Double calculateAmount(Long amount) {
|
|
||||||
return amount / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
|
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
|
||||||
this.channelId = channelId;
|
this.channelId = channelId;
|
||||||
this.channelCode = channelCode;
|
this.channelCode = channelCode;
|
||||||
|
@ -75,6 +68,10 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Double calculateAmount(Long amount) {
|
||||||
|
return amount / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return channelId;
|
return channelId;
|
||||||
|
@ -96,12 +93,9 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
||||||
throws Throwable;
|
throws Throwable;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||||
PayCommonResult<PayRefundUnifiedRespDTO> resp;
|
PayCommonResult<PayRefundUnifiedRespDTO> resp;
|
||||||
|
@ -115,7 +109,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>yudao-framework</artifactId>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yudao-spring-boot-starter-file</artifactId>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>文件客户端,支持多种存储器
|
||||||
|
1. file:本地磁盘
|
||||||
|
2. ftp:FTP 服务器
|
||||||
|
3. db:数据库
|
||||||
|
4. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等
|
||||||
|
</description>
|
||||||
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring 核心 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3</artifactId>
|
||||||
|
<version>2.17.147</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,41 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件客户端
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface FileClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得客户端编号
|
||||||
|
*
|
||||||
|
* @return 客户端编号
|
||||||
|
*/
|
||||||
|
Long getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param content 文件流
|
||||||
|
* @param path 相对路径
|
||||||
|
* @return 完整路径,即 HTTP 访问地址
|
||||||
|
*/
|
||||||
|
String upload(byte[] content, String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*
|
||||||
|
* @param path 相对路径
|
||||||
|
*/
|
||||||
|
void delete(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得文件的内容
|
||||||
|
*
|
||||||
|
* @param path 相对路径
|
||||||
|
* @return 文件的内容
|
||||||
|
*/
|
||||||
|
byte[] getContent(String path);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件客户端的配置
|
||||||
|
* 不同实现的客户端,需要不同的配置,通过子类来定义
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||||
|
// @JsonTypeInfo 注解的作用,Jackson 多态
|
||||||
|
// 1. 序列化到时数据库时,增加 @class 属性。
|
||||||
|
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
|
||||||
|
public interface FileClientConfig {
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client.impl;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.FileClient;
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractFileClient<Config extends FileClientConfig> implements FileClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置编号
|
||||||
|
*/
|
||||||
|
private final Long id;
|
||||||
|
/**
|
||||||
|
* 文件配置
|
||||||
|
*/
|
||||||
|
protected Config config;
|
||||||
|
|
||||||
|
public AbstractFileClient(Long id, Config config) {
|
||||||
|
this.id = id;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
public final void init() {
|
||||||
|
doInit();
|
||||||
|
log.info("[init][配置({}) 初始化完成]", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义初始化
|
||||||
|
*/
|
||||||
|
protected abstract void doInit();
|
||||||
|
|
||||||
|
public final void refresh(Config config) {
|
||||||
|
// 判断是否更新
|
||||||
|
if (config.equals(this.config)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("[refresh][配置({})发生变化,重新初始化]", config);
|
||||||
|
this.config = config;
|
||||||
|
// 初始化
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client.impl.s3;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.impl.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.PutObjectRequest;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||||
|
*
|
||||||
|
* S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||||
|
|
||||||
|
private S3Client client;
|
||||||
|
|
||||||
|
public S3FileClient(Long id, S3FileClientConfig config) {
|
||||||
|
super(id, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doInit() {
|
||||||
|
// 补全 domain
|
||||||
|
if (StrUtil.isEmpty(config.getDomain())) {
|
||||||
|
config.setDomain(createDomain());
|
||||||
|
}
|
||||||
|
// 初始化客户端
|
||||||
|
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())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 endpoint 构建调用云服务的 URI 地址
|
||||||
|
*
|
||||||
|
* @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());
|
||||||
|
}
|
||||||
|
return URI.create(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 bucket + endpoint 构建访问的 Domain 地址
|
||||||
|
*
|
||||||
|
* @return Domain 地址
|
||||||
|
*/
|
||||||
|
private String createDomain() {
|
||||||
|
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String upload(byte[] content, String path) {
|
||||||
|
// 执行上传
|
||||||
|
PutObjectRequest.Builder request = PutObjectRequest.builder()
|
||||||
|
.bucket(config.getBucket()) // bucket 必须传递
|
||||||
|
.key(path); // 相对路径作为 key
|
||||||
|
client.putObject(request.build(), RequestBody.fromBytes(content));
|
||||||
|
// 拼接返回路径
|
||||||
|
return config.getDomain() + "/" + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getContent(String path) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client.impl.s3;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S3 文件客户端的配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class S3FileClientConfig implements FileClientConfig {
|
||||||
|
|
||||||
|
public static final String ENDPOINT_QINIU = "qiniucs.com";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点地址
|
||||||
|
* 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 = "endpoint 不能为空")
|
||||||
|
private String endpoint;
|
||||||
|
/**
|
||||||
|
* 自定义域名
|
||||||
|
* 1. MinIO:
|
||||||
|
* 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. 华为云:
|
||||||
|
*/
|
||||||
|
@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 不能为空")
|
||||||
|
private String region;
|
||||||
|
/**
|
||||||
|
* 存储 Bucket
|
||||||
|
*/
|
||||||
|
@NotNull(message = "bucket 不能为空")
|
||||||
|
private String bucket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问 Key
|
||||||
|
* 1. MinIO:
|
||||||
|
* 2. 阿里云:
|
||||||
|
* 3. 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||||
|
* 4. 七牛云:https://portal.qiniu.com/user/key
|
||||||
|
* 5. 华为云:
|
||||||
|
*/
|
||||||
|
@NotNull(message = "accessKey 不能为空")
|
||||||
|
private String accessKey;
|
||||||
|
/**
|
||||||
|
* 访问 Secret
|
||||||
|
*/
|
||||||
|
@NotNull(message = "accessSecret 不能为空")
|
||||||
|
private String accessSecret;
|
||||||
|
|
||||||
|
@AssertTrue(message = "domain 不能为空")
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
public boolean isDomainValid() {
|
||||||
|
// 如果是七牛,必须带有 domain
|
||||||
|
if (endpoint.contains(ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client.impl.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* 占位,避免 package 无法提交到 Git 仓库
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.framework.file.config;
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* 占位,避免 package 无法提交到 Git 仓库
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client;
|
|
@ -0,0 +1,81 @@
|
||||||
|
package cn.iocoder.yudao.framework.file.core.client.s3;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClient;
|
||||||
|
import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.validation.Validation;
|
||||||
|
|
||||||
|
public class S3FileClientTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // 阿里云 OSS,如果要集成测试,可以注释本行
|
||||||
|
public void testAliyun() {
|
||||||
|
S3FileClientConfig config = new S3FileClientConfig();
|
||||||
|
// 配置成你自己的
|
||||||
|
config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY"));
|
||||||
|
config.setAccessSecret(System.getenv("ALIYUN_SECRET_KEY"));
|
||||||
|
config.setBucket("yunai-aoteman");
|
||||||
|
config.setDomain(null); // 如果有自定义域名,则可以设置。http://ali-oss.iocoder.cn
|
||||||
|
// 默认北京的 endpoint
|
||||||
|
config.setEndpoint("oss-cn-beijing.aliyuncs.com");
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
testExecuteUpload(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // 腾讯云 COS,如果要集成测试,可以注释本行
|
||||||
|
public void testQCloud() {
|
||||||
|
S3FileClientConfig config = new S3FileClientConfig();
|
||||||
|
// 配置成你自己的
|
||||||
|
config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY"));
|
||||||
|
config.setAccessSecret(System.getenv("QCLOUD_SECRET_KEY"));
|
||||||
|
config.setBucket("aoteman-1255880240");
|
||||||
|
config.setDomain(null); // 如果有自定义域名,则可以设置。http://tengxun-oss.iocoder.cn
|
||||||
|
// 默认上海的 endpoint
|
||||||
|
config.setEndpoint("cos.ap-shanghai.myqcloud.com");
|
||||||
|
config.setRegion("ap-shanghai");
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
testExecuteUpload(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // 七牛云存储,如果要集成测试,可以注释本行
|
||||||
|
public void testQiniu() {
|
||||||
|
S3FileClientConfig config = new S3FileClientConfig();
|
||||||
|
// 配置成你自己的
|
||||||
|
config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
|
||||||
|
config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));
|
||||||
|
config.setBucket("s3-test-yudao");
|
||||||
|
config.setDomain("http://r8oo8po1q.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn
|
||||||
|
// 默认上海的 endpoint
|
||||||
|
config.setEndpoint("s3-cn-south-1.qiniucs.com");
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
testExecuteUpload(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testExecuteUpload(S3FileClientConfig config) {
|
||||||
|
// 补全配置
|
||||||
|
if (config.getRegion() == null) {
|
||||||
|
config.setRegion(StrUtil.subBefore(config.getEndpoint(), '.', false));
|
||||||
|
}
|
||||||
|
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
|
||||||
|
// 创建 Client
|
||||||
|
S3FileClient client = new S3FileClient(0L, config);
|
||||||
|
client.init();
|
||||||
|
// 上传文件
|
||||||
|
String path = IdUtil.fastSimpleUUID() + ".jpg";
|
||||||
|
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
||||||
|
String fullPath = client.upload(content, path);
|
||||||
|
System.out.println("访问地址:" + fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* 占位,避免 package 无法提交到 Git 仓库
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.framework.file.core.enums;
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Loading…
Reference in New Issue