Commit ed53ca3de9283dc5ab2a31140b04e40ec4acf5ad
1 parent
446f601c
封装 yudao-spring-boot-starter-file 组件,初步实现 S3 对接云存储的能力
Showing
15 changed files
with
498 additions
and
13 deletions
yudao-framework/pom.xml
| ... | ... | @@ -16,6 +16,7 @@ |
| 16 | 16 | <module>yudao-spring-boot-starter-web</module> |
| 17 | 17 | <module>yudao-spring-boot-starter-security</module> |
| 18 | 18 | |
| 19 | + <module>yudao-spring-boot-starter-file</module> | |
| 19 | 20 | <module>yudao-spring-boot-starter-monitor</module> |
| 20 | 21 | <module>yudao-spring-boot-starter-protection</module> |
| 21 | 22 | <module>yudao-spring-boot-starter-config</module> | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
| ... | ... | @@ -39,8 +39,8 @@ public class ValidationUtils { |
| 39 | 39 | && PATTERN_XML_NCNAME.matcher(str).matches(); |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | - public static void validate(Validator validator, Object reqVO, Class<?>... groups) { | |
| 43 | - Set<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups); | |
| 42 | + public static void validate(Validator validator, Object object, Class<?>... groups) { | |
| 43 | + Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); | |
| 44 | 44 | if (CollUtil.isNotEmpty(constraintViolations)) { |
| 45 | 45 | throw new ConstraintViolationException(constraintViolations); |
| 46 | 46 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
| 1 | 1 | package cn.iocoder.yudao.framework.pay.core.client.impl; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.extra.validation.ValidationUtil; |
| 4 | -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | |
| 5 | 4 | import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; |
| 6 | 5 | import cn.iocoder.yudao.framework.pay.core.client.PayClient; |
| 7 | 6 | import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; |
| ... | ... | @@ -11,7 +10,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; |
| 11 | 10 | import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; |
| 12 | 11 | import lombok.extern.slf4j.Slf4j; |
| 13 | 12 | |
| 14 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 15 | 13 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |
| 16 | 14 | |
| 17 | 15 | /** |
| ... | ... | @@ -26,7 +24,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen |
| 26 | 24 | * 渠道编号 |
| 27 | 25 | */ |
| 28 | 26 | private final Long channelId; |
| 29 | - | |
| 30 | 27 | /** |
| 31 | 28 | * 渠道编码 |
| 32 | 29 | */ |
| ... | ... | @@ -40,10 +37,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen |
| 40 | 37 | */ |
| 41 | 38 | protected Config config; |
| 42 | 39 | |
| 43 | - protected Double calculateAmount(Long amount) { | |
| 44 | - return amount / 100.0; | |
| 45 | - } | |
| 46 | - | |
| 47 | 40 | public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) { |
| 48 | 41 | this.channelId = channelId; |
| 49 | 42 | this.channelCode = channelCode; |
| ... | ... | @@ -75,6 +68,10 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen |
| 75 | 68 | this.init(); |
| 76 | 69 | } |
| 77 | 70 | |
| 71 | + protected Double calculateAmount(Long amount) { | |
| 72 | + return amount / 100.0; | |
| 73 | + } | |
| 74 | + | |
| 78 | 75 | @Override |
| 79 | 76 | public Long getId() { |
| 80 | 77 | return channelId; |
| ... | ... | @@ -96,12 +93,9 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen |
| 96 | 93 | return result; |
| 97 | 94 | } |
| 98 | 95 | |
| 99 | - | |
| 100 | - | |
| 101 | 96 | protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) |
| 102 | 97 | throws Throwable; |
| 103 | 98 | |
| 104 | - | |
| 105 | 99 | @Override |
| 106 | 100 | public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { |
| 107 | 101 | PayCommonResult<PayRefundUnifiedRespDTO> resp; |
| ... | ... | @@ -115,7 +109,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen |
| 115 | 109 | return resp; |
| 116 | 110 | } |
| 117 | 111 | |
| 118 | - | |
| 119 | 112 | protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; |
| 120 | 113 | |
| 121 | 114 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | |
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 5 | + <parent> | |
| 6 | + <artifactId>yudao-framework</artifactId> | |
| 7 | + <groupId>cn.iocoder.boot</groupId> | |
| 8 | + <version>${revision}</version> | |
| 9 | + </parent> | |
| 10 | + <modelVersion>4.0.0</modelVersion> | |
| 11 | + <artifactId>yudao-spring-boot-starter-file</artifactId> | |
| 12 | + | |
| 13 | + <name>${project.artifactId}</name> | |
| 14 | + <description>文件客户端,支持多种存储器 | |
| 15 | + 1. file:本地磁盘 | |
| 16 | + 2. ftp:FTP 服务器 | |
| 17 | + 3. db:数据库 | |
| 18 | + 4. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等 | |
| 19 | + </description> | |
| 20 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | |
| 21 | + | |
| 22 | + <dependencies> | |
| 23 | + <dependency> | |
| 24 | + <groupId>cn.iocoder.boot</groupId> | |
| 25 | + <artifactId>yudao-common</artifactId> | |
| 26 | + </dependency> | |
| 27 | + | |
| 28 | + <!-- Spring 核心 --> | |
| 29 | + <dependency> | |
| 30 | + <groupId>org.springframework.boot</groupId> | |
| 31 | + <artifactId>spring-boot-starter</artifactId> | |
| 32 | + </dependency> | |
| 33 | + | |
| 34 | + <!-- 工具类相关 --> | |
| 35 | + <dependency> | |
| 36 | + <groupId>org.springframework.boot</groupId> | |
| 37 | + <artifactId>spring-boot-starter-validation</artifactId> | |
| 38 | + </dependency> | |
| 39 | + | |
| 40 | + <dependency> | |
| 41 | + <groupId>org.slf4j</groupId> | |
| 42 | + <artifactId>slf4j-api</artifactId> | |
| 43 | + </dependency> | |
| 44 | + | |
| 45 | + <dependency> | |
| 46 | + <groupId>com.fasterxml.jackson.core</groupId> | |
| 47 | + <artifactId>jackson-databind</artifactId> | |
| 48 | + </dependency> | |
| 49 | + <dependency> | |
| 50 | + <groupId>com.fasterxml.jackson.core</groupId> | |
| 51 | + <artifactId>jackson-core</artifactId> | |
| 52 | + </dependency> | |
| 53 | + | |
| 54 | + <!-- 三方云服务相关 --> | |
| 55 | + <dependency> | |
| 56 | + <groupId>software.amazon.awssdk</groupId> | |
| 57 | + <artifactId>s3</artifactId> | |
| 58 | + <version>2.17.147</version> | |
| 59 | + </dependency> | |
| 60 | + | |
| 61 | + <!-- Test 测试相关 --> | |
| 62 | + <dependency> | |
| 63 | + <groupId>cn.iocoder.boot</groupId> | |
| 64 | + <artifactId>yudao-spring-boot-starter-test</artifactId> | |
| 65 | + <scope>test</scope> | |
| 66 | + </dependency> | |
| 67 | + </dependencies> | |
| 68 | + | |
| 69 | +</project> | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * 文件客户端 | |
| 5 | + * | |
| 6 | + * @author 芋道源码 | |
| 7 | + */ | |
| 8 | +public interface FileClient { | |
| 9 | + | |
| 10 | + /** | |
| 11 | + * 获得客户端编号 | |
| 12 | + * | |
| 13 | + * @return 客户端编号 | |
| 14 | + */ | |
| 15 | + Long getId(); | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * 上传文件 | |
| 19 | + * | |
| 20 | + * @param content 文件流 | |
| 21 | + * @param path 相对路径 | |
| 22 | + * @return 完整路径,即 HTTP 访问地址 | |
| 23 | + */ | |
| 24 | + String upload(byte[] content, String path); | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 删除文件 | |
| 28 | + * | |
| 29 | + * @param path 相对路径 | |
| 30 | + */ | |
| 31 | + void delete(String path); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 获得文件的内容 | |
| 35 | + * | |
| 36 | + * @param path 相对路径 | |
| 37 | + * @return 文件的内容 | |
| 38 | + */ | |
| 39 | + byte[] getContent(String path); | |
| 40 | + | |
| 41 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.annotation.JsonTypeInfo; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 文件客户端的配置 | |
| 7 | + * 不同实现的客户端,需要不同的配置,通过子类来定义 | |
| 8 | + * | |
| 9 | + * @author 芋道源码 | |
| 10 | + */ | |
| 11 | +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) | |
| 12 | +// @JsonTypeInfo 注解的作用,Jackson 多态 | |
| 13 | +// 1. 序列化到时数据库时,增加 @class 属性。 | |
| 14 | +// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 | |
| 15 | +public interface FileClientConfig { | |
| 16 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client.impl; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.file.core.client.FileClient; | |
| 4 | +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; | |
| 5 | +import lombok.extern.slf4j.Slf4j; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 文件客户端的抽象类,提供模板方法,减少子类的冗余代码 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Slf4j | |
| 13 | +public abstract class AbstractFileClient<Config extends FileClientConfig> implements FileClient { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 配置编号 | |
| 17 | + */ | |
| 18 | + private final Long id; | |
| 19 | + /** | |
| 20 | + * 文件配置 | |
| 21 | + */ | |
| 22 | + protected Config config; | |
| 23 | + | |
| 24 | + public AbstractFileClient(Long id, Config config) { | |
| 25 | + this.id = id; | |
| 26 | + this.config = config; | |
| 27 | + } | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 初始化 | |
| 31 | + */ | |
| 32 | + public final void init() { | |
| 33 | + doInit(); | |
| 34 | + log.info("[init][配置({}) 初始化完成]", config); | |
| 35 | + } | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 自定义初始化 | |
| 39 | + */ | |
| 40 | + protected abstract void doInit(); | |
| 41 | + | |
| 42 | + public final void refresh(Config config) { | |
| 43 | + // 判断是否更新 | |
| 44 | + if (config.equals(this.config)) { | |
| 45 | + return; | |
| 46 | + } | |
| 47 | + log.info("[refresh][配置({})发生变化,重新初始化]", config); | |
| 48 | + this.config = config; | |
| 49 | + // 初始化 | |
| 50 | + this.init(); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public Long getId() { | |
| 55 | + return id; | |
| 56 | + } | |
| 57 | + | |
| 58 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client.impl.s3; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; | |
| 5 | +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; | |
| 6 | +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; | |
| 7 | +import software.amazon.awssdk.core.sync.RequestBody; | |
| 8 | +import software.amazon.awssdk.regions.Region; | |
| 9 | +import software.amazon.awssdk.services.s3.S3Client; | |
| 10 | +import software.amazon.awssdk.services.s3.model.PutObjectRequest; | |
| 11 | + | |
| 12 | +import java.net.URI; | |
| 13 | + | |
| 14 | +import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 | |
| 18 | + * | |
| 19 | + * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | |
| 24 | + | |
| 25 | + private S3Client client; | |
| 26 | + | |
| 27 | + public S3FileClient(Long id, S3FileClientConfig config) { | |
| 28 | + super(id, config); | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + protected void doInit() { | |
| 33 | + // 补全 domain | |
| 34 | + if (StrUtil.isEmpty(config.getDomain())) { | |
| 35 | + config.setDomain(createDomain()); | |
| 36 | + } | |
| 37 | + // 初始化客户端 | |
| 38 | + client = S3Client.builder() | |
| 39 | + .serviceConfiguration(sb -> sb.pathStyleAccessEnabled(false) // 关闭路径风格 | |
| 40 | + .chunkedEncodingEnabled(false)) // 禁用 chunk | |
| 41 | + .endpointOverride(createURI()) // 上传地址 | |
| 42 | + .region(Region.of(config.getRegion())) // Region | |
| 43 | + .credentialsProvider(StaticCredentialsProvider.create( // 认证密钥 | |
| 44 | + AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()))) | |
| 45 | + .overrideConfiguration(cb -> cb.addExecutionInterceptor(new S3ModifyPathInterceptor(config.getBucket()))) | |
| 46 | + .build(); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 基于 endpoint 构建调用云服务的 URI 地址 | |
| 51 | + * | |
| 52 | + * @return URI 地址 | |
| 53 | + */ | |
| 54 | + private URI createURI() { | |
| 55 | + String uri; | |
| 56 | + // 如果是七牛,无需拼接 bucket | |
| 57 | + if (config.getEndpoint().contains(ENDPOINT_QINIU)) { | |
| 58 | + uri = StrUtil.format("https://{}", config.getEndpoint()); | |
| 59 | + } else { | |
| 60 | + uri = StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); | |
| 61 | + } | |
| 62 | + return URI.create(uri); | |
| 63 | + } | |
| 64 | + | |
| 65 | + /** | |
| 66 | + * 基于 bucket + endpoint 构建访问的 Domain 地址 | |
| 67 | + * | |
| 68 | + * @return Domain 地址 | |
| 69 | + */ | |
| 70 | + private String createDomain() { | |
| 71 | + return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public String upload(byte[] content, String path) { | |
| 76 | + // 执行上传 | |
| 77 | + PutObjectRequest.Builder request = PutObjectRequest.builder() | |
| 78 | + .bucket(config.getBucket()) // bucket 必须传递 | |
| 79 | + .key(path); // 相对路径作为 key | |
| 80 | + client.putObject(request.build(), RequestBody.fromBytes(content)); | |
| 81 | + // 拼接返回路径 | |
| 82 | + return config.getDomain() + "/" + path; | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + public void delete(String path) { | |
| 87 | + | |
| 88 | + } | |
| 89 | + | |
| 90 | + @Override | |
| 91 | + public byte[] getContent(String path) { | |
| 92 | + return new byte[0]; | |
| 93 | + } | |
| 94 | + | |
| 95 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client.impl.s3; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; | |
| 5 | +import lombok.Data; | |
| 6 | +import org.hibernate.validator.constraints.URL; | |
| 7 | + | |
| 8 | +import javax.validation.constraints.AssertTrue; | |
| 9 | +import javax.validation.constraints.NotNull; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * S3 文件客户端的配置类 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +@Data | |
| 17 | +public class S3FileClientConfig implements FileClientConfig { | |
| 18 | + | |
| 19 | + public static final String ENDPOINT_QINIU = "qiniucs.com"; | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * 节点地址 | |
| 23 | + * 1. MinIO: | |
| 24 | + * 2. 阿里云:https://help.aliyun.com/document_detail/31837.html | |
| 25 | + * 3. 腾讯云: | |
| 26 | + * 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname | |
| 27 | + * 5. 华为云: | |
| 28 | + */ | |
| 29 | + @NotNull(message = "endpoint 不能为空") | |
| 30 | + private String endpoint; | |
| 31 | + /** | |
| 32 | + * 自定义域名 | |
| 33 | + * 1. MinIO: | |
| 34 | + * 2. 阿里云:https://help.aliyun.com/document_detail/31836.html | |
| 35 | + * 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142 | |
| 36 | + * 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name | |
| 37 | + * 5. 华为云: | |
| 38 | + */ | |
| 39 | + @URL(message = "domain 必须是 URL 格式") | |
| 40 | + private String domain; | |
| 41 | + /** | |
| 42 | + * 区域 | |
| 43 | + * 1. MinIO: | |
| 44 | + * 2. 阿里云:https://help.aliyun.com/document_detail/31837.html | |
| 45 | + * 3. 腾讯云: | |
| 46 | + * 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname | |
| 47 | + * 5. 华为云: | |
| 48 | + */ | |
| 49 | + @NotNull(message = "region 不能为空") | |
| 50 | + private String region; | |
| 51 | + /** | |
| 52 | + * 存储 Bucket | |
| 53 | + */ | |
| 54 | + @NotNull(message = "bucket 不能为空") | |
| 55 | + private String bucket; | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 访问 Key | |
| 59 | + * 1. MinIO: | |
| 60 | + * 2. 阿里云: | |
| 61 | + * 3. 腾讯云:https://console.cloud.tencent.com/cam/capi | |
| 62 | + * 4. 七牛云:https://portal.qiniu.com/user/key | |
| 63 | + * 5. 华为云: | |
| 64 | + */ | |
| 65 | + @NotNull(message = "accessKey 不能为空") | |
| 66 | + private String accessKey; | |
| 67 | + /** | |
| 68 | + * 访问 Secret | |
| 69 | + */ | |
| 70 | + @NotNull(message = "accessSecret 不能为空") | |
| 71 | + private String accessSecret; | |
| 72 | + | |
| 73 | + @AssertTrue(message = "domain 不能为空") | |
| 74 | + @SuppressWarnings("RedundantIfStatement") | |
| 75 | + public boolean isDomainValid() { | |
| 76 | + // 如果是七牛,必须带有 domain | |
| 77 | + if (endpoint.contains(ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { | |
| 78 | + return false; | |
| 79 | + } | |
| 80 | + return true; | |
| 81 | + } | |
| 82 | + | |
| 83 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client.impl.s3; | |
| 2 | + | |
| 3 | +import software.amazon.awssdk.core.interceptor.Context; | |
| 4 | +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; | |
| 5 | +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; | |
| 6 | +import software.amazon.awssdk.http.SdkHttpRequest; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * S3 修改路径的拦截器,移除多余的 Bucket 前缀。 | |
| 10 | + * 如果不使用该拦截器,希望上传的路径是 /tudou.jpg 时,会被添加成 /bucket/tudou.jpg | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +public class S3ModifyPathInterceptor implements ExecutionInterceptor { | |
| 15 | + | |
| 16 | + private final String bucket; | |
| 17 | + | |
| 18 | + public S3ModifyPathInterceptor(String bucket) { | |
| 19 | + this.bucket = "/" + bucket; | |
| 20 | + } | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { | |
| 24 | + SdkHttpRequest request = context.httpRequest(); | |
| 25 | + SdkHttpRequest.Builder rb = SdkHttpRequest.builder().protocol(request.protocol()).host(request.host()).port(request.port()) | |
| 26 | + .method(request.method()).headers(request.headers()).rawQueryParameters(request.rawQueryParameters()); | |
| 27 | + // 移除 path 前的 bucket 路径 | |
| 28 | + if (request.encodedPath().startsWith(bucket)) { | |
| 29 | + rb.encodedPath(request.encodedPath().substring(bucket.length())); | |
| 30 | + } else { | |
| 31 | + rb.encodedPath(request.encodedPath()); | |
| 32 | + } | |
| 33 | + return rb.build(); | |
| 34 | + } | |
| 35 | + | |
| 36 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java
0 → 100644
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java
0 → 100644
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.file.core.client.s3; | |
| 2 | + | |
| 3 | +import cn.hutool.core.io.resource.ResourceUtil; | |
| 4 | +import cn.hutool.core.util.IdUtil; | |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 6 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 7 | +import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClient; | |
| 8 | +import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig; | |
| 9 | +import org.junit.jupiter.api.Disabled; | |
| 10 | +import org.junit.jupiter.api.Test; | |
| 11 | + | |
| 12 | +import javax.validation.Validation; | |
| 13 | + | |
| 14 | +public class S3FileClientTest { | |
| 15 | + | |
| 16 | + @Test | |
| 17 | + @Disabled // 阿里云 OSS,如果要集成测试,可以注释本行 | |
| 18 | + public void testAliyun() { | |
| 19 | + S3FileClientConfig config = new S3FileClientConfig(); | |
| 20 | + // 配置成你自己的 | |
| 21 | + config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY")); | |
| 22 | + config.setAccessSecret(System.getenv("ALIYUN_SECRET_KEY")); | |
| 23 | + config.setBucket("yunai-aoteman"); | |
| 24 | + config.setDomain(null); // 如果有自定义域名,则可以设置。http://ali-oss.iocoder.cn | |
| 25 | + // 默认北京的 endpoint | |
| 26 | + config.setEndpoint("oss-cn-beijing.aliyuncs.com"); | |
| 27 | + | |
| 28 | + // 执行上传 | |
| 29 | + testExecuteUpload(config); | |
| 30 | + } | |
| 31 | + | |
| 32 | + @Test | |
| 33 | + @Disabled // 腾讯云 COS,如果要集成测试,可以注释本行 | |
| 34 | + public void testQCloud() { | |
| 35 | + S3FileClientConfig config = new S3FileClientConfig(); | |
| 36 | + // 配置成你自己的 | |
| 37 | + config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY")); | |
| 38 | + config.setAccessSecret(System.getenv("QCLOUD_SECRET_KEY")); | |
| 39 | + config.setBucket("aoteman-1255880240"); | |
| 40 | + config.setDomain(null); // 如果有自定义域名,则可以设置。http://tengxun-oss.iocoder.cn | |
| 41 | + // 默认上海的 endpoint | |
| 42 | + config.setEndpoint("cos.ap-shanghai.myqcloud.com"); | |
| 43 | + config.setRegion("ap-shanghai"); | |
| 44 | + | |
| 45 | + // 执行上传 | |
| 46 | + testExecuteUpload(config); | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Test | |
| 50 | + @Disabled // 七牛云存储,如果要集成测试,可以注释本行 | |
| 51 | + public void testQiniu() { | |
| 52 | + S3FileClientConfig config = new S3FileClientConfig(); | |
| 53 | + // 配置成你自己的 | |
| 54 | + config.setAccessKey(System.getenv("QINIU_ACCESS_KEY")); | |
| 55 | + config.setAccessSecret(System.getenv("QINIU_SECRET_KEY")); | |
| 56 | + config.setBucket("s3-test-yudao"); | |
| 57 | + config.setDomain("http://r8oo8po1q.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn | |
| 58 | + // 默认上海的 endpoint | |
| 59 | + config.setEndpoint("s3-cn-south-1.qiniucs.com"); | |
| 60 | + | |
| 61 | + // 执行上传 | |
| 62 | + testExecuteUpload(config); | |
| 63 | + } | |
| 64 | + | |
| 65 | + private void testExecuteUpload(S3FileClientConfig config) { | |
| 66 | + // 补全配置 | |
| 67 | + if (config.getRegion() == null) { | |
| 68 | + config.setRegion(StrUtil.subBefore(config.getEndpoint(), '.', false)); | |
| 69 | + } | |
| 70 | + ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); | |
| 71 | + // 创建 Client | |
| 72 | + S3FileClient client = new S3FileClient(0L, config); | |
| 73 | + client.init(); | |
| 74 | + // 上传文件 | |
| 75 | + String path = IdUtil.fastSimpleUUID() + ".jpg"; | |
| 76 | + byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); | |
| 77 | + String fullPath = client.upload(content, path); | |
| 78 | + System.out.println("访问地址:" + fullPath); | |
| 79 | + } | |
| 80 | + | |
| 81 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java
0 → 100644
yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg
0 → 100644
18 KB