From ed53ca3de9283dc5ab2a31140b04e40ec4acf5ad Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 13 Mar 2022 21:23:03 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=B0=81=E8=A3=85=20yudao-spring-boot-star?= =?UTF-8?q?ter-file=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20S3=20=E5=AF=B9=E6=8E=A5=E4=BA=91=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E7=9A=84=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-framework/pom.xml | 1 + .../util/validation/ValidationUtils.java | 4 +- .../core/client/impl/AbstractPayClient.java | 15 +-- .../yudao-spring-boot-starter-file/pom.xml | 69 +++++++++++++ .../file/core/client/FileClient.java | 41 ++++++++ .../file/core/client/FileClientConfig.java | 16 +++ .../core/client/impl/AbstractFileClient.java | 58 +++++++++++ .../core/client/impl/s3/S3FileClient.java | 95 ++++++++++++++++++ .../client/impl/s3/S3FileClientConfig.java | 83 +++++++++++++++ .../impl/s3/S3ModifyPathInterceptor.java | 36 +++++++ .../framework/file/config/package-info.java | 4 + .../file/core/client/package-info.java | 4 + .../file/core/client/s3/S3FileClientTest.java | 81 +++++++++++++++ .../file/core/enums/package-info.java | 4 + .../src/test/resources/file/erweima.jpg | Bin 0 -> 18385 bytes 15 files changed, 498 insertions(+), 13 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-file/pom.xml create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg 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 0000000000000000000000000000000000000000..1447283cdf1b49b51c1204a160e01cb789e957a8 GIT binary patch literal 18385 zcmb7s2Rzm9+yCL%A+xNq$sv0yBYV$7${yL72_=by>^(#Fv7$0Ev!kp?5wb^FWrg_P zpQHMH%lG#@|3|%E9nLwQao^YbdXMYm@crQ@7>SC4vH}bP0|vtYe_)5BFj*KJ6BGIi zzOcYwY&>jiEG%pSTwEMHLIOg<69gwt5D}A+5D}9SpEyBsiiDJ$f|8Pw@Z@Q#QxsHW z6qFRuK``LpJy_WI*x2|KL??(S{?A{B%`j3t3``76I0h*UlN19^igDNqI}3xsF@Ao* zj=u2laWQZRFtM=V;CVW5C*U2>{rvjEz{bSF!G+@;j=%`v;8_wl33v)+_Urjy|Kg$_ zEYOL&EWqgCnJzFG_?HFa?p@d+?8NuGcTew3GT`ns5n#fwFfbc17@A-w_F=eqSjKR; zC??8VnnQ-OJ&kBTmB9>~jf6qSOyt`gO6|87%2A|djZH9@yYjjWB&Ro!>ROqY1@<|Y zc|P4G(mthFSx+S#-!Mn8dyS3^ETUjdRAj&UIql~S&E5+R@66)&C29)F?Sy-m-fmrFjj`}75A z#U0`pyj^w}Jn#_4-||4(p0Hr(zTObg<@h{(A+|jGdrzzt^sksBg=A19CU2m;|q@V?(-Q-TUlUHCcE>k7!N!Q88=Kd@gvmHAV_*V?R zN)s3OQ!d<})?>Y&3!RfW5R+#OM#n&EN(jqjk3PEK0z7ZcUYu)B7S=2Vo-*QoNoCHn zw@GBdFntXheD%!tA`GJ$<5XH#(337KtVKju9-?hsD(4U;Fr8o=__^`&>jU+xHuPUx z)gHq2tu1#E?ICdG819;K7EXX(_bCpU`;0zj6^>9*kVL8@Z05s-n{0 z5%j%t)VuM_a-GwVRAceS^+OoF%pr_@=(cg-7kl#MVz(AuXj4h}TM+zD7v*5YWUw1@ zcugl%%y3{*?J(R2vfk|@2Y%eU_?uy-#btxv!)J9j8cAhuA=GGOPN}P*tT5q=3#n{* zS|l)0X{JLMwak(dp}5=#{o}@TlJ)l74~H__P1{z|61w{qU&{J% z?lfX7g*|Cfh8cW~y9<{3s{WQ)XGGX-5ek{~>uZz2sO?l>3nSq-^WKlL{%$~b=IpI+b3}vH?rA4U|84`l&A1d141Arr=m8v&nim7z^G_L#>Re*LqX;SlZY5E-z6<=Gjq!T(CYC3 z1z?bY*4?>XxivmIddt~qxxT9M5EgL=3knw(+^-$owB0!^5zRp&v3 zOMEV?+A*vsQ4cBh@!e8rzC_BHw%)w^RTLs^#lc*oKy8*iMJ$Z1y5Lz2j>(DE4c2(C z1sY;HZCRHO)CSxIDEFF@Axe##LN6W{_RQ@Y+*`khZ*3xu6YzjS>!Yv&@|8FBl$$ks zgo5-=I+Y?}bewNN$A);cz*AH4G+8(m0(6x}deZ z44=nrlCxEMavdMr+i2e>r%uztdFmgLCf*~yNE~k>P7S}infXyg0lA6GFx{7c{A3>0 zdQzUK#U?L9lTzVw-V(PZe#yx2S($hOV*!2F1vb%FO>aIK;j`Ll=Uys!$eJdPAJXii z?nRBMd`0gxL13K?+w1<CvsnKCQ)ZGB}`NLRl94D ztUvT$l%qFotTR|7%YHf-nc+8dHO2NjC|5E6a z6*#SyNur8-rPIKohUc4@C1U~YkOgYd9^N;PjBw^q<;BP$e2bi~jofm>l&1g)8U0|mE{Q4mqt~RertE&QOi!2o9(B~{F!a02`znU zM%Fhv-El$K6zpC`4-~CE;!Bdm``@Id1dtX&9(qL@UcYI$Ko-wcMMZ&+Z+3_5|!PQ8QI%Oh7#>n;oaizjrJ(G_ET9ua(s8Z2v-oc z{pewsq1IpP?n|vrTK9_7!eZ|E^>?|x>TX#LYFz2l4(+q)I~L(z)ZVOz`)7>z;=O;o zwUMIF+8E>oejL7iPS46{={XApISL{t%+GSzV#Zxh7) ztoBDKKfm5!gGKeZ2)Zf|%X(Fms&@90$8ub7l;#{++ zGl5-augrriUaXKX1!*5%H`?YDUuK;TX*mwwZq(HUb@*GVsNf0(7A@N}(bjj~Q8X#2 zmueeSKIAZ^q*g23Y+S}Vj{E^JZtAW}Rf&wLK@VSMSW1_Ylf$c%M6C(4@g~93TVUnuX#LAFYJR~59Ig~d7oXzcM6{x~h^X#KbW;?Gz#rnRFGqAO3g^ie6_UIzJ zDV!}p^*V9@RHO~eDK2@ z`>Qy08dCeZzgc<+?7tr)Y!nue<#`hpq&3WTx@z@m`)rK0%zZ_Eo6My4H?(fgITx!R z-=a8zMiqcYb4D_ApPJ>h@R}bMQr^b`~M6~TKSj}8@@ zs_BUqyR*rMJWMNv&YJI^KR$ui6+jSS)hojno+3PJfAuXA2eJPu+QMRu9sm&WfRh4^$3@mkm*J=oraIo z=Kt2&;nF39X`ht9ex4}CWisN(XYJ0<8;bAFUmu&lXnY-_j$&KNtk^Vs&>*vJ+?`Rq zFE~4}%(vD5!Z=Uz!#>`S+_k)saib8>eldNc%pb9f#dy(K@rJyUa}`^xde@xHa|)p; z=uRa75eZ>k=-NyEq7H_>+x%g4K}ua>pu25|HAVBfbh;W6%T?=Y$k}HL#NKe`mV3AC z5FmMsZ9ZE(DF~bS5dYnct;zwZy!Br&82TbhAoiNUY-h??36gyj;z{T3M$d3@k3(4Y zv5u;yko#6h>D|{EaFJf*Uu-FxEzE90_Zx>MgxMidzHPqXA*IWOG~iB45j>c_VViUE zvG9T=-o4}*tS{|Sovp2PXX6yuplTWD;KQ@~To(f|I zoR^kL>G8sbgR87N>P?$GO0&XPPde>Zr5eA^=yr2=0yk3)CI;$=1_yDq6T@MIcgZ_cvl=!*%6# z>7qWOEmQ7u4ZC3zRIgmZ=ae>xP74)zJ{MXtRil#PyQYd@?nXmU+i7I5EN=r8`oxCm zep@nEK_s_TB*2w?l>>i6QewHmoEmtqnDKS}JYP-E$N@C)pcfngkCJq@+wK$_UqKb+ zDTBA-vv~Q^h!{# zjb)X~Z@;tg`o(n?)%{R&HEX4%fQrh{;BA|BGD_iLQZwl89Z^lJD=`3av^M3bM=F@mdIJbt*}Jv?UJBIl_rUjQhAM22Gr zyKHppGf)baiCda)%1!EeFPY&J;ldRu%w|L$B4+Bs*2VZ^JD3@6Yk*5v>XMw)C?FjA zEp3T#d=*kZFh5eL)cC@ z`W$8Sps7z#mZgb|jgMOX++-)<$8CstucDcEhcv{aM+aF~P54pI1Z$6VNY0R%?3|zm zorS}NP9k-r*^}E`6h-^$AKqTa^1SaTJOVn2@g^bDw%qk8uE9E?w#X@Jj&|+$W;hhy zJ5EO z)FssO1~au>nB$aq0$!$@R?Mj=GCW*ta~Q2Pg7ac>mdMXw>!tiVz_dd!e;m z*mg7?5d3ZhjZs?C_~=W6@NSl_;IE)vky8y`^#Qse9dr=>8PGvKnASVfCftbGqS${$ zYMzbmAj5eGCXr+&E;XZfB};(z4`KZezMZnFVm4OxD;OQ+Ue#PLi}n0L<6&oc5r@=9 zbcv0R${(1hL;}0b<7hN$9JTM7?cG`0cvQwuD9}33uL%zeA{Fn9V81B5`)^QDn`KQA zi})`tkcG+sl25oeVT|!nGe8=2hO6erI6y*^7`k&f%V@cx_t%S^NJW6^pN-L&K`KS+`uiaTN|OhWd-%y!Gx_jp6_Jc(OG;( z`a#NfSp?~b?)SGujdK+xzWT3huf*F+NRFt39PGl&POFX4MH*lcnXh(73$@9A(yTlZ z=C6){VHmQfm7L)uuX?b8!eq)-n%r31;(eEbllGFjQ#Ex|ITNL3RJna#9HYL=9KHnK zBz`Qu%v20sV6k&#P@s64T|54EWAOuaAT7CGhqs^4U8!($(g*XW=kqNOXy)YQ9&tH6 z$`R0EpbKtvV)Sp$p{4hwl=f`8SjS_1^1XyZZTO_6Xq{*SQ3`P+?Q~Rl!h@t#!nn)l zu+}j>L+cwI>e|!hKUPYYZ{WQlw#Spz96Bd4p11t4Ygr~gYoO=>Gh|(9LzKE2D*s_h z!qI;9Nj~VAj=)i&du9L+79C`gJos@jJ_C~!R`l6itw5FH0~9z^Ip~4FLBY#x4mlki zCCLsXT#hyU9V8^SA;AoZ?emKn`m(Btu_q?B)TiBEi2yW6{X|19Xb((4N&jjOjj4v* zq;-|i=8xx|yJ}ynR|em`I_nREh-7{&Stf3o27^WpamZSZvicH{C3m;3HwBn1*)+QWUg;QnFR(^aWhTodzth2yBp6z6AGVw zwm}Xw6L`atAL5vkMdQa2Ik0Y#%HPN>E4*x9DB0%o?IJ!J6Li`E5GG=dB{lcOvjpN2 zfc66^!oUo*7Y{iISEe&tXSmbcld{}udQzmVYghYRTdj)9BDOurAD2C=DMGwC&FZ zJ!V2(O8pSFk8x6D3?p2zKZj%0ysV^hcyQ47L?~l66trQamw5|94wo_Y;$dicnvtY=Z7!!XT**3G)920ZUw%&)#9;0thF<^ z&SeLG$)mLF$Y$=OVB1>FW6Hg3IcIDpMmDJ~#%+92_)96sek011m%gzMzA)sPIw$Cw zW}e#ANv4jp1j-fi>eOo&kfc_r1vO!;RSrhpILA7HOpupZGV+saR5Lrs4cPN040yJg zD{9;ixz%3D?!thh0)k}^{&i8D>Bi=ex zjx0F|ufBNkVnpcKlKq#>8>tjhzBr~~KyyZ3GBogqG$(}!5zw3y*&D0JbA~eD{CmWM z0{mM-J0n0_bi06Lw1CzM^a!w4M@{mjSG0|a))}}=OK4vND>GuYCdzG{>6KL`4o;XN zg*KhAk6rg{+vy4I)M;uSi?Y|r3pCvPFFuy2`y2%`tY7p2v|8kQ42(Ps%XC!(ok5qe z1^W7VVVUl(SGr$REkav9yR}rc4S5Y!2j}!Bv{yAsU-lZ< z6)*2rr`qI7LESJZ4(f(aITs6WQQooYmF{x)0z--sur?Y3KB<909r#%6Gr`i|rEpo~=7ti-wA#DA9*+b1G@ ztutaR+qh35R30Wxra~41O|gyzI*V3eN?CmE8aR&M7=$=g!p|b2dUOPhpb*XELcp;A zOdZO!ggeD>Nx}ZX34<7X!2Vqn+^r*V1ridYuagcJNXQEQvc@7+hK*ASXO!iL1*q;h zU@C!7ZAxFO9kWfc1skK5{zZ2=-H^`7P?RxOtO>B^_WoB;nw3;hxTj~tuR2wZp4r{q zWE&)ueJ;4#x0-#+;&C2fPIvt732oLY@WP`wxp{iR&}L?o@`fBpamxT}*Mg@ijDecz zVG2;BZl|sz^k*5;7y2U}>>xMt0P!I80`Ehy4sE{R)|0>>RO@OM{T=PCP%H)Z=on@v zO##oP8U^w=O;G<4P#y>BALMc0MOOOrH8M7iVc?eqB5c_5()7=?g#?M31%IeE5F`|D zmuCA1V}PFfeySVjx#M2{eKG5_n6h<{QP6e7c^%(lVmtL8*=V~SBDlh^RVyit9c$P> ziX}c*Mr(L$q`oHXK)i0o_B=YTIGz>ecK>+g2=%#Wu9~H9G}talNoQI?8?>fUBm!-a z1}E4m2~)0}`CKlY82Eu?G%C1UN2ngV6UvxverC+p$Mf{uuC(RU<9?};5FPdl%t65x z%dePjJgI;WnD~d}r>TL7&-GO%;?_`I_4@#{Hq)59AAZ$^bY-c0PIm4x&9A0;gi=XQ zS%JSm$Iu0poAw58MXLIbTywh~mG0N~@*yDpt6r#RND4}1XWef2g#0Noeo5|}`ukGn z^D}J${30F-EGEtR#(r~5d{O|efe2772omb~KuX;S;<~x#&ftpBI>XrsN~vQUfx`jw zig+AOx_{((pKP|PntukkuUr6^ZxErmTxc;J;&NtRpm4g;dcS13QDOOfC{IJSNc-*X zmtKzoE6ULQ@7#4-X2eVY!P?8$MNOaj1Np%qp;x`eqBOCaVicrX)aZ0erc)GhZB$mk z@B-Jim_KwWQI*9)9zTNLMZhK*qwtEJ9g&~g)zPHhnXCK?_r|#2(mQ^@1vI{h{A6a^ z>UxE!H9s${+yG?cj$QlJwIn+`f*>JM~z1rnx$ zQ1_ZJFi)|18l2e=<|)o_24yf$LCX~jA?{Ngl~sutsh-c9GL8~%fyr#m#oYyqdhbVp zO=L90I1%Swb~>LzcB3aN!Qilr(d1WUGlxrEpiZX@G#YBxU;Nx3epo62T!^2M<`kD zIgQt~9m39xy9u%#6Az0X`4ZRO>*h|2315q_#BmxK;+8o+{_>`ra!+QDkjdP!wo)XI zj&ob|%$Kk|W;hi5AA=AlnlHGE0}63;HFNAAbLoXU%b@R>daI3g!Z3B3>R1{=dH>So zLarxhCxp`6-$9RM{+T<2P`!Y+;2$bg1U;v#0;*m_q@a74&#`g6w8k%RwA6W$P4&H= z+j}1zG(Ml2r2bqT^r*;}+KsW){Yy!UgXG8~t7?U>Bl1DO2DHhiY;xB$g14Ja3V%-B zQU14Rq2KDm)?lo~!`0%R^{_uu@|>WkVK$Mf)!)b+=PQ@nItM0VvbGLsd!t?>J(W4B zfHNn44wt}8A>>uuBWR}JVNS;nW(p{#G%$&Q>I@Vl7(5*fHYD+HXj2!7ZV<*kph{We zhJ-FRV$rKyy~Y&GMWEz;=l^$>Wc;7OE&4P^rgvbO-?Kk4+JuS?=`}-hG6)EE03VhY z1z|&JJ~+xPVd`F|p;D&^Q_T(i+q9!o`V#ci6%1C@+#{x-!D6O2O8P;~S{F|~aK$k7?tj~-yGhM`tvrA zMzH@F^@AY?n2a--O-4-tLUYMWL(c^z#RU&CN`pATljt1Bb_z@qK#mhSEe8_C$p8{Z zoujDQ=P^gm9RD#XCwPn}0fD4HI})>hPp4)SS)1EL^ub16e^*WZrS5bHa9Qhpf^?$5 zWmWK({*0i{a(Gne=oz&`Y43#q(5-pZQ0Q(Ark*kU5l1j3xN*sb<3j&ybC3+>cJd#y z-IPF1vf*m=2aBGBh8z`T)kP?BF=6XFM))ONLpr|bl{tj1_<;|QNRq%XFtIUk;J9!s z7#8%wK1?_{3 zxfJ;DNVH%I_Nbdbwr08>-cEA6f5i_$TUwsC3cnve5R=W1O z7DM4Tls`SWs5$GXTRm1}v^GO}Q;(4XFNdAst@Az2GPjE${;B;^FXF4{j|M6gaJAObxyr^<2Gs=yH#l0p;^=?mp zrjsUk)!;_|TwL__dXW~xv zQfpor6aOP2dsDV#LM<&*{1v@BLC}vDld}2AxYR>6eMVfj{V&TG^qsySH z4l_e)%}fum>Q%L)lvyryW)^-+bJ*hq=uT^u%_&;b2~!Hf{4>(o0;nNH1P~Q74%8B`iqt#nsee1vbuJB%;yb z_Oi`QpZv3i29ufA{BICSH{I@S+yCGTiR%_p<{6ndF;+5OFyP`^{(iS>bye!DjR6e~ zXRi+G!Oe5aHqLLS|I6zp(fctQ*Ff)g)>^YLW;z2ETUXNq z!Cf*dXBtlkGWi3h`PO$sVI@0 zwiEYXi$@)kSVa0oR!=_g{n6<*Lt3I6$L8dtydGOw7nChUq~OW5>t@gZ_GR+I<#=wsZEAq zM%hj5<7?I?iyIryitDC*H!p45X{5F+a*u`(Wc5UoXnZo4#FkKm)~-F;r1m-+!2@xn zqo1C&QzkF_eHImuae8IXk4i{lk6?6daEyR^=0%_$*_gE01T{+p}GF9B*-aFaMH1*HMv?N~1w6DXBPY;OD*5$De#H zo<*WY=GOB&N#DdT@BRG~r6Eu)UnTF=?)A2t939ey$Fu5%HO+x8*APpi0|k#~$q>CC z%|Flg$8$85#KMw_ebCvuyR1|6qE=S0SKe)_WxrmuT%DFa`q3=*%FwPQIY)whjOp`+ z)3y3basRmBpV!~`or(gWIC#RB`FXmiISbwOhAf5|B{^0zSRzIc8NgPb(sxQS0P5LR zOU#7$#EOq!SWef#eGZYrH-@5e~QBwe3XW z(&~Zj57y8Z1lw;A?q~0lG`y8xxYm@5@=l~(&GCGi|CW#?Abfa`g)Rgyuoy=6Ns@X~ zued&8-dwAG1kV?F)P8lC(_i=8RuK%vqI1%Mpsk_L3!wc!9p>o1hSB%cCv2J*&gPFx zn`sPM6ow=YslTe;H_`Cz0SUvm(4{)xQUZO199uS>1&`f@Hv@C+kJ|KQ9d%^{r)5Gn zw|q1v{>MtESH`6+lhK{m3uHBeXOID)Eo@B4+e~@$S7sgnT`PF z8Ggmz^JAtl1Q|&-SAV45xATCVmThEFea!`;W0?5#Opih52Q3%fUwzN(zMidpueWS- z>F=)j7xq9|{o4wCQP^9$ztaCV7H^Fks!}DZJyk*dC|Ro?snW~dx+tN!muS>`2vZfJ z_6E@eC^WoKMr!Grl)&ZfAtU zITGx2WNYJ2*BUH_)fqfOfwN4md>C6vZZAoXjq2!Eb-xh|p|?=)Cwhm!0w^Q*E@$0( ze!XDmiR=3NT>{YKY4i&pg#5sL-S9AVr_^)SX-|Re2)MT);DS~Fz=>`JU%O1{xzJ=o z1@)Z)NqITYcf?$&M}&^WyPr_{hcPpokf@my)&UN7b{R|P&jV|(NCi)vPg+Up4W4DlRAiIn@2 ztP%>b(x9DjgTlL;0Y6E^1{DgZP$VWO6u?D@UBHmt?oSF#ma+bRt7806$9sinpcbG5 z7LfML*+{YyveaHT$OPSyg}j=&JGdP!g=*1KNXTb{&WRW5^Oy8t_%8JHM}+&Ypm2vk z@gV5G$(C^l9(B{kpy%7}I#W?1-+!P~8Dd^9)IpNJ;LN%Gfby27f1K3Ek;^>;hcLLP zihtl@MEU_)dHTw}u44x|j$N!Q%CmkXoQ_!$uOK(Y{@kWYQO`8T75$XTdrGq$s{1|t z87cwJ7D6`hC&sb?r~#wp+DGpU79v8%C2qVb1B+Z}yRU zH=mgy4J@sYoO+S%xypJP3#HLt>Fk6SM8|vxz(V19VC>ileXgAKS%MhJgG#~oyr-}{ z;#t8y4H9{IrD746L2=p3+tKp&XKv@IveEh~#*IX$*85uXO2OzhlwQBemHWF^M|xI0 zdW0OmgoQ+P3p!pco{H-5f4NiE;b zZ$OJYS&5YcKVPCZY=zyf_x?O*m;QZrbBwq$u^#sW)^o+meIC4j4w6-*guf#?9p{GZvQ=yMF28O5M%(C z4IzGV=RdA{Y$?8szI<`S@8gTPo4bYu1JBhiXf8-v&lpX#xmOBLRMi&^XHMJQlj)3^ zB6|W;y$kB>?{e>0Nlps0y#4LXignrO^Xv*wNtiD&YJagofWTv*ihaFPw|;)NJFrCJ%UH$ZG6vQUaOJ5m*0cJ^0lEk7~JG5Th`P? zKY%no{qxD$#@Mf1VvV5>+_pZ3-8XP*QwP)%JnycR-wKhDX&yui6R*CXqMK-wt+{nw z;Sz8GdBAVTxz`lvPU%+F=NOcZhTH>E4~x8-O4DDKpma2j67m;7A_i6yLW=quphEzG z#X6ux|3!4$((1ld5HAoN$vaX+c{)@4XkB;&h)3pVKvd31#w=Gyq39M#(e$QUbVJy_jF32h^zgECx7T?77EvL$0 zOo6)CM}<|2P@T9Oed->>9UEINl$8RCQXn zLH8zK29N_{*F7mE4Eq3u0-XTr1?ehwxupdYRXFDcG!L*v|Dzt9EHZ;|KnW2Ir(R3z zLBO1e{F{9P3Cq9e0TNyo7Im9CMO#(9o;{-|SKtr?QDsL?qEI0=ATly~b7*4x&V`D^ zCaKUIf=A`_2LYild{P#>d|_oFtk#wU-EtN8xScMS>bqQevDA5SEy;ou_1iP}gt?5| z4)H7L7HXLSOk+w3BtGU&A6}oSqK;I<@*9IJhow_!MAgj5+yIoh2 zAhZu;B=XL?b0*7IQIgF<`#|r3Z#hN);`ktkhTOY43CX%2ks5CgW`_kD2i_>eqs*uL zWWMn*%^h&2*Id0b4jC;8dKu#%^V1`Z&7r3IvWR%vLl}PiIi>jnky%=Wm@FZkL)cX{ z92ye@sjlnAX%gBIzgHw()K@tAbj$X^a zv+0OwYooG?PF(IEj0!ou0n|?yI5`^^oe8=HqTr@m#5>O)d^z)0R(yXWsdh+oOZS`Q zPX#bO^tsRV*-J=H;$McbHd%HpO-q9CCG{qx?r@PmaaL4bfr!by`e_J(V)ro66KnpD zChfanA{Yw1x-OJB0NEq9&U5m~pDJ~z2rT#p)JYi@1b{*XoVz(dESCT{ml-qV{=N?q z1w_=VyaGcjGHzV*vO<)1nnG>Ync~Hcz{I1=ITF}ZO~CYjLF?;2 zAbkImzDfOU^dbdwA{*B_0ATcfYdq=^DTkJBOWOHlcR$KIW6L%FqB|!uK=*kczC8pe z^KTcnwzmWHLK7C?&^cT$M(KVu(AuIu-U^WJXGD9~zs`|JXxK+n>i=01W}iVG zr6@<>wy{meMso6qPWXalkxYfsBWxs)NIFQ>+x9sza$KPV8Dj1APcNz+*Q)f9@dLL; z{+F1cABq{a%GR=xL%i!!rQfesmDQWcjCv^S@GNyl4tqusw&{caR5|5LkTjh^z zX+R%Ht(Dm`;4>VSUznlvA{TsnEWHS4$EBxd{#?{px9@GucSuuBjIMY2M^rLXeQaKYy$kR|hq3IuZRO7x-jYE?3+5MwjMUAz| z+UsAFkxr-xha_((Mcfo3xUJ&3){Dxuo*Vmu0C~%hS5q#?26B(esp!l*V^B_iCCP$B ziHq5is0Zw4!LS+xuZ2vffCIpak${NF z2qZd@oJEmOA_a>c49K*9?gPDt5%jP5*6eMy*NR{7$Zu`)N3C4hTaN(I6BHf?gXk%) zIxO4t!<{^VMbJG!iVkE0=^r7t>>^0g7ZA6XqCmO?ZnW%6qwdL^+UqQc2jX%v68<2& zTBcem9SsFdCMvpT&YT-AA-8}iH1uxYTf|=J-x$A88pAhaTt$fm90iD>nvHNgL;C)c zK>q-ri_8VaOBgadP#1>k8OWiddS2ZZEa4I0*`-hRzP8Puwj2iq6+h+CAS&xf9(BA| zOdi^?6Z=3-=<87>ayd-xh^)S^d>Fl<0m&~2et>}fa)L%*(vF-UDZtyWoSEqJ^Xr8Q z@d4q#RU}Bd69kK)*A@fUND30my>SEOzy15K(E~b1-7(YYo0%RcLi<|Qd)*)frL!4A zLOpvEY}>wI3KrdU{~I7K!`h)zJeCdoF_!56L&;M+Q=JE)k8p!lp0#AlI2aLRd940xO8)2c;q%_wa_-fq4KiT0YSH z;4?Y@h~dHwAgk0kn+&Xy{$5Pwk7?zX9UYzMNy;Ch2pW+5E(Y|2Xb@+iprjwas~tlU zo8E85?$4g4)vQ`MrSLnT0;4d;UUd*IgE5xMK=zs)5*lN%(D|Q4fm}@QD9Sb?|D^q^ zT>1W1=HzAnOaumOPdjyCLE{kSDVwtf22gBWH=2GQ1Xbc?Cs98o2Qc8kx%|)b9Q#d> z2wwviTJy`-K-u)o-`Vu{3^3C~kisy)e}~7$!i4|)Pxa7mI>Evw#bG2P=abeZpBmvzi;Fz^^9|;={p08WvC3Y?8h*Mmb)Au)n#!f~?Z7 zm^h8Nwu#)$eeMx4t02Rv=Km$2x?*~9XIs%GUoWBOQuaOZk;mUfItJ>m6ii5*owGdg zUWZ1T9tJ1)p#K@!zXnertNBD2*YO}?A*|@jJy^$%qAs$3n53oR(HiID*A<@%g>Lsn zNQ<4-tFkqrW%lFY{3ux-7U-9&cH%OVl<_;4Q~5R<#EvS*7~D|DNuH%==|zgTdiZ2veKD@y07V|$L+%OoSyy2p^|K;kOJ?HXLNf&l|! zXF9be{VTevm)=RB+Fi#^MzooQH-G1%=X~%b(95*qLnG|Pov}k0SWs55pjgnKU+W2e z{|#7BQZONw<|kp2xl3`$9KDv{*U3o1_B`L^i{NL8Rj+(SNq;wC+0n6^h`M1pMLiUxm6zVxu^+(Dx0;X-q zTq>8_S*A7W8(3U)c~eXqzGG$a`R-OiC#P=WSEfxkG&=-_PhOeqmGvKK*CVHjxOBZ@ pEwzL`>i*8R__Kv1*C}gmHZq>QI%40h(qzO Date: Mon, 14 Mar 2022 22:09:41 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E5=AE=8C=E5=96=84=20yudao-spring-boot-star?= =?UTF-8?q?ter-file=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=20S3?= =?UTF-8?q?=20=E5=AF=B9=E6=8E=A5=E4=BA=91=E5=AD=98=E5=82=A8=E3=80=81local?= =?UTF-8?q?=E3=80=81ftp=E3=80=81sftp=20=E7=AD=89=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/io/FileUtils.java | 35 ++++++++- .../yudao-spring-boot-starter-file/pom.xml | 12 +++ .../core/client/impl/AbstractFileClient.java | 13 ++++ .../core/client/impl/ftp/FtpFileClient.java | 73 +++++++++++++++++++ .../client/impl/ftp/FtpFileClientConfig.java | 59 +++++++++++++++ .../client/impl/local/LocalFileClient.java | 52 +++++++++++++ .../impl/local/LocalFileClientConfig.java | 30 ++++++++ .../core/client/impl/s3/S3FileClient.java | 14 +++- .../core/client/impl/sftp/SftpFileClient.java | 61 ++++++++++++++++ .../impl/sftp/SftpFileClientConfig.java | 52 +++++++++++++ .../core/client/ftp/FtpFileClientTest.java | 39 ++++++++++ .../client/local/LocalFileClientTest.java | 27 +++++++ .../file/core/client/s3/S3FileClientTest.java | 9 +++ .../core/client/sftp/SftpFileClientTest.java | 37 ++++++++++ .../controller/admin/file/FileController.java | 11 ++- 15 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java index 56baaed95..63732f1b3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java @@ -22,13 +22,40 @@ public class FileUtils { */ @SneakyThrows public static File createTempFile(String data) { - // 创建文件,通过 UUID 保证唯一 - File file = File.createTempFile(IdUtil.simpleUUID(), null); - // 标记 JVM 退出时,自动删除 - file.deleteOnExit(); + File file = createTempFile(); // 写入内容 FileUtil.writeUtf8String(data, file); return file; } + /** + * 创建临时文件 + * 该文件会在 JVM 退出时,进行删除 + * + * @param data 文件内容 + * @return 文件 + */ + @SneakyThrows + public static File createTempFile(byte[] data) { + File file = createTempFile(); + // 写入内容 + FileUtil.writeBytes(data, file); + return file; + } + + /** + * 创建临时文件,无内容 + * 该文件会在 JVM 退出时,进行删除 + * + * @return 文件 + */ + @SneakyThrows + public static File createTempFile() { + // 创建文件,通过 UUID 保证唯一 + File file = File.createTempFile(IdUtil.simpleUUID(), null); + // 标记 JVM 退出时,自动删除 + file.deleteOnExit(); + return file; + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-file/pom.xml b/yudao-framework/yudao-spring-boot-starter-file/pom.xml index 126bf9b77..015463941 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-file/pom.xml @@ -51,6 +51,18 @@ jackson-core + + commons-net + commons-net + 3.8.0 + + + com.jcraft + jsch + 0.1.55 + + + software.amazon.awssdk 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 index 13f104118..4aef2f387 100644 --- 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 @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.file.core.client.impl; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.extern.slf4j.Slf4j; @@ -55,4 +56,16 @@ public abstract class AbstractFileClient implem return id; } + /** + * 格式化文件的 URL 访问地址 + * 使用场景:local、ftp、db,通过 FileController 的 getFile 来获取文件内容 + * + * @param domain 自定义域名 + * @param path 文件路径 + * @return URL 访问地址 + */ + protected String formatFileUrl(String domain, String path) { + return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java new file mode 100644 index 000000000..556f4e28c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.ftp; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.ftp.Ftp; +import cn.hutool.extra.ftp.FtpException; +import cn.hutool.extra.ftp.FtpMode; +import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; + +/** + * Ftp 文件客户端 + * + * @author 芋道源码 + */ +public class FtpFileClient extends AbstractFileClient { + + private Ftp ftp; + + public FtpFileClient(Long id, FtpFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全风格。例如说 Linux 是 /,Windows 是 \ + if (!config.getBasePath().endsWith(File.separator)) { + config.setBasePath(config.getBasePath() + File.separator); + } + // 初始化 Ftp 对象 + this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), + CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode())); + } + + @Override + public String upload(byte[] content, String path) { + // 执行写入 + String filePath = getFilePath(path); + String fileName = FileUtil.getName(filePath); + String dir = StrUtil.removeSuffix(filePath, fileName); + boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); + if (!success) { + throw new FtpException(StrUtil.format("上海文件到目标目录 ({}) 失败", filePath)); + } + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + ftp.delFile(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + String fileName = FileUtil.getName(filePath); + String dir = StrUtil.removeSuffix(path, fileName); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.download(dir, fileName, out); + return out.toByteArray(); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java new file mode 100644 index 000000000..bc0038219 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.ftp; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * Ftp 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class FtpFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + + /** + * 主机地址 + */ + @NotEmpty(message = "host 不能为空") + private String host; + /** + * 主机端口 + */ + @NotNull(message = "port 不能为空") + private Integer port; + /** + * 用户名 + */ + @NotEmpty(message = "用户名不能为空") + private String username; + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String password; + /** + * 连接模式 + * + * 使用 {@link cn.hutool.extra.ftp.FtpMode} 对应的字符串 + */ + @NotEmpty(message = "连接模式不能为空") + private String mode; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java new file mode 100644 index 000000000..aea6b1ee2 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.local; + +import cn.hutool.core.io.FileUtil; +import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; + +import java.io.File; + +/** + * 本地文件客户端 + * + * @author 芋道源码 + */ +public class LocalFileClient extends AbstractFileClient { + + public LocalFileClient(Long id, LocalFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全风格。例如说 Linux 是 /,Windows 是 \ + if (!config.getBasePath().endsWith(File.separator)) { + config.setBasePath(config.getBasePath() + File.separator); + } + } + + @Override + public String upload(byte[] content, String path) { + // 执行写入 + String filePath = getFilePath(path); + FileUtil.writeBytes(content, filePath); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + FileUtil.del(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + return FileUtil.readBytes(filePath); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java new file mode 100644 index 000000000..9820de7dd --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.local; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotEmpty; + +/** + * 本地文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class LocalFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + +} 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 index 09d98398e..1f2b4aaae 100644 --- 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 @@ -7,6 +7,8 @@ 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 java.net.URI; @@ -14,7 +16,7 @@ import java.net.URI; import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU; /** - * 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 + * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 * * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库 * @@ -84,12 +86,18 @@ public class S3FileClient extends AbstractFileClient { @Override public void delete(String path) { - + DeleteObjectRequest.Builder request = DeleteObjectRequest.builder() + .bucket(config.getBucket()) // bucket 必须传递 + .key(path); // 相对路径作为 key + client.deleteObject(request.build()); } @Override public byte[] getContent(String path) { - return new byte[0]; + GetObjectRequest.Builder request = GetObjectRequest.builder() + .bucket(config.getBucket()) // bucket 必须传递 + .key(path); // 相对路径作为 key + return client.getObjectAsBytes(request.build()).asByteArray(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java new file mode 100644 index 000000000..704300264 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.sftp; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.ssh.Sftp; +import cn.iocoder.yudao.framework.common.util.io.FileUtils; +import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; + +import java.io.File; + +/** + * Sftp 文件客户端 + * + * @author 芋道源码 + */ +public class SftpFileClient extends AbstractFileClient { + + private Sftp sftp; + + public SftpFileClient(Long id, SftpFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全风格。例如说 Linux 是 /,Windows 是 \ + if (!config.getBasePath().endsWith(File.separator)) { + config.setBasePath(config.getBasePath() + File.separator); + } + // 初始化 Ftp 对象 + this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); + } + + @Override + public String upload(byte[] content, String path) { + // 执行写入 + String filePath = getFilePath(path); + File file = FileUtils.createTempFile(content); + sftp.upload(filePath, file); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + sftp.delFile(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + File destFile = FileUtils.createTempFile(); + sftp.download(filePath, destFile); + return FileUtil.readBytes(destFile); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java new file mode 100644 index 000000000..6941e1521 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.sftp; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * Sftp 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class SftpFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + + /** + * 主机地址 + */ + @NotEmpty(message = "host 不能为空") + private String host; + /** + * 主机端口 + */ + @NotNull(message = "port 不能为空") + private Integer port; + /** + * 用户名 + */ + @NotEmpty(message = "用户名不能为空") + private String username; + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String password; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java new file mode 100644 index 000000000..7c37c014d --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.framework.file.core.client.ftp; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.extra.ftp.FtpMode; +import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClient; +import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClientConfig; +import org.junit.jupiter.api.Test; + +public class FtpFileClientTest { + + @Test + public void test() { + // 创建客户端 + FtpFileClientConfig config = new FtpFileClientConfig(); + config.setDomain("http://127.0.0.1:48080"); + config.setBasePath("/home/ftp"); + config.setHost("kanchai.club"); + config.setPort(221); + config.setUsername(""); + config.setPassword(""); + config.setMode(FtpMode.Passive.name()); + FtpFileClient client = new FtpFileClient(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); + if (false) { + byte[] bytes = client.getContent(path); + System.out.println("文件内容:" + bytes); + } + if (false) { + client.delete(path); + } + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java new file mode 100644 index 000000000..62e5ea249 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.framework.file.core.client.local; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClient; +import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClientConfig; +import org.junit.jupiter.api.Test; + +public class LocalFileClientTest { + + @Test + public void test() { + // 创建客户端 + LocalFileClientConfig config = new LocalFileClientConfig(); + config.setDomain("http://127.0.0.1:48080"); + config.setBasePath("/Users/yunai/file_test"); + LocalFileClient client = new LocalFileClient(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); + client.delete(path); + } + +} 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 index 5db3ac699..5d9224b74 100644 --- 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 @@ -76,6 +76,15 @@ public class S3FileClientTest { byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); String fullPath = client.upload(content, path); System.out.println("访问地址:" + fullPath); + // 读取文件 + if (false) { + byte[] bytes = client.getContent(path); + System.out.println("文件内容:" + bytes); + } + // 删除文件 + if (false) { + client.delete(path); + } } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java new file mode 100644 index 000000000..13e331047 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.framework.file.core.client.sftp; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClient; +import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClientConfig; +import org.junit.jupiter.api.Test; + +public class SftpFileClientTest { + + @Test + public void test() { + // 创建客户端 + SftpFileClientConfig config = new SftpFileClientConfig(); + config.setDomain("http://127.0.0.1:48080"); + config.setBasePath("/home/ftp"); + config.setHost("kanchai.club"); + config.setPort(222); + config.setUsername(""); + config.setPassword(""); + SftpFileClient client = new SftpFileClient(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); + if (false) { + byte[] bytes = client.getContent(path); + System.out.println("文件内容:" + bytes); + } + if (false) { + client.delete(path); + } + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 48c7e146c..7d449dbe8 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -57,10 +57,15 @@ public class FileController { return success(true); } - @GetMapping("/get/{path}") + @GetMapping("/{configId}/get/{path}") @ApiOperation("下载文件") - @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class) - public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException { + @ApiImplicitParams({ + @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = String.class), + @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class) + }) + public void getFile(HttpServletResponse response, + @PathVariable("configId") String configId, + @PathVariable("path") String path) throws IOException { FileDO file = fileService.getFile(path); if (file == null) { log.warn("[getFile][path({}) 文件不存在]", path); From 05d4aae65d624ba08935792f64f33468f199b77f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 14 Mar 2022 23:07:37 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E5=AE=8C=E6=88=90=20yudao-spring-boot-star?= =?UTF-8?q?ter-file=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=20S3?= =?UTF-8?q?=20=E5=AF=B9=E6=8E=A5=E4=BA=91=E5=AD=98=E5=82=A8=E3=80=81local?= =?UTF-8?q?=E3=80=81ftp=E3=80=81sftp=E3=80=81db=20=E7=AD=89=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/config/YudaoPayAutoConfiguration.java | 1 + .../client/impl/PayClientFactoryImpl.java | 10 +-- .../pay/core/enums/PayChannelEnum.java | 1 - .../config/YudaoFileAutoConfiguration.java | 21 ++++++ .../client/{impl => }/AbstractFileClient.java | 2 +- .../file/core/client/FileClientFactory.java | 22 ++++++ .../core/client/FileClientFactoryImpl.java | 69 +++++++++++++++++++ .../file/core/client/db/DBFileClient.java | 41 +++++++++++ .../core/client/db/DBFileClientConfig.java | 24 +++++++ .../client/db/DBFileContentFrameworkDAO.java | 16 +++++ .../client/{impl => }/ftp/FtpFileClient.java | 4 +- .../{impl => }/ftp/FtpFileClientConfig.java | 2 +- .../{impl => }/local/LocalFileClient.java | 4 +- .../local/LocalFileClientConfig.java | 2 +- .../client/{impl => }/s3/S3FileClient.java | 6 +- .../{impl => }/s3/S3FileClientConfig.java | 2 +- .../s3/S3ModifyPathInterceptor.java | 2 +- .../{impl => }/sftp/SftpFileClient.java | 4 +- .../{impl => }/sftp/SftpFileClientConfig.java | 2 +- .../file/core/enums/FileStorageEnum.java | 55 +++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + .../core/client/ftp/FtpFileClientTest.java | 2 - .../client/local/LocalFileClientTest.java | 2 - .../file/core/client/package-info.java | 4 -- .../file/core/client/s3/S3FileClientTest.java | 2 - .../core/client/sftp/SftpFileClientTest.java | 2 - 26 files changed, 271 insertions(+), 33 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/config/YudaoFileAutoConfiguration.java rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/AbstractFileClient.java (96%) create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/ftp/FtpFileClient.java (94%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/ftp/FtpFileClientConfig.java (95%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/local/LocalFileClient.java (89%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/local/LocalFileClientConfig.java (90%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/s3/S3FileClient.java (93%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/s3/S3FileClientConfig.java (97%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/s3/S3ModifyPathInterceptor.java (95%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/sftp/SftpFileClient.java (92%) rename yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/{impl => }/sftp/SftpFileClientConfig.java (94%) create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/enums/FileStorageEnum.java create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/resources/META-INF/spring.factories delete mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java index d49c1c2b2..c4d41b6f5 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration; * * @author 芋道源码 */ +@Configuration @EnableConfigurationProperties(PayProperties.class) public class YudaoPayAutoConfiguration { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java index edfd44952..647152196 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java @@ -27,11 +27,11 @@ public class PayClientFactoryImpl implements PayClientFactory { * 支付客户端 Map * key:渠道编号 */ - private final ConcurrentMap> channelIdClients = new ConcurrentHashMap<>(); + private final ConcurrentMap> clients = new ConcurrentHashMap<>(); @Override public PayClient getPayClient(Long channelId) { - AbstractPayClient client = channelIdClients.get(channelId); + AbstractPayClient client = clients.get(channelId); if (client == null) { log.error("[getPayClient][渠道编号({}) 找不到客户端]", channelId); } @@ -42,11 +42,11 @@ public class PayClientFactoryImpl implements PayClientFactory { @SuppressWarnings("unchecked") public void createOrUpdatePayClient(Long channelId, String channelCode, Config config) { - AbstractPayClient client = (AbstractPayClient) channelIdClients.get(channelId); + AbstractPayClient client = (AbstractPayClient) clients.get(channelId); if (client == null) { client = this.createPayClient(channelId, channelCode, config); client.init(); - channelIdClients.put(client.getId(), client); + clients.put(client.getId(), client); } else { client.refresh(config); } @@ -69,7 +69,7 @@ public class PayClientFactoryImpl implements PayClientFactory { case ALIPAY_PC: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); } // 创建失败,错误日志 + 抛出异常 - log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config); + log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config); throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config)); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java index f8a4971a1..fccbab84b 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java @@ -56,5 +56,4 @@ public enum PayChannelEnum { return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); } - } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/config/YudaoFileAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/config/YudaoFileAutoConfiguration.java new file mode 100644 index 000000000..5f7bd91fe --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/config/YudaoFileAutoConfiguration.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.framework.file.config; + +import cn.iocoder.yudao.framework.file.core.client.FileClientFactory; +import cn.iocoder.yudao.framework.file.core.client.FileClientFactoryImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 文件配置类 + * + * @author 芋道源码 + */ +@Configuration +public class YudaoFileAutoConfiguration { + + @Bean + public FileClientFactory fileClientFactory() { + return new FileClientFactoryImpl(); + } + +} 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/AbstractFileClient.java similarity index 96% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java index 4aef2f387..262ceab20 100644 --- 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/AbstractFileClient.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl; +package cn.iocoder.yudao.framework.file.core.client; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.file.core.client.FileClient; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java new file mode 100644 index 000000000..85b21766f --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.framework.file.core.client; + +public interface FileClientFactory { + + /** + * 获得文件客户端 + * + * @param channelId 渠道编号 + * @return 文件客户端 + */ + FileClient getFileClient(Long channelId); + + /** + * 创建文件客户端 + * + * @param configId 配置编号 + * @param storage 存储器的枚举 {@link cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum} + * @param config 文件配置 + */ + void createOrUpdateFileClient(Long configId, Integer storage, Config config); + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java new file mode 100644 index 000000000..8573cf10a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.framework.file.core.client; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReflectUtil; +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 文件客户端的工厂实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class FileClientFactoryImpl implements FileClientFactory { + + /** + * 文件客户端 Map + * key:配置编号 + */ + private final ConcurrentMap> clients = new ConcurrentHashMap<>(); + + @Override + public FileClient getFileClient(Long channelId) { + AbstractFileClient client = clients.get(channelId); + if (client == null) { + log.error("[getFileClient][配置编号({}) 找不到客户端]", channelId); + } + return client; + } + + @Override + @SuppressWarnings("unchecked") + public void createOrUpdateFileClient(Long configId, Integer storage, Config config) { + AbstractFileClient client = (AbstractFileClient) clients.get(configId); + if (client == null) { + client = this.createFileClient(configId, storage, config); + client.init(); + clients.put(client.getId(), client); + } else { + client.refresh(config); + } + } + + @SuppressWarnings("unchecked") + private AbstractFileClient createFileClient( + Long configId, Integer storage, Config config) { + FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage); + Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum)); + // 创建客户端 +// switch (storageEnum) { +// case WX_PUB: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); +// case WX_LITE: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); +// case WX_APP: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); +// case ALIPAY_WAP: return (AbstractFileClient) new AlipayWapFileClient(channelId, (AlipayFileClientConfig) config); +// case ALIPAY_QR: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); +// case ALIPAY_APP: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); +// case ALIPAY_PC: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); +// } + return (AbstractFileClient) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config); +// storageEnum.getClientClass().newInstance() +// // 创建失败,错误日志 + 抛出异常 +// log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config); +// throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config)); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java new file mode 100644 index 000000000..57c649a88 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.framework.file.core.client.db; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; + +/** + * 基于 DB 存储的文件客户端的配置类 + * + * @author 芋道源码 + */ +public class DBFileClient extends AbstractFileClient { + + private DBFileContentFrameworkDAO dao; + + public DBFileClient(Long id, DBFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); + } + + @Override + public String upload(byte[] content, String path) { + dao.insert(getId(), path, content); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + dao.delete(getId(), path); + } + + @Override + public byte[] getContent(String path) { + return dao.selectContent(getId(), path); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClientConfig.java new file mode 100644 index 000000000..65d837b44 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClientConfig.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.framework.file.core.client.db; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotEmpty; + +/** + * 基于 DB 存储的文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class DBFileClientConfig implements FileClientConfig { + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java new file mode 100644 index 000000000..44850ed77 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.framework.file.core.client.db; + +/** + * 文件内容 Framework DAO 接口 + * + * @author 芋道源码 + */ +public interface DBFileContentFrameworkDAO { + + void insert(Long configId, String path, byte[] content); + + void delete(Long configId, String path); + + byte[] selectContent(Long configId, String path); + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java similarity index 94% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java index 556f4e28c..c02b469ff 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.ftp; +package cn.iocoder.yudao.framework.file.core.client.ftp; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.CharsetUtil; @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; import cn.hutool.extra.ftp.FtpException; import cn.hutool.extra.ftp.FtpMode; -import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; +import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientConfig.java similarity index 95% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientConfig.java index bc0038219..5ccf86cd4 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientConfig.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.ftp; +package cn.iocoder.yudao.framework.file.core.client.ftp; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.Data; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClient.java similarity index 89% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClient.java index aea6b1ee2..1c79f8999 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClient.java @@ -1,7 +1,7 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.local; +package cn.iocoder.yudao.framework.file.core.client.local; import cn.hutool.core.io.FileUtil; -import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; +import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; import java.io.File; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientConfig.java similarity index 90% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientConfig.java index 9820de7dd..1f595ed89 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientConfig.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.local; +package cn.iocoder.yudao.framework.file.core.client.local; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.Data; 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/s3/S3FileClient.java similarity index 93% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java index 1f2b4aaae..4fcc674d5 100644 --- 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/s3/S3FileClient.java @@ -1,7 +1,7 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.s3; +package cn.iocoder.yudao.framework.file.core.client.s3; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; +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; @@ -13,7 +13,7 @@ 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; +import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_QINIU; /** * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 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/s3/S3FileClientConfig.java similarity index 97% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientConfig.java index 858759c26..3cb702db1 100644 --- 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/s3/S3FileClientConfig.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.s3; +package cn.iocoder.yudao.framework.file.core.client.s3; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; 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/s3/S3ModifyPathInterceptor.java similarity index 95% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3ModifyPathInterceptor.java index 2f4f19f84..1b7550dd7 100644 --- 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/s3/S3ModifyPathInterceptor.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.s3; +package cn.iocoder.yudao.framework.file.core.client.s3; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClient.java similarity index 92% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClient.java index 704300264..3e18e888d 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClient.java @@ -1,9 +1,9 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.sftp; +package cn.iocoder.yudao.framework.file.core.client.sftp; import cn.hutool.core.io.FileUtil; import cn.hutool.extra.ssh.Sftp; import cn.iocoder.yudao.framework.common.util.io.FileUtils; -import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; +import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; import java.io.File; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientConfig.java similarity index 94% rename from yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java rename to yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientConfig.java index 6941e1521..1a976f5d9 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientConfig.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.file.core.client.impl.sftp; +package cn.iocoder.yudao.framework.file.core.client.sftp; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.Data; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/enums/FileStorageEnum.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/enums/FileStorageEnum.java new file mode 100644 index 000000000..03c6ed8a7 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/enums/FileStorageEnum.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.framework.file.core.enums; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.db.DBFileClient; +import cn.iocoder.yudao.framework.file.core.client.db.DBFileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.ftp.FtpFileClient; +import cn.iocoder.yudao.framework.file.core.client.ftp.FtpFileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.local.LocalFileClient; +import cn.iocoder.yudao.framework.file.core.client.local.LocalFileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.s3.S3FileClient; +import cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.sftp.SftpFileClient; +import cn.iocoder.yudao.framework.file.core.client.sftp.SftpFileClientConfig; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文件存储器枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum FileStorageEnum { + + DB(1, DBFileClientConfig.class, DBFileClient.class), + + LOCAL(10, LocalFileClientConfig.class, LocalFileClient.class), + FTP(11, FtpFileClientConfig.class, FtpFileClient.class), + SFTP(12, SftpFileClientConfig.class, SftpFileClient.class), + + S3(20, S3FileClientConfig.class, S3FileClient.class), + ; + + /** + * 存储器 + */ + private final Integer storage; + + /** + * 配置类 + */ + private final Class configClass; + /** + * 客户端类 + */ + private final Class clientClass; + + public static FileStorageEnum getByStorage(Integer storage) { + return ArrayUtil.firstMatch(o -> o.getStorage().equals(storage), values()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-file/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..8d2a4be45 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.iocoder.yudao.framework.file.config.YudaoFileAutoConfiguration diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java index 7c37c014d..ee0d74078 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java @@ -3,8 +3,6 @@ package cn.iocoder.yudao.framework.file.core.client.ftp; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.extra.ftp.FtpMode; -import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClient; -import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClientConfig; import org.junit.jupiter.api.Test; public class FtpFileClientTest { diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java index 62e5ea249..60f781b01 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.framework.file.core.client.local; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClient; -import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClientConfig; import org.junit.jupiter.api.Test; public class LocalFileClientTest { 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 deleted file mode 100644 index b6c1cd4a9..000000000 --- a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位,避免 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 index 5d9224b74..145894a44 100644 --- 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 @@ -4,8 +4,6 @@ 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; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java index 13e331047..cc8e59ede 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.framework.file.core.client.sftp; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClient; -import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClientConfig; import org.junit.jupiter.api.Test; public class SftpFileClientTest { From 18a5c4628480f7eab9a139a72873c3ec7e8dd181 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 15 Mar 2022 22:30:52 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=AE=8C=E6=88=90=20FileConfig=20=E7=9A=84?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 24 ++ .../framework/common/util/json/JsonUtils.java | 20 +- .../yudao-spring-boot-starter-file/pom.xml | 4 - .../file/core/client/FileClientFactory.java | 4 +- .../core/client/FileClientFactoryImpl.java | 19 +- .../core/client/s3/S3FileClientConfig.java | 6 +- .../file/core/client/s3/S3FileClientTest.java | 10 +- .../infra/enums/ErrorCodeConstants.java | 6 +- .../yudao-module-infra-impl/pom.xml | 7 + .../admin/file/FileConfigController.http | 45 ++++ .../admin/file/FileConfigController.java | 89 ++++++++ .../file/vo/config/FileConfigBaseVO.java | 22 ++ .../file/vo/config/FileConfigCreateReqVO.java | 26 +++ .../file/vo/config/FileConfigPageReqVO.java | 35 +++ .../file/vo/config/FileConfigRespVO.java | 32 +++ .../file/vo/config/FileConfigUpdateReqVO.java | 26 +++ .../infra/convert/file/FileConfigConvert.java | 36 +++ .../dal/dataobject/file/FileConfigDO.java | 56 +++++ .../dal/dataobject/file/FileContentDO.java | 45 ++++ .../infra/dal/dataobject/file/FileDO.java | 22 +- .../dal/mysql/file/FileConfigMapper.java | 32 +++ .../file/FileConfigRefreshConsumer.java | 29 +++ .../infra/mq/consumer/package-info.java | 1 - .../file/FileConfigRefreshMessage.java | 17 ++ .../module/infra/mq/message/package-info.java | 1 - .../mq/producer/file/FileConfigProducer.java | 26 +++ .../infra/mq/producer/package-info.java | 1 - .../infra/service/file/FileConfigService.java | 86 +++++++ .../service/file/FileConfigServiceImpl.java | 216 ++++++++++++++++++ .../{test => main}/resources/file/erweima.jpg | Bin .../file/FileConfigServiceImplTest.java | 132 +++++++++++ .../merchant/PayChannelServiceImpl.java | 4 + .../src/main/resources/application.yaml | 2 + 33 files changed, 1044 insertions(+), 37 deletions(-) create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.http create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigBaseVO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigCreateReqVO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigUpdateReqVO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConfigConvert.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileConfigDO.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java rename yudao-module-infra/yudao-module-infra-impl/src/{test => main}/resources/file/erweima.jpg (100%) create mode 100755 yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index e47ee45e5..ada5d4ddf 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -53,7 +53,10 @@ 1.0.5 30.1.1-jre 2.12.2 + 3.8.0 + 0.1.55 + 2.17.147 4.5.25 2.1.0 1.2.7 @@ -493,7 +496,28 @@ ${transmittable-thread-local.version} + + commons-net + commons-net + ${commons-net.version} + + + com.jcraft + jsch + ${jsch.version} + + + + cn.iocoder.boot + yudao-spring-boot-starter-file + ${revision} + + + software.amazon.awssdk + s3 + ${s3.version} + diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index 35bac965c..21dc101f9 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.json; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -55,7 +56,6 @@ public class JsonUtils { if (StrUtil.isEmpty(text)) { return null; } - try { return objectMapper.readValue(text, clazz); } catch (IOException e) { @@ -64,11 +64,26 @@ public class JsonUtils { } } + /** + * 将字符串解析成指定类型的对象 + * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下, + * 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。 + * + * @param text 字符串 + * @param clazz 类型 + * @return 对象 + */ + public static T parseObject2(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + return JSONUtil.toBean(text, clazz); + } + public static T parseObject(byte[] bytes, Class clazz) { if (ArrayUtil.isEmpty(bytes)) { return null; } - try { return objectMapper.readValue(bytes, clazz); } catch (IOException e) { @@ -90,7 +105,6 @@ public class JsonUtils { if (StrUtil.isEmpty(text)) { return new ArrayList<>(); } - try { return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); } catch (IOException e) { diff --git a/yudao-framework/yudao-spring-boot-starter-file/pom.xml b/yudao-framework/yudao-spring-boot-starter-file/pom.xml index 015463941..890035a7e 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-file/pom.xml @@ -54,20 +54,16 @@ commons-net commons-net - 3.8.0 com.jcraft jsch - 0.1.55 - software.amazon.awssdk s3 - 2.17.147 diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java index 85b21766f..db90f4892 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactory.java @@ -5,10 +5,10 @@ public interface FileClientFactory { /** * 获得文件客户端 * - * @param channelId 渠道编号 + * @param configId 配置编号 * @return 文件客户端 */ - FileClient getFileClient(Long channelId); + FileClient getFileClient(Long configId); /** * 创建文件客户端 diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java index 8573cf10a..980f8a310 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientFactoryImpl.java @@ -23,10 +23,10 @@ public class FileClientFactoryImpl implements FileClientFactory { private final ConcurrentMap> clients = new ConcurrentHashMap<>(); @Override - public FileClient getFileClient(Long channelId) { - AbstractFileClient client = clients.get(channelId); + public FileClient getFileClient(Long configId) { + AbstractFileClient client = clients.get(configId); if (client == null) { - log.error("[getFileClient][配置编号({}) 找不到客户端]", channelId); + log.error("[getFileClient][配置编号({}) 找不到客户端]", configId); } return client; } @@ -50,20 +50,7 @@ public class FileClientFactoryImpl implements FileClientFactory { FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage); Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum)); // 创建客户端 -// switch (storageEnum) { -// case WX_PUB: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); -// case WX_LITE: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); -// case WX_APP: return (AbstractFileClient) new WXPubFileClient(channelId, (WXFileClientConfig) config); -// case ALIPAY_WAP: return (AbstractFileClient) new AlipayWapFileClient(channelId, (AlipayFileClientConfig) config); -// case ALIPAY_QR: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); -// case ALIPAY_APP: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); -// case ALIPAY_PC: return (AbstractFileClient) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config); -// } return (AbstractFileClient) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config); -// storageEnum.getClientClass().newInstance() -// // 创建失败,错误日志 + 抛出异常 -// log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config); -// throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config)); } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientConfig.java index 3cb702db1..e35e38cef 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientConfig.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.file.core.client.s3; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.hibernate.validator.constraints.URL; @@ -70,11 +71,12 @@ public class S3FileClientConfig implements FileClientConfig { @NotNull(message = "accessSecret 不能为空") private String accessSecret; - @AssertTrue(message = "domain 不能为空") @SuppressWarnings("RedundantIfStatement") + @AssertTrue(message = "domain 不能为空") + @JsonIgnore public boolean isDomainValid() { // 如果是七牛,必须带有 domain - if (endpoint.contains(ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { + if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { return false; } return true; 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 index 145894a44..47c5e76e4 100644 --- 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 @@ -49,10 +49,12 @@ public class S3FileClientTest { 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 +// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY")); +// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY")); + config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8"); + config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); + config.setBucket("ruoyi-vue-pro"); + config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn // 默认上海的 endpoint config.setEndpoint("s3-cn-south-1.qiniucs.com"); diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java index 5283cda91..3b5423792 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java @@ -42,8 +42,10 @@ public interface ErrorCodeConstants { ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在"); ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变"); - // ========== 字典类型(测试) 1003000000 ========== - ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1003000000, "测试示例不存在"); + // ========== 字典类型(测试)1001005000 ========== + ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1001005000, "测试示例不存在"); + // ========== 文件配置 1001006000 ========== + ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在"); } diff --git a/yudao-module-infra/yudao-module-infra-impl/pom.xml b/yudao-module-infra/yudao-module-infra-impl/pom.xml index 439c70e82..19c9a1472 100644 --- a/yudao-module-infra/yudao-module-infra-impl/pom.xml +++ b/yudao-module-infra/yudao-module-infra-impl/pom.xml @@ -109,6 +109,13 @@ de.codecentric spring-boot-admin-starter-server + + + + cn.iocoder.boot + yudao-spring-boot-starter-file + + diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.http b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.http new file mode 100644 index 000000000..499f64df7 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.http @@ -0,0 +1,45 @@ +### 请求 /infra/file-config/create 接口 => 成功 +POST {{baseUrl}}/infra/file-config/create +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +{ + "name": "S3 - 七牛云", + "remark": "", + "storage": 20, + "config": { + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", + "bucket": "ruoyi-vue-pro", + "endpoint": "s3-cn-south-1.qiniucs.com", + "domain": "http://test.yudao.iocoder.cn", + "region": "oss-cn-beijing" + } +} + +### 请求 /infra/file-config/update 接口 => 成功 +PUT {{baseUrl}}/infra/file-config/update +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +{ + "id": 2, + "name": "S3 - 七牛云", + "remark": "", + "config": { + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", + "bucket": "ruoyi-vue-pro", + "endpoint": "s3-cn-south-1.qiniucs.com", + "domain": "http://test.yudao.iocoder.cn", + "region": "oss-cn-beijing" + } +} + +### 请求 /infra/file-config/test 接口 => 成功 +GET {{baseUrl}}/infra/file-config/test?id=2 +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.java new file mode 100755 index 000000000..92ecd2bb9 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; +import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import cn.iocoder.yudao.module.infra.service.file.FileConfigService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Api(tags = "管理后台 - 文件配置") +@RestController +@RequestMapping("/infra/file-config") +@Validated +public class FileConfigController { + + @Resource + private FileConfigService fileConfigService; + + @PostMapping("/create") + @ApiOperation("创建文件配置") + @PreAuthorize("@ss.hasPermission('infra:file-config:create')") + public CommonResult createFileConfig(@Valid @RequestBody FileConfigCreateReqVO createReqVO) { + return success(fileConfigService.createFileConfig(createReqVO)); + } + + @PutMapping("/update") + @ApiOperation("更新文件配置") + @PreAuthorize("@ss.hasPermission('infra:file-config:update')") + public CommonResult updateFileConfig(@Valid @RequestBody FileConfigUpdateReqVO updateReqVO) { + fileConfigService.updateFileConfig(updateReqVO); + return success(true); + } + + @PutMapping("/update-master") + @ApiOperation("更新文件配置为 Master") + @PreAuthorize("@ss.hasPermission('infra:file-config:update')") + public CommonResult updateFileConfigMaster(@RequestParam("id") Long id) { + fileConfigService.updateFileConfigMaster(id); + return success(true); + } + + @DeleteMapping("/delete") + @ApiOperation("删除文件配置") + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('infra:file-config:delete')") + public CommonResult deleteFileConfig(@RequestParam("id") Long id) { + fileConfigService.deleteFileConfig(id); + return success(true); + } + + @GetMapping("/get") + @ApiOperation("获得文件配置") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult getFileConfig(@RequestParam("id") Long id) { + FileConfigDO fileConfig = fileConfigService.getFileConfig(id); + return success(FileConfigConvert.INSTANCE.convert(fileConfig)); + } + + @GetMapping("/page") + @ApiOperation("获得文件配置分页") + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult> getFileConfigPage(@Valid FileConfigPageReqVO pageVO) { + PageResult pageResult = fileConfigService.getFileConfigPage(pageVO); + return success(FileConfigConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/test") + @ApiOperation("测试文件配置是否正确") + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult testFileConfig(@RequestParam("id") Long id) { + String url = fileConfigService.testFileConfig(id); + return success(url); + } +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigBaseVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigBaseVO.java new file mode 100755 index 000000000..09faa645b --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigBaseVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** +* 文件配置 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class FileConfigBaseVO { + + @ApiModelProperty(value = "配置名", required = true, example = "S3 - 阿里云") + @NotNull(message = "配置名不能为空") + private String name; + + @ApiModelProperty(value = "备注", example = "我是备注") + private String remark; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigCreateReqVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigCreateReqVO.java new file mode 100755 index 000000000..876757920 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigCreateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +@ApiModel("管理后台 - 文件配置创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FileConfigCreateReqVO extends FileConfigBaseVO { + + @ApiModelProperty(value = "存储器", required = true, example = "1", notes = "参见 FileStorageEnum 枚举类") + @NotNull(message = "存储器不能为空") + private Integer storage; + + @ApiModelProperty(value = "存储配置", required = true, notes = "配置是动态参数,所以使用 Map 接收") + @NotNull(message = "存储配置不能为空") + private Map config; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java new file mode 100755 index 000000000..3b3f0d4e5 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("管理后台 - 文件配置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FileConfigPageReqVO extends PageParam { + + @ApiModelProperty(value = "配置名", example = "S3 - 阿里云") + private String name; + + @ApiModelProperty(value = "存储器", example = "1") + private Integer storage; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java new file mode 100755 index 000000000..5988b48c9 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.Date; + +@ApiModel("管理后台 - 文件配置 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FileConfigRespVO extends FileConfigBaseVO { + + @ApiModelProperty(value = "编号", required = true, example = "1") + private Long id; + + @ApiModelProperty(value = "存储配置", required = true) + private FileClientConfig config; + + @ApiModelProperty(value = "是否为主配置", required = true, example = "true") + @NotNull(message = "是否为主配置不能为空") + private Boolean master; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigUpdateReqVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigUpdateReqVO.java new file mode 100755 index 000000000..45f8f751a --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigUpdateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +@ApiModel("管理后台 - 文件配置更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FileConfigUpdateReqVO extends FileConfigBaseVO { + + @ApiModelProperty(value = "编号", required = true, example = "1") + @NotNull(message = "编号不能为空") + private Long id; + + @ApiModelProperty(value = "存储配置", required = true, notes = "配置是动态参数,所以使用 Map 接收") + @NotNull(message = "存储配置不能为空") + private Map config; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConfigConvert.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConfigConvert.java new file mode 100755 index 000000000..442368302 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConfigConvert.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.infra.convert.file; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 文件配置 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface FileConfigConvert { + + FileConfigConvert INSTANCE = Mappers.getMapper(FileConfigConvert.class); + + @Mapping(target = "config", ignore = true) + FileConfigDO convert(FileConfigCreateReqVO bean); + + @Mapping(target = "config", ignore = true) + FileConfigDO convert(FileConfigUpdateReqVO bean); + + FileConfigRespVO convert(FileConfigDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileConfigDO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileConfigDO.java new file mode 100755 index 000000000..30df60ad5 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileConfigDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.infra.dal.dataobject.file; + +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +/** + * 文件配置表 + * + * @author 芋道源码 + */ +@Data +@TableName(value = "infra_file_config", autoResultMap = true) +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileConfigDO extends BaseDO { + + /** + * 配置编号,数据库自增 + */ + private Long id; + /** + * 配置名 + */ + private String name; + /** + * 存储器 + * + * 枚举 {@link FileStorageEnum} + */ + private Integer storage; + /** + * 备注 + */ + private String remark; + /** + * 是否为主配置 + * + * 由于我们可以配置多个文件配置,默认情况下,使用主配置进行文件的上传 + */ + private Boolean master; + + /** + * 支付渠道配置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private FileClientConfig config; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java new file mode 100644 index 000000000..a7917eda5 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.infra.dal.dataobject.file; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 文件内容表 + * + * 专门用于存储 {@link cn.iocoder.yudao.framework.file.core.client.db.DBFileClient} 的文件内容 + * + * @author 芋道源码 + */ +@Data +@TableName("infra_file_content") +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileContentDO extends BaseDO { + + /** + * 编号,数据库自增 + */ + @TableId(type = IdType.INPUT) + private String id; + /** + * 配置编号 + * + * 关联 {@link FileConfigDO#getId()} + */ + private Long configId; + /** + * 路径,即文件名 + */ + private String path; + /** + * 文件内容 + */ + private byte[] content; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java index 7ed2c5522..2803c9d10 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java @@ -11,6 +11,7 @@ import java.io.InputStream; /** * 文件表 + * 每次文件上传,都会记录一条记录到该表中 * * @author 芋道源码 */ @@ -24,10 +25,20 @@ import java.io.InputStream; public class FileDO extends BaseDO { /** - * 文件路径 + * 编号,数据库自增 */ @TableId(type = IdType.INPUT) private String id; + /** + * 配置编号 + * + * 关联 {@link FileConfigDO#getId()} + */ + private Long configId; + /** + * 路径,即文件名 + */ + private String path; /** * 文件类型 * @@ -35,9 +46,18 @@ public class FileDO extends BaseDO { */ @TableField(value = "`type`") private String type; + /** + * 访问地址 + */ + private String url; + /** + * 文件大小 + */ + private Integer size; /** * 文件内容 */ + @Deprecated private byte[] content; } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java new file mode 100755 index 000000000..6566347c5 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.infra.dal.mysql.file; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.Date; + +/** + * 文件配置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface FileConfigMapper extends BaseMapperX { + + default PageResult selectPage(FileConfigPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(FileConfigDO::getName, reqVO.getName()) + .eqIfPresent(FileConfigDO::getStorage, reqVO.getStorage()) + .betweenIfPresent(FileConfigDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc(FileConfigDO::getId)); + } + + @Select("SELECT id FROM infra_file_config WHERE update_time > #{maxUpdateTime} LIMIT 1") + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime); + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java new file mode 100644 index 000000000..671b41943 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.infra.mq.consumer.file; + +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; +import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage; +import cn.iocoder.yudao.module.infra.service.file.FileConfigService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 针对 {@link FileConfigRefreshMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class FileConfigRefreshConsumer extends AbstractChannelMessageListener { + + @Resource + private FileConfigService fileConfigService; + + @Override + public void onMessage(FileConfigRefreshMessage message) { + log.info("[onMessage][收到 FileConfig 刷新消息]"); + fileConfigService.initFileClients(); + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java deleted file mode 100644 index aa5028bec..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.yudao.module.infra.mq.consumer; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java new file mode 100644 index 000000000..7cc120de3 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.infra.mq.message.file; + +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; +import lombok.Data; + +/** + * 文件配置数据刷新 Message + */ +@Data +public class FileConfigRefreshMessage extends AbstractChannelMessage { + + @Override + public String getChannel() { + return "infra.file-config.refresh"; + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java deleted file mode 100644 index 997752c34..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.yudao.module.infra.mq.message; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java new file mode 100644 index 000000000..a666b185d --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.infra.mq.producer.file; + +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; +import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 文件配置相关消息的 Producer + */ +@Component +public class FileConfigProducer { + + @Resource + private RedisMQTemplate redisMQTemplate; + + /** + * 发送 {@link FileConfigRefreshMessage} 消息 + */ + public void sendFileConfigRefreshMessage() { + FileConfigRefreshMessage message = new FileConfigRefreshMessage(); + redisMQTemplate.send(message); + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java deleted file mode 100644 index 5bd8d7cc5..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.yudao.module.infra.mq.producer; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java new file mode 100755 index 000000000..1d7fcfaaa --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.infra.service.file; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 文件配置 Service 接口 + * + * @author 芋道源码 + */ +public interface FileConfigService { + + /** + * 初始化文件客户端 + */ + void initFileClients(); + + /** + * 创建文件配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFileConfig(@Valid FileConfigCreateReqVO createReqVO); + + /** + * 更新文件配置 + * + * @param updateReqVO 更新信息 + */ + void updateFileConfig(@Valid FileConfigUpdateReqVO updateReqVO); + + /** + * 更新文件配置为 Master + * + * @param id 编号 + */ + void updateFileConfigMaster(Long id); + + /** + * 删除文件配置 + * + * @param id 编号 + */ + void deleteFileConfig(Long id); + + /** + * 获得文件配置 + * + * @param id 编号 + * @return 文件配置 + */ + FileConfigDO getFileConfig(Long id); + + /** + * 获得文件配置列表 + * + * @param ids 编号 + * @return 文件配置列表 + */ + List getFileConfigList(Collection ids); + + /** + * 获得文件配置分页 + * + * @param pageReqVO 分页查询 + * @return 文件配置分页 + */ + PageResult getFileConfigPage(FileConfigPageReqVO pageReqVO); + + /** + * 测试文件配置是否正确,通过上传文件 + * + * @param id 编号 + * @return 文件 URL + */ + String testFileConfig(Long id); + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java new file mode 100755 index 000000000..deb4c1e0e --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java @@ -0,0 +1,216 @@ +package cn.iocoder.yudao.module.infra.service.file; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.FileClientFactory; +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; +import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; +import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.validation.Validator; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS; + +/** + * 文件配置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class FileConfigServiceImpl implements FileConfigService { + + /** + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 + */ + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; + + /** + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 + */ + private volatile Date maxUpdateTime; + + @Resource + private FileClientFactory fileClientFactory; + /** + * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的 + */ + private FileClient masterFileClient; + + @Resource + private FileConfigMapper fileConfigMapper; + + @Resource + private FileConfigProducer fileConfigProducer; + + @Resource + private Validator validator; + + @Resource + @Lazy // 注入自己,所以延迟加载 + private FileConfigService self; + + @Override + @PostConstruct + public void initFileClients() { + // 获取文件配置,如果有更新 + List configs = loadFileConfigIfUpdate(maxUpdateTime); + if (CollUtil.isEmpty(configs)) { + return; + } + + // 创建或更新支付 Client + configs.forEach(config -> { + fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig()); + // 如果是 master,进行设置 + if (Boolean.TRUE.equals(config.getMaster())) { + masterFileClient = fileClientFactory.getFileClient(config.getId()); + } + }); + + // 写入缓存 + maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime); + log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size()); + } + + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) + public void schedulePeriodicRefresh() { + self.initFileClients(); + } + + /** + * 如果文件配置发生变化,从数据库中获取最新的全量文件配置。 + * 如果未发生变化,则返回空 + * + * @param maxUpdateTime 当前文件配置的最大更新时间 + * @return 文件配置列表 + */ + private List loadFileConfigIfUpdate(Date maxUpdateTime) { + // 第一步,判断是否要更新。 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 + log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]"); + } else { // 判断数据库中是否有更新的文件配置 + if (fileConfigMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) { + return null; + } + log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]"); + } + // 第二步,如果有更新,则从数据库加载所有文件配置 + return fileConfigMapper.selectList(); + } + + @Override + public Long createFileConfig(FileConfigCreateReqVO createReqVO) { + // 插入 + FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO) + .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig())) + .setMaster(false); // 默认非 master + fileConfigMapper.insert(fileConfig); + // 发送刷新配置的消息 + fileConfigProducer.sendFileConfigRefreshMessage(); + // 返回 + return fileConfig.getId(); + } + + @Override + public void updateFileConfig(FileConfigUpdateReqVO updateReqVO) { + // 校验存在 + FileConfigDO config = this.validateFileConfigExists(updateReqVO.getId()); + // 更新 + FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO) + .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig())); + fileConfigMapper.updateById(updateObj); + // 发送刷新配置的消息 + fileConfigProducer.sendFileConfigRefreshMessage(); + } + + @Override + public void updateFileConfigMaster(Long id) { + // 校验存在 + this.validateFileConfigExists(id); + // 更新 + fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); + // 发送刷新配置的消息 + fileConfigProducer.sendFileConfigRefreshMessage(); + } + + private FileClientConfig parseClientConfig(Integer storage, Map config) { + // 获取配置类 + Class configClass = FileStorageEnum.getByStorage(storage) + .getConfigClass(); + FileClientConfig clientConfig = JsonUtils.parseObject2(JsonUtils.toJsonString(config), configClass); + // 参数校验 + ValidationUtils.validate(validator, clientConfig); + // 设置参数 + return clientConfig; + } + + @Override + public void deleteFileConfig(Long id) { + // 校验存在 + this.validateFileConfigExists(id); + // 删除 + fileConfigMapper.deleteById(id); + // 发送刷新配置的消息 + fileConfigProducer.sendFileConfigRefreshMessage(); + } + + private FileConfigDO validateFileConfigExists(Long id) { + FileConfigDO config = fileConfigMapper.selectById(id); + if (config == null) { + throw exception(FILE_CONFIG_NOT_EXISTS); + } + return config; + } + + @Override + public FileConfigDO getFileConfig(Long id) { + return fileConfigMapper.selectById(id); + } + + @Override + public List getFileConfigList(Collection ids) { + return fileConfigMapper.selectBatchIds(ids); + } + + @Override + public PageResult getFileConfigPage(FileConfigPageReqVO pageReqVO) { + return fileConfigMapper.selectPage(pageReqVO); + } + + @Override + public String testFileConfig(Long id) { + // 校验存在 + this.validateFileConfigExists(id); + // 上传文件 + byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg"); + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/file/erweima.jpg b/yudao-module-infra/yudao-module-infra-impl/src/main/resources/file/erweima.jpg similarity index 100% rename from yudao-module-infra/yudao-module-infra-impl/src/test/resources/file/erweima.jpg rename to yudao-module-infra/yudao-module-infra-impl/src/main/resources/file/erweima.jpg diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java new file mode 100755 index 000000000..e97df1ac6 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java @@ -0,0 +1,132 @@ +package cn.iocoder.yudao.module.infra.service.file; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; +import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** +* {@link FileConfigServiceImpl} 的单元测试类 +* +* @author 芋道源码 +*/ +@Import(FileConfigServiceImpl.class) +public class FileConfigServiceImplTest extends BaseDbUnitTest { + + @Resource + private FileConfigServiceImpl fileConfigService; + + @Resource + private FileConfigMapper fileConfigMapper; + + @Test + public void testCreateFileConfig_success() { + // 准备参数 + FileConfigCreateReqVO reqVO = randomPojo(FileConfigCreateReqVO.class); + + // 调用 + Long fileConfigId = fileConfigService.createFileConfig(reqVO); + // 断言 + assertNotNull(fileConfigId); + // 校验记录的属性是否正确 + FileConfigDO fileConfig = fileConfigMapper.selectById(fileConfigId); + assertPojoEquals(reqVO, fileConfig); + } + + @Test + public void testUpdateFileConfig_success() { + // mock 数据 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class); + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + FileConfigUpdateReqVO reqVO = randomPojo(FileConfigUpdateReqVO.class, o -> { + o.setId(dbFileConfig.getId()); // 设置更新的 ID + }); + + // 调用 + fileConfigService.updateFileConfig(reqVO); + // 校验是否更新正确 + FileConfigDO fileConfig = fileConfigMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, fileConfig); + } + + @Test + public void testUpdateFileConfig_notExists() { + // 准备参数 + FileConfigUpdateReqVO reqVO = randomPojo(FileConfigUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> fileConfigService.updateFileConfig(reqVO), FILE_CONFIG_NOT_EXISTS); + } + + @Test + public void testDeleteFileConfig_success() { + // mock 数据 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class); + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbFileConfig.getId(); + + // 调用 + fileConfigService.deleteFileConfig(id); + // 校验数据不存在了 + assertNull(fileConfigMapper.selectById(id)); + } + + @Test + public void testDeleteFileConfig_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> fileConfigService.deleteFileConfig(id), FILE_CONFIG_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetFileConfigPage() { + // mock 数据 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class, o -> { // 等会查询到 + o.setName(null); + o.setStorage(null); + o.setCreateTime(null); + }); + fileConfigMapper.insert(dbFileConfig); + // 测试 name 不匹配 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName(null))); + // 测试 storage 不匹配 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(null))); + // 测试 createTime 不匹配 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(null))); + // 准备参数 + FileConfigPageReqVO reqVO = new FileConfigPageReqVO(); + reqVO.setName(null); + reqVO.setStorage(null); + reqVO.setBeginCreateTime(null); + reqVO.setEndCreateTime(null); + + // 调用 + PageResult pageResult = fileConfigService.getFileConfigPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbFileConfig, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java b/yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java index 1c58cf1e1..62b29ee98 100644 --- a/yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java @@ -127,6 +127,7 @@ public class PayChannelServiceImpl implements PayChannelService { PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO); settingConfigAndCheckParam(channel, reqVO.getConfig()); channelMapper.insert(channel); + // TODO 芋艿:缺少刷新本地缓存的机制 return channel.getId(); } @@ -138,6 +139,7 @@ public class PayChannelServiceImpl implements PayChannelService { PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO); settingConfigAndCheckParam(channel, updateReqVO.getConfig()); channelMapper.updateById(channel); + // TODO 芋艿:缺少刷新本地缓存的机制 } @Override @@ -146,6 +148,7 @@ public class PayChannelServiceImpl implements PayChannelService { this.validateChannelExists(id); // 删除 channelMapper.deleteById(id); + // TODO 芋艿:缺少刷新本地缓存的机制 } private void validateChannelExists(Long id) { @@ -224,6 +227,7 @@ public class PayChannelServiceImpl implements PayChannelService { if (ObjectUtil.isNull(payClass)) { throw exception(CHANNEL_NOT_EXISTS); } + // TODO @芋艿:不要使用 hutool 的 json 工具,用项目的 PayClientConfig config = JSONUtil.toBean(configStr, payClass); // 验证参数 diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 738c70be6..7f340cb33 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -110,7 +110,9 @@ yudao: - tables - columns - infra_config + - infra_file_config - infra_file + - infra_file_content - infra_job - infra_job_log - infra_job_log From 659023bb358512bc7c75178d6af7f8edb65ec1e1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 16 Mar 2022 00:21:49 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=AE=8C=E6=88=90=20FileConfig=20=E7=9A=84?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/mapper/BaseMapperX.java | 3 + .../infra/enums/ErrorCodeConstants.java | 1 + .../file/vo/config/FileConfigRespVO.java | 8 +- .../service/codegen/CodegenServiceImpl.java | 16 +- .../service/file/FileConfigServiceImpl.java | 21 +- .../dal/mysql/permission/RoleMenuMapper.java | 3 +- yudao-ui-admin/src/api/infra/fileConfig.js | 59 ++++ .../src/components/DictTag/index.vue | 2 +- yudao-ui-admin/src/utils/dict.js | 2 + .../src/views/infra/fileConfig/index.vue | 313 ++++++++++++++++++ 10 files changed, 411 insertions(+), 17 deletions(-) create mode 100755 yudao-ui-admin/src/api/infra/fileConfig.js create mode 100755 yudao-ui-admin/src/views/infra/fileConfig/index.vue diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index e31600cb1..4ca5becb3 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -80,4 +80,7 @@ public interface BaseMapperX extends BaseMapper { entities.forEach(this::insert); } + default void updateBatch(T update) { + update(update, new QueryWrapper<>()); + } } diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java index 3b5423792..1528955f9 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java @@ -47,5 +47,6 @@ public interface ErrorCodeConstants { // ========== 文件配置 1001006000 ========== ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在"); + ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1001006001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件"); } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java index 5988b48c9..74291925b 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java @@ -19,13 +19,17 @@ public class FileConfigRespVO extends FileConfigBaseVO { @ApiModelProperty(value = "编号", required = true, example = "1") private Long id; - @ApiModelProperty(value = "存储配置", required = true) - private FileClientConfig config; + @ApiModelProperty(value = "存储器", required = true, example = "1", notes = "参见 FileStorageEnum 枚举类") + @NotNull(message = "存储器不能为空") + private Integer storage; @ApiModelProperty(value = "是否为主配置", required = true, example = "true") @NotNull(message = "是否为主配置不能为空") private Boolean master; + @ApiModelProperty(value = "存储配置", required = true) + private FileClientConfig config; + @ApiModelProperty(value = "创建时间", required = true) private Date createTime; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java index 1c2b59ba8..31db3d572 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper; import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaColumnMapper; import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaTableMapper; import cn.iocoder.yudao.module.infra.enums.codegen.CodegenImportTypeEnum; -import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum; import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties; import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder; import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine; @@ -26,7 +25,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -81,10 +83,7 @@ public class CodegenServiceImpl implements CodegenService { codegenTableMapper.insert(table); // 构建 CodegenColumnDO 数组,插入到 DB 中 List columns = codegenBuilder.buildColumns(schemaColumns); - columns.forEach(column -> { - column.setTableId(table.getId()); - codegenColumnMapper.insert(column); // TODO 批量插入 - }); + codegenColumnMapper.insertBatch(columns); return table.getId(); } @@ -198,10 +197,7 @@ public class CodegenServiceImpl implements CodegenService { // 插入新增的字段 List columns = codegenBuilder.buildColumns(schemaColumns); - columns.forEach(column -> { - column.setTableId(tableId); - codegenColumnMapper.insert(column); // TODO 批量插入 - }); + codegenColumnMapper.insertBatch(columns); // 删除不存在的字段 if (CollUtil.isNotEmpty(deleteColumnIds)) { codegenColumnMapper.deleteBatchIds(deleteColumnIds); diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java index deb4c1e0e..d74f1db0a 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java @@ -22,6 +22,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.validation.annotation.Validated; import javax.annotation.PostConstruct; @@ -33,6 +36,7 @@ import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS; /** @@ -151,13 +155,23 @@ public class FileConfigServiceImpl implements FileConfigService { } @Override + @Transactional(rollbackFor = Exception.class) public void updateFileConfigMaster(Long id) { // 校验存在 this.validateFileConfigExists(id); + // 更新其它为非 master + fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false)); // 更新 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); // 发送刷新配置的消息 - fileConfigProducer.sendFileConfigRefreshMessage(); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + fileConfigProducer.sendFileConfigRefreshMessage(); + } + + }); } private FileClientConfig parseClientConfig(Integer storage, Map config) { @@ -174,7 +188,10 @@ public class FileConfigServiceImpl implements FileConfigService { @Override public void deleteFileConfig(Long id) { // 校验存在 - this.validateFileConfigExists(id); + FileConfigDO config = this.validateFileConfigExists(id); + if (Boolean.TRUE.equals(config.getMaster())) { + throw exception(FILE_CONFIG_DELETE_FAIL_MASTER); + } // 删除 fileConfigMapper.deleteById(id); // 发送刷新配置的消息 diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java index 02008bce8..f519b4ee9 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java @@ -25,8 +25,7 @@ public interface RoleMenuMapper extends BaseMapperX { entity.setMenuId(menuId); return entity; }).collect(Collectors.toList()); - // TODO 芋艿,mybatis plus 增加批量插入的功能 - list.forEach(this::insert); + insertBatch(list); } default void deleteListByRoleIdAndMenuIds(Long roleId, Collection menuIds) { diff --git a/yudao-ui-admin/src/api/infra/fileConfig.js b/yudao-ui-admin/src/api/infra/fileConfig.js new file mode 100755 index 000000000..4b75773bf --- /dev/null +++ b/yudao-ui-admin/src/api/infra/fileConfig.js @@ -0,0 +1,59 @@ +import request from '@/utils/request' + +// 创建文件配置 +export function createFileConfig(data) { + return request({ + url: '/infra/file-config/create', + method: 'post', + data: data + }) +} + +// 更新文件配置 +export function updateFileConfig(data) { + return request({ + url: '/infra/file-config/update', + method: 'put', + data: data + }) +} + +// 更新文件配置为主配置 +export function updateFileConfigMaster(id) { + return request({ + url: '/infra/file-config/update-master?id=' + id, + method: 'put' + }) +} + +// 删除文件配置 +export function deleteFileConfig(id) { + return request({ + url: '/infra/file-config/delete?id=' + id, + method: 'delete' + }) +} + +// 获得文件配置 +export function getFileConfig(id) { + return request({ + url: '/infra/file-config/get?id=' + id, + method: 'get' + }) +} + +// 获得文件配置分页 +export function getFileConfigPage(query) { + return request({ + url: '/infra/file-config/page', + method: 'get', + params: query + }) +} + +export function testFileConfig(id) { + return request({ + url: '/infra/file-config/test?id=' + id, + method: 'get' + }) +} diff --git a/yudao-ui-admin/src/components/DictTag/index.vue b/yudao-ui-admin/src/components/DictTag/index.vue index 8f863046d..f7efc5eaf 100644 --- a/yudao-ui-admin/src/components/DictTag/index.vue +++ b/yudao-ui-admin/src/components/DictTag/index.vue @@ -17,7 +17,7 @@ export default { name: "DictTag", props: { type: String, - value: [Number, String, Array], + value: [Number, String, Boolean, Array], }, }; diff --git a/yudao-ui-admin/src/utils/dict.js b/yudao-ui-admin/src/utils/dict.js index 46f0c60d3..2e7d0a090 100644 --- a/yudao-ui-admin/src/utils/dict.js +++ b/yudao-ui-admin/src/utils/dict.js @@ -25,6 +25,7 @@ export const DICT_TYPE = { SYSTEM_ERROR_CODE_TYPE: 'system_error_code_type', // ========== INFRA 模块 ========== + INFRA_BOOLEAN_STRING: 'infra_boolean_string', INFRA_REDIS_TIMEOUT_TYPE: 'infra_redis_timeout_type', INFRA_JOB_STATUS: 'infra_job_status', INFRA_JOB_LOG_STATUS: 'infra_job_log_status', @@ -32,6 +33,7 @@ export const DICT_TYPE = { INFRA_CONFIG_TYPE: 'infra_config_type', INFRA_CODEGEN_TEMPLATE_TYPE: 'infra_codegen_template_type', INFRA_CODEGEN_SCENE: 'infra_codegen_scene', + INFRA_FILE_STORAGE: 'infra_file_storage', // ========== BPM 模块 ========== BPM_MODEL_CATEGORY: 'bpm_model_category', diff --git a/yudao-ui-admin/src/views/infra/fileConfig/index.vue b/yudao-ui-admin/src/views/infra/fileConfig/index.vue new file mode 100755 index 000000000..1643dd638 --- /dev/null +++ b/yudao-ui-admin/src/views/infra/fileConfig/index.vue @@ -0,0 +1,313 @@ + + + From cdcecd0d4a2d782cd7043cd34510e6004a837fea Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 16 Mar 2022 20:47:17 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E5=AE=8C=E5=96=84=20FileConfig=20=E7=9A=84?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/file/FileConfigServiceImpl.java | 3 + .../file/FileConfigServiceImplTest.java | 162 ++++++++++++++++-- .../test/resources/application-unit-test.yaml | 5 +- .../src/test/resources/sql/clean.sql | 1 + .../src/test/resources/sql/create_tables.sql | 15 ++ 5 files changed, 165 insertions(+), 21 deletions(-) diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java index d74f1db0a..51cc21bbc 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Scheduled; @@ -58,6 +59,7 @@ public class FileConfigServiceImpl implements FileConfigService { /** * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 */ + @Getter private volatile Date maxUpdateTime; @Resource @@ -65,6 +67,7 @@ public class FileConfigServiceImpl implements FileConfigService { /** * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的 */ + @Getter private FileClient masterFileClient; @Resource diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java index e97df1ac6..457c0682a 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java @@ -1,25 +1,42 @@ package cn.iocoder.yudao.module.infra.service.file; +import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import cn.iocoder.yudao.framework.file.core.client.FileClientFactory; +import cn.iocoder.yudao.framework.file.core.client.local.LocalFileClientConfig; +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; +import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer; import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; -import org.junit.jupiter.api.Disabled; +import lombok.Data; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import javax.annotation.Resource; +import javax.validation.Validator; +import java.io.Serializable; +import java.util.Map; +import static cn.hutool.core.util.RandomUtil.randomEle; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; /** * {@link FileConfigServiceImpl} 的单元测试类 @@ -35,10 +52,44 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @Resource private FileConfigMapper fileConfigMapper; + @MockBean + private FileConfigProducer fileConfigProducer; + @MockBean + private Validator validator; + @MockBean + private FileClientFactory fileClientFactory; + + @Test + public void testInitLocalCache() { + // mock 数据 + FileConfigDO configDO1 = randomFileConfigDO().setId(1L).setMaster(true); + fileConfigMapper.insert(configDO1); + FileConfigDO configDO2 = randomFileConfigDO().setId(2L).setMaster(false); + fileConfigMapper.insert(configDO2); + // mock fileClientFactory 获得 master + FileClient masterFileClient = mock(FileClient.class); + when(fileClientFactory.getFileClient(eq(1L))).thenReturn(masterFileClient); + + // 调用 + fileConfigService.initFileClients(); + // 断言 fileClientFactory 调用 + verify(fileClientFactory).createOrUpdateFileClient(eq(1L), + eq(configDO1.getStorage()), eq(configDO1.getConfig())); + verify(fileClientFactory).createOrUpdateFileClient(eq(2L), + eq(configDO2.getStorage()), eq(configDO2.getConfig())); + assertSame(masterFileClient, fileConfigService.getMasterFileClient()); + // 断言 maxUpdateTime 缓存 + assertEquals(max(configDO1.getUpdateTime(), configDO2.getUpdateTime()), + fileConfigService.getMaxUpdateTime()); + } + @Test public void testCreateFileConfig_success() { // 准备参数 - FileConfigCreateReqVO reqVO = randomPojo(FileConfigCreateReqVO.class); + Map config = MapUtil.builder().put("basePath", "/yunai") + .put("domain", "https://www.iocoder.cn").build(); + FileConfigCreateReqVO reqVO = randomPojo(FileConfigCreateReqVO.class, + o -> o.setStorage(FileStorageEnum.LOCAL.getStorage()).setConfig(config)); // 调用 Long fileConfigId = fileConfigService.createFileConfig(reqVO); @@ -46,24 +97,37 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { assertNotNull(fileConfigId); // 校验记录的属性是否正确 FileConfigDO fileConfig = fileConfigMapper.selectById(fileConfigId); - assertPojoEquals(reqVO, fileConfig); + assertPojoEquals(reqVO, fileConfig, "config"); + assertFalse(fileConfig.getMaster()); + assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); + assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); + // verify 调用 + verify(fileConfigProducer).sendFileConfigRefreshMessage(); } @Test public void testUpdateFileConfig_success() { // mock 数据 - FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class); + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class, o -> o.setStorage(FileStorageEnum.LOCAL.getStorage()) + .setConfig(new LocalFileClientConfig().setBasePath("/yunai").setDomain("https://www.iocoder.cn"))); fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 // 准备参数 FileConfigUpdateReqVO reqVO = randomPojo(FileConfigUpdateReqVO.class, o -> { o.setId(dbFileConfig.getId()); // 设置更新的 ID + Map config = MapUtil.builder().put("basePath", "/yunai2") + .put("domain", "https://doc.iocoder.cn").build(); + o.setConfig(config); }); // 调用 fileConfigService.updateFileConfig(reqVO); // 校验是否更新正确 FileConfigDO fileConfig = fileConfigMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, fileConfig); + assertPojoEquals(reqVO, fileConfig, "config"); + assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); + assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); + // verify 调用 + verify(fileConfigProducer).sendFileConfigRefreshMessage(); } @Test @@ -75,10 +139,33 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { assertServiceException(() -> fileConfigService.updateFileConfig(reqVO), FILE_CONFIG_NOT_EXISTS); } + @Test + public void testUpdateFileConfigMaster_success() { + // mock 数据 + FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false); + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 + FileConfigDO masterFileConfig = randomFileConfigDO().setMaster(true); + fileConfigMapper.insert(masterFileConfig);// @Sql: 先插入出一条存在的数据 + + // 调用 + fileConfigService.updateFileConfigMaster(dbFileConfig.getId()); + // 断言数据 + assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster()); + assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster()); + // verify 调用 + verify(fileConfigProducer).sendFileConfigRefreshMessage(); + } + + @Test + public void testUpdateFileConfigMaster_notExists() { + // 调用, 并断言异常 + assertServiceException(() -> fileConfigService.updateFileConfigMaster(randomLongId()), FILE_CONFIG_NOT_EXISTS); + } + @Test public void testDeleteFileConfig_success() { // mock 数据 - FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class); + FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false); fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 // 准备参数 Long id = dbFileConfig.getId(); @@ -87,6 +174,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { fileConfigService.deleteFileConfig(id); // 校验数据不存在了 assertNull(fileConfigMapper.selectById(id)); + // verify 调用 + verify(fileConfigProducer).sendFileConfigRefreshMessage(); } @Test @@ -99,27 +188,36 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testDeleteFileConfig_master() { + // mock 数据 + FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(true); + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbFileConfig.getId(); + + // 调用, 并断言异常 + assertServiceException(() -> fileConfigService.deleteFileConfig(id), FILE_CONFIG_DELETE_FAIL_MASTER); + } + + @Test public void testGetFileConfigPage() { // mock 数据 - FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class, o -> { // 等会查询到 - o.setName(null); - o.setStorage(null); - o.setCreateTime(null); - }); + FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码") + .setStorage(FileStorageEnum.LOCAL.getStorage()); + dbFileConfig.setCreateTime(buildTime(2022, 11, 11));// 等会查询到 fileConfigMapper.insert(dbFileConfig); // 测试 name 不匹配 - fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName(null))); + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码"))); // 测试 storage 不匹配 - fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(null))); + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage()))); // 测试 createTime 不匹配 - fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(null))); + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(buildTime(2022, 12, 12)))); // 准备参数 FileConfigPageReqVO reqVO = new FileConfigPageReqVO(); - reqVO.setName(null); - reqVO.setStorage(null); - reqVO.setBeginCreateTime(null); - reqVO.setEndCreateTime(null); + reqVO.setName("芋道"); + reqVO.setStorage(FileStorageEnum.LOCAL.getStorage()); + reqVO.setBeginCreateTime(buildTime(2022, 11, 10)); + reqVO.setEndCreateTime(buildTime(2022, 11, 12)); // 调用 PageResult pageResult = fileConfigService.getFileConfigPage(reqVO); @@ -129,4 +227,30 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { assertPojoEquals(dbFileConfig, pageResult.getList().get(0)); } + @Test + public void testFileConfig() { + // mock 数据 + FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false); + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbFileConfig.getId(); + // mock 获得 Client + FileClient fileClient = mock(FileClient.class); + when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient); + when(fileClient.upload(any(), any())).thenReturn("https://www.iocoder.cn"); + + // 调用,并断言 + assertEquals("https://www.iocoder.cn", fileConfigService.testFileConfig(id)); + } + + private FileConfigDO randomFileConfigDO() { + return randomPojo(FileConfigDO.class).setStorage(randomEle(FileStorageEnum.values()).getStorage()) + .setConfig(new EmptyFileClientConfig()); + } + + @Data + public static class EmptyFileClientConfig implements FileClientConfig, Serializable { + + } + } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml index 60914d97f..3a2079cdc 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml @@ -26,8 +26,9 @@ spring: port: 16379 # 端口(单元测试,使用 16379 端口) database: 0 # 数据库索引 -mybatis: +mybatis-plus: lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject --- #################### 定时任务相关配置 #################### @@ -46,4 +47,4 @@ mybatis: # 芋道配置项,设置当前项目所有自定义的配置 yudao: info: - base-package: cn.iocoder.yudao.module + base-package: cn.iocoder.yudao diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/clean.sql b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/clean.sql index 071c77bc7..cc8316837 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/clean.sql +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/clean.sql @@ -8,3 +8,4 @@ DELETE FROM "infra_api_access_log"; DELETE FROM "infra_file"; DELETE FROM "infra_api_error_log"; DELETE FROM "infra_test_demo"; +DELETE FROM "infra_file_config"; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql index 80213b4a7..0265690d0 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql @@ -16,6 +16,21 @@ CREATE TABLE IF NOT EXISTS "infra_config" ( PRIMARY KEY ("id") ) COMMENT '参数配置表'; +CREATE TABLE IF NOT EXISTS "infra_file_config" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(63) NOT NULL, + "storage" tinyint NOT NULL, + "remark" varchar(255), + "master" bit(1) NOT NULL, + "config" varchar(4096) NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '文件配置表'; + CREATE TABLE IF NOT EXISTS "infra_file" ( "id" varchar(188) NOT NULL, "type" varchar(63) DEFAULT NULL, From 87670d18fdf8c96f5a61861b41381d8adf9cecc7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 16 Mar 2022 23:31:26 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=96=B0=20File=20?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/core/client/AbstractFileClient.java | 4 +- .../file/core/client/db/DBFileClient.java | 15 +++- .../client/db/DBFileContentFrameworkDAO.java | 20 +++++ .../controller/admin/file/FileController.java | 24 ++--- .../controller/admin/file/vo/FileRespVO.java | 22 ----- .../file/vo/{ => file}/FilePageReqVO.java | 4 +- .../admin/file/vo/file/FileRespVO.java | 31 +++++++ .../infra/convert/file/FileConvert.java | 2 +- .../infra/dal/dataobject/file/FileDO.java | 18 ++-- .../dal/mysql/file/FileContentDAOImpl.java | 41 +++++++++ .../dal/mysql/file/FileContentMapper.java | 9 ++ .../infra/dal/mysql/file/FileMapper.java | 19 +--- .../file/config/FileConfiguration.java | 12 --- .../framework/file/config/FileProperties.java | 22 ----- .../infra/framework/file/package-info.java | 16 ---- .../config/SecurityConfiguration.java | 2 +- .../infra/service/file/FileConfigService.java | 16 ++++ .../service/file/FileConfigServiceImpl.java | 5 ++ .../infra/service/file/FileService.java | 11 +-- .../infra/service/file/FileServiceImpl.java | 52 +++++++---- .../infra/service/file/FileServiceTest.java | 87 +++++++++++-------- .../src/test/resources/sql/create_tables.sql | 7 +- .../src/main/resources/application-dev.yaml | 2 - .../src/main/resources/application-local.yaml | 2 - .../src/main/resources/application.yaml | 2 +- yudao-ui-admin/src/views/infra/file/index.vue | 37 +++++--- 26 files changed, 277 insertions(+), 205 deletions(-) delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java rename yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/{ => file}/FilePageReqVO.java (92%) create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java index 262ceab20..a2d7304a8 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java @@ -1,8 +1,6 @@ package cn.iocoder.yudao.framework.file.core.client; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.file.core.client.FileClient; -import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.extern.slf4j.Slf4j; /** @@ -65,7 +63,7 @@ public abstract class AbstractFileClient implem * @return URL 访问地址 */ protected String formatFileUrl(String domain, String path) { - return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path); + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java index 57c649a88..a227cc314 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java @@ -18,24 +18,31 @@ public class DBFileClient extends AbstractFileClient { @Override protected void doInit() { - dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); } @Override public String upload(byte[] content, String path) { - dao.insert(getId(), path, content); + getDao().insert(getId(), path, content); // 拼接返回路径 return super.formatFileUrl(config.getDomain(), path); } @Override public void delete(String path) { - dao.delete(getId(), path); + getDao().delete(getId(), path); } @Override public byte[] getContent(String path) { - return dao.selectContent(getId(), path); + return getDao().selectContent(getId(), path); + } + + private DBFileContentFrameworkDAO getDao() { + // 延迟获取,因为 SpringUtil 初始化太慢 + if (dao == null) { + dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); + } + return dao; } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java index 44850ed77..9423e065d 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java @@ -7,10 +7,30 @@ package cn.iocoder.yudao.framework.file.core.client.db; */ public interface DBFileContentFrameworkDAO { + /** + * 插入文件内容 + * + * @param configId 配置编号 + * @param path 路径 + * @param content 内容 + */ void insert(Long configId, String path, byte[] content); + /** + * 删除文件内容 + * + * @param configId 配置编号 + * @param path 路径 + */ void delete(Long configId, String path); + /** + * 获得文件内容 + * + * @param configId 配置编号 + * @param path 路径 + * @return 内容 + */ byte[] selectContent(Long configId, String path); } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 7d449dbe8..e1cc6d076 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -4,8 +4,8 @@ import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.convert.file.FileConvert; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.service.file.FileService; @@ -50,9 +50,9 @@ public class FileController { @DeleteMapping("/delete") @ApiOperation("删除文件") - @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = String.class) + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) @PreAuthorize("@ss.hasPermission('infra:file:delete')") - public CommonResult deleteFile(@RequestParam("id") String id) { + public CommonResult deleteFile(@RequestParam("id") Long id) { fileService.deleteFile(id); return success(true); } @@ -60,19 +60,19 @@ public class FileController { @GetMapping("/{configId}/get/{path}") @ApiOperation("下载文件") @ApiImplicitParams({ - @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = String.class), + @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = Long.class), @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class) }) - public void getFile(HttpServletResponse response, - @PathVariable("configId") String configId, - @PathVariable("path") String path) throws IOException { - FileDO file = fileService.getFile(path); - if (file == null) { - log.warn("[getFile][path({}) 文件不存在]", path); + public void getFileContent(HttpServletResponse response, + @PathVariable("configId") Long configId, + @PathVariable("path") String path) throws IOException { + byte[] content = fileService.getFileContent(configId, path); + if (content == null) { + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); response.setStatus(HttpStatus.NOT_FOUND.value()); return; } - ServletUtils.writeAttachment(response, path, file.getContent()); + ServletUtils.writeAttachment(response, path, content); } @GetMapping("/page") diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java deleted file mode 100644 index 838f3272b..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.util.Date; - -@ApiModel(value = "管理后台 - 文件 Response VO", description = "不返回 content 字段,太大") -@Data -public class FileRespVO { - - @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") - private String id; - - @ApiModelProperty(value = "文件类型", required = true, example = "jpg") - private String type; - - @ApiModelProperty(value = "创建时间", required = true) - private Date createTime; - -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java similarity index 92% rename from yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java rename to yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java index bf67300ab..346314e83 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.annotations.ApiModel; @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ public class FilePageReqVO extends PageParam { @ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配") - private String id; + private String path; @ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配") private String type; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java new file mode 100644 index 000000000..8e39a7520 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@ApiModel(value = "管理后台 - 文件 Response VO", description = "不返回 content 字段,太大") +@Data +public class FileRespVO { + + @ApiModelProperty(value = "文件编号", required = true, example = "1024") + private Long id; + + @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") + private String path; + + @ApiModelProperty(value = "文件 URL", required = true, example = "https://www.iocoder.cn/yudao.jpg") + private String url; + + @ApiModelProperty(value = "文件类型", example = "jpg") + private String type; + + @ApiModelProperty(value = "文件大小", example = "2048", required = true) + private Integer size; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java index affeddba4..ee7e5186b 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.infra.convert.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java index 2803c9d10..e080fa2fb 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java @@ -1,9 +1,7 @@ package cn.iocoder.yudao.module.infra.dal.dataobject.file; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -27,8 +25,7 @@ public class FileDO extends BaseDO { /** * 编号,数据库自增 */ - @TableId(type = IdType.INPUT) - private String id; + private Long id; /** * 配置编号 * @@ -39,6 +36,10 @@ public class FileDO extends BaseDO { * 路径,即文件名 */ private String path; + /** + * 访问地址 + */ + private String url; /** * 文件类型 * @@ -46,18 +47,9 @@ public class FileDO extends BaseDO { */ @TableField(value = "`type`") private String type; - /** - * 访问地址 - */ - private String url; /** * 文件大小 */ private Integer size; - /** - * 文件内容 - */ - @Deprecated - private byte[] content; } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java new file mode 100644 index 000000000..c4dcfe8a0 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.infra.dal.mysql.file; + +import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; + +@Repository +public class FileContentDAOImpl implements DBFileContentFrameworkDAO { + + @Resource + private FileContentMapper fileContentMapper; + + @Override + public void insert(Long configId, String path, byte[] content) { + FileContentDO entity = new FileContentDO().setConfigId(configId) + .setPath(path).setContent(content); + fileContentMapper.insert(entity); + } + + @Override + public void delete(Long configId, String path) { + fileContentMapper.delete(buildQuery(configId, path)); + } + + @Override + public byte[] selectContent(Long configId, String path) { + FileContentDO fileContentDO = fileContentMapper.selectOne( + buildQuery(configId, path).select(FileContentDO::getContent)); + return fileContentDO != null ? fileContentDO.getContent() : null; + } + + private LambdaQueryWrapper buildQuery(Long configId, String path) { + return new LambdaQueryWrapper() + .eq(FileContentDO::getConfigId, configId) + .eq(FileContentDO::getPath, path); + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java new file mode 100644 index 000000000..501979dbe --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java @@ -0,0 +1,9 @@ +package cn.iocoder.yudao.module.infra.dal.mysql.file; + +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FileContentMapper extends BaseMapper { +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java index 1786d130f..1938ee285 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.dal.mysql.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import org.apache.ibatis.annotations.Mapper; @@ -17,25 +17,10 @@ public interface FileMapper extends BaseMapperX { default PageResult selectPage(FilePageReqVO reqVO) { return selectPage(reqVO, new QueryWrapperX() - .likeIfPresent("id", reqVO.getId()) + .likeIfPresent("path", reqVO.getPath()) .likeIfPresent("type", reqVO.getType()) .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) .orderByDesc("create_time")); } - default Long selectCountById(String id) { - return selectCount(FileDO::getId, id); - } - - /** - * 基于 Path 获取文件 - * 实际上,是基于 ID 查询 - * - * @param path 路径 - * @return 文件 - */ - default FileDO selectByPath(String path) { - return selectById(path); - } - } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java deleted file mode 100644 index f944daa23..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.config; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * 文件 配置类 - */ -@Configuration -@EnableConfigurationProperties(FileProperties.class) -public class FileConfiguration { -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java deleted file mode 100644 index c112802b0..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotNull; - -@ConfigurationProperties(prefix = "yudao.file") -@Validated -@Data -public class FileProperties { - - /** - * 对应 FileController 的 getFile 方法 - */ - @NotNull(message = "基础文件路径不能为空") - private String basePath; - - // TODO 七牛、等等 - -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java deleted file mode 100644 index ee21b922e..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 文件的存储,推荐使用七牛、阿里云、华为云、腾讯云等文件服务 - * - * 在不采用云服务的情况下,我们有几种技术选型: - * 方案 1. 使用自建的文件服务,例如说 minIO、FastDFS 等等 - * 方案 2. 使用服务器的文件系统存储 - * 方案 3. 使用数据库进行存储 - * - * 如果考虑额外在搭建服务,推荐方案 1。 - * 对于方案 2 来说,如果要实现文件存储的高可用,需要多台服务器之间做实时同步,可以基于 rsync + inotify 来做 - * 对于方案 3 的话,实现起来最简单,但是数据库本身不适合存储海量的文件 - * - * 综合考虑,暂时使用方案 3 的方式,比较适合这样一个 all in one 的项目。 - * 随着文件的量级大了之后,还是推荐采用云服务。 - */ -package cn.iocoder.yudao.module.infra.framework.file; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java index 3b2a5501e..048411769 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java @@ -36,7 +36,7 @@ public class SecurityConfiguration { registry.antMatchers(adminSeverContextPath).anonymous() .antMatchers(adminSeverContextPath + "/**").anonymous(); // 文件的获取接口,可匿名访问 - registry.antMatchers(buildAdminApi("/infra/file/get/**"), buildAppApi("/infra/file/get/**")).anonymous(); + registry.antMatchers(buildAdminApi("/infra/file/*/get/**"), buildAppApi("/infra/file/get/**")).permitAll(); } }; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java index 1d7fcfaaa..326052688 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; @@ -83,4 +84,19 @@ public interface FileConfigService { */ String testFileConfig(Long id); + /** + * 获得指定编号的文件客户端 + * + * @param id 配置编号 + * @return 文件客户端 + */ + FileClient getFileClient(Long id); + + /** + * 获得 Master 文件客户端 + * + * @return 文件客户端 + */ + FileClient getMasterFileClient(); + } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java index 51cc21bbc..3d79bdeef 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java @@ -233,4 +233,9 @@ public class FileConfigServiceImpl implements FileConfigService { return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg"); } + @Override + public FileClient getFileClient(Long id) { + return fileClientFactory.getFileClient(id); + } + } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 4fb658aa5..ea5f31390 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.infra.service.file; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; @@ -33,14 +33,15 @@ public interface FileService { * * @param id 编号 */ - void deleteFile(String id); + void deleteFile(Long id); /** - * 获得文件 + * 获得文件内容 * + * @param configId 配置编号 * @param path 文件路径 - * @return 文件 + * @return 文件内容 */ - FileDO getFile(String path); + byte[] getFileContent(Long configId, String path); } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 1f1724ecd..3a3c3c010 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -1,18 +1,19 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; /** * 文件 Service 实现类 @@ -23,10 +24,10 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; public class FileServiceImpl implements FileService { @Resource - private FileMapper fileMapper; + private FileConfigService fileConfigService; @Resource - private FileProperties fileProperties; + private FileMapper fileMapper; @Override public PageResult getFilePage(FilePageReqVO pageReqVO) { @@ -35,36 +36,49 @@ public class FileServiceImpl implements FileService { @Override public String createFile(String path, byte[] content) { - if (fileMapper.selectCountById(path) > 0) { - throw exception(FILE_PATH_EXISTS); - } + // 上传到文件存储器 + FileClient client = fileConfigService.getMasterFileClient(); + Assert.notNull(client, "客户端(master) 不能为空"); + String url = client.upload(content, path); + // 保存到数据库 FileDO file = new FileDO(); - file.setId(path); + file.setConfigId(client.getId()); + file.setPath(path); + file.setUrl(url); file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); - file.setContent(content); + file.setSize(content.length); fileMapper.insert(file); - // 拼接路径返回 - return fileProperties.getBasePath() + path; + return url; } @Override - public void deleteFile(String id) { + public void deleteFile(Long id) { // 校验存在 - this.validateFileExists(id); - // 更新 + FileDO file = this.validateFileExists(id); + + // 从文件存储器中删除 + FileClient client = fileConfigService.getFileClient(file.getConfigId()); + Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId()); + client.delete(file.getPath()); + + // 删除记录 fileMapper.deleteById(id); } - private void validateFileExists(String id) { - if (fileMapper.selectById(id) == null) { + private FileDO validateFileExists(Long id) { + FileDO fileDO = fileMapper.selectById(id); + if (fileDO == null) { throw exception(FILE_NOT_EXISTS); } + return fileDO; } @Override - public FileDO getFile(String path) { - return fileMapper.selectByPath(path); + public byte[] getFileContent(Long configId, String path) { + FileClient client = fileConfigService.getFileClient(configId); + Assert.notNull(client, "客户端({}) 不能为空", configId); + return client.getContent(path); } } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java index 259502ca7..6ff74a21f 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.io.resource.ResourceUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; @@ -17,47 +17,46 @@ import javax.annotation.Resource; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; -@Import({FileServiceImpl.class, FileProperties.class}) +@Import({FileServiceImpl.class}) public class FileServiceTest extends BaseDbUnitTest { @Resource private FileService fileService; - @MockBean - private FileProperties fileProperties; - @Resource private FileMapper fileMapper; + @MockBean + private FileConfigService fileConfigService; + @Test public void testGetFilePage() { // mock 数据 FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 - o.setId("yunai"); + o.setPath("yunai"); o.setType("jpg"); o.setCreateTime(buildTime(2021, 1, 15)); }); fileMapper.insert(dbFile); - // 测试 id 不匹配 - fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setId("tudou"))); + // 测试 path 不匹配 + fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou"))); // 测试 type 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { - o.setId("yunai02"); o.setType("png"); })); // 测试 createTime 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { - o.setId("yunai03"); o.setCreateTime(buildTime(2020, 1, 15)); })); // 准备参数 FilePageReqVO reqVO = new FilePageReqVO(); - reqVO.setId("yunai"); + reqVO.setPath("yunai"); reqVO.setType("jp"); reqVO.setBeginCreateTime(buildTime(2021, 1, 10)); reqVO.setEndCreateTime(buildTime(2021, 1, 20)); @@ -67,7 +66,7 @@ public class FileServiceTest extends BaseDbUnitTest { // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); - AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0), "content"); + AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0)); } @Test @@ -75,52 +74,68 @@ public class FileServiceTest extends BaseDbUnitTest { // 准备参数 String path = randomString(); byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + // mock Master 文件客户端 + FileClient client = mock(FileClient.class); + when(fileConfigService.getMasterFileClient()).thenReturn(client); + String url = randomString(); + when(client.upload(same(content), same(path))).thenReturn(url); + when(client.getId()).thenReturn(10L); // 调用 - String url = fileService.createFile(path, content); + String result = fileService.createFile(path, content); // 断言 - assertEquals(fileProperties.getBasePath() + path, url); + assertEquals(result, url); // 校验数据 - FileDO file = fileMapper.selectById(path); - assertEquals(path, file.getId()); + FileDO file = fileMapper.selectOne(FileDO::getPath, path); + assertEquals(10L, file.getConfigId()); + assertEquals(path, file.getPath()); + assertEquals(url, file.getUrl()); assertEquals("jpg", file.getType()); - assertArrayEquals(content, file.getContent()); - } - - @Test - public void testCreateFile_exists() { - // mock 数据 - FileDO dbFile = randomPojo(FileDO.class); - fileMapper.insert(dbFile); - // 准备参数 - String path = dbFile.getId(); // 模拟已存在 - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); - - // 调用,并断言异常 - assertServiceException(() -> fileService.createFile(path, content), FILE_PATH_EXISTS); + assertEquals(content.length, file.getSize()); } @Test public void testDeleteFile_success() { // mock 数据 - FileDO dbFile = randomPojo(FileDO.class); + FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("tudou.jpg")); fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据 + // mock Master 文件客户端 + FileClient client = mock(FileClient.class); + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); // 准备参数 - String id = dbFile.getId(); + Long id = dbFile.getId(); // 调用 fileService.deleteFile(id); // 校验数据不存在了 assertNull(fileMapper.selectById(id)); + // 校验调用 + verify(client).delete(eq("tudou.jpg")); } @Test public void testDeleteFile_notExists() { // 准备参数 - String id = randomString(); + Long id = randomLongId(); // 调用, 并断言异常 assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS); } + @Test + public void testGetFileContent() { + // 准备参数 + Long configId = 10L; + String path = "tudou.jpg"; + // mock 方法 + FileClient client = mock(FileClient.class); + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); + byte[] content = new byte[]{}; + when(client.getContent(eq("tudou.jpg"))).thenReturn(content); + + // 调用 + byte[] result = fileService.getFileContent(configId, path); + // 断言 + assertSame(result, content); + } } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql index 0265690d0..3c86102e6 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql @@ -32,9 +32,12 @@ CREATE TABLE IF NOT EXISTS "infra_file_config" ( ) COMMENT '文件配置表'; CREATE TABLE IF NOT EXISTS "infra_file" ( - "id" varchar(188) NOT NULL, + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "config_id" bigint NOT NULL, + "path" varchar(512), + "url" varchar(1024), "type" varchar(63) DEFAULT NULL, - "content" blob NOT NULL, + "size" bigint NOT NULL, "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "updater" varchar(64) DEFAULT '', diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 6ce1bd981..a2f191b7b 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -172,8 +172,6 @@ yudao: session-timeout: 30m mock-enable: true mock-secret: test - file: - base-path: http://api-dashboard.yudao.iocoder.cn${yudao.web.admin-api.prefix}/infra/file/get/ xss: enable: false exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 42279a198..81d698300 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -184,8 +184,6 @@ yudao: session-timeout: 1d mock-enable: true mock-secret: test - file: - base-path: http://127.0.0.1:${server.port}${yudao.web.admin-api.prefix}/infra/file/get/ xss: enable: false exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 7f340cb33..7c99c5f5d 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -92,7 +92,7 @@ yudao: ignore-urls: - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 - - /admin-api/infra/file/get/* # 获取图片,和租户无关 + - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 ignore-tables: - system_tenant diff --git a/yudao-ui-admin/src/views/infra/file/index.vue b/yudao-ui-admin/src/views/infra/file/index.vue index 78f1dc17e..4b41401d9 100644 --- a/yudao-ui-admin/src/views/infra/file/index.vue +++ b/yudao-ui-admin/src/views/infra/file/index.vue @@ -3,8 +3,8 @@ - - + + @@ -31,21 +31,23 @@ - + + + - - - - + + + + + + + + - +