diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml
index 8c4a9b586..73bb614cd 100644
--- a/yudao-framework/pom.xml
+++ b/yudao-framework/pom.xml
@@ -16,6 +16,7 @@
yudao-spring-boot-starter-web
yudao-spring-boot-starter-security
+ yudao-spring-boot-starter-file
yudao-spring-boot-starter-monitor
yudao-spring-boot-starter-protection
yudao-spring-boot-starter-config
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
index 9ff5b0583..d9a01747d 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
@@ -39,8 +39,8 @@ public class ValidationUtils {
&& PATTERN_XML_NCNAME.matcher(str).matches();
}
- public static void validate(Validator validator, Object reqVO, Class>... groups) {
- Set> constraintViolations = validator.validate(reqVO, groups);
+ public static void validate(Validator validator, Object object, Class>... groups) {
+ Set> constraintViolations = validator.validate(object, groups);
if (CollUtil.isNotEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
index 38b8875a1..292b6cf01 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.impl;
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.PayClient;
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 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;
/**
@@ -26,7 +24,6 @@ public abstract class AbstractPayClient implemen
* 渠道编号
*/
private final Long channelId;
-
/**
* 渠道编码
*/
@@ -40,10 +37,6 @@ public abstract class AbstractPayClient implemen
*/
protected Config config;
- protected Double calculateAmount(Long amount) {
- return amount / 100.0;
- }
-
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
this.channelId = channelId;
this.channelCode = channelCode;
@@ -75,6 +68,10 @@ public abstract class AbstractPayClient implemen
this.init();
}
+ protected Double calculateAmount(Long amount) {
+ return amount / 100.0;
+ }
+
@Override
public Long getId() {
return channelId;
@@ -96,12 +93,9 @@ public abstract class AbstractPayClient implemen
return result;
}
-
-
protected abstract PayCommonResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable;
-
@Override
public PayCommonResult unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
PayCommonResult resp;
@@ -115,7 +109,6 @@ public abstract class AbstractPayClient implemen
return resp;
}
-
protected abstract PayCommonResult doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/pom.xml b/yudao-framework/yudao-spring-boot-starter-file/pom.xml
new file mode 100644
index 000000000..126bf9b77
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+ yudao-framework
+ cn.iocoder.boot
+ ${revision}
+
+ 4.0.0
+ yudao-spring-boot-starter-file
+
+ ${project.artifactId}
+ 文件客户端,支持多种存储器
+ 1. file:本地磁盘
+ 2. ftp:FTP 服务器
+ 3. db:数据库
+ 4. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等
+
+ https://github.com/YunaiV/ruoyi-vue-pro
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+ software.amazon.awssdk
+ s3
+ 2.17.147
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
new file mode 100644
index 000000000..60e0fc51b
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
@@ -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);
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java
new file mode 100644
index 000000000..9461c05d8
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java
@@ -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 {
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java
new file mode 100644
index 000000000..13f104118
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java
@@ -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 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;
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java
new file mode 100644
index 000000000..09d98398e
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java
@@ -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 {
+
+ 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];
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java
new file mode 100644
index 000000000..858759c26
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java
@@ -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;
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java
new file mode 100644
index 000000000..2f4f19f84
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java
@@ -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();
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java
new file mode 100644
index 000000000..113f3e5e5
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位,避免 package 无法提交到 Git 仓库
+ */
+package cn.iocoder.yudao.framework.file.config;
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java
new file mode 100644
index 000000000..b6c1cd4a9
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位,避免 package 无法提交到 Git 仓库
+ */
+package cn.iocoder.yudao.framework.file.core.client;
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java
new file mode 100644
index 000000000..5db3ac699
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java
@@ -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);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java
new file mode 100644
index 000000000..e1da5db23
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位,避免 package 无法提交到 Git 仓库
+ */
+package cn.iocoder.yudao.framework.file.core.enums;
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg b/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg
new file mode 100644
index 000000000..1447283cd
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg differ