diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml index 8c4a9b586..73bb614cd 100644 --- a/yudao-framework/pom.xml +++ b/yudao-framework/pom.xml @@ -16,6 +16,7 @@ yudao-spring-boot-starter-web yudao-spring-boot-starter-security + yudao-spring-boot-starter-file yudao-spring-boot-starter-monitor yudao-spring-boot-starter-protection yudao-spring-boot-starter-config diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java index 9ff5b0583..d9a01747d 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java @@ -39,8 +39,8 @@ public class ValidationUtils { && PATTERN_XML_NCNAME.matcher(str).matches(); } - public static void validate(Validator validator, Object reqVO, Class... groups) { - Set> constraintViolations = validator.validate(reqVO, groups); + public static void validate(Validator validator, Object object, Class... groups) { + Set> constraintViolations = validator.validate(object, groups); if (CollUtil.isNotEmpty(constraintViolations)) { throw new ConstraintViolationException(constraintViolations); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index 38b8875a1..292b6cf01 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.framework.pay.core.client.impl; import cn.hutool.extra.validation.ValidationUtil; -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; @@ -11,7 +10,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; import lombok.extern.slf4j.Slf4j; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** @@ -26,7 +24,6 @@ public abstract class AbstractPayClient implemen * 渠道编号 */ private final Long channelId; - /** * 渠道编码 */ @@ -40,10 +37,6 @@ public abstract class AbstractPayClient implemen */ protected Config config; - protected Double calculateAmount(Long amount) { - return amount / 100.0; - } - public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) { this.channelId = channelId; this.channelCode = channelCode; @@ -75,6 +68,10 @@ public abstract class AbstractPayClient implemen this.init(); } + protected Double calculateAmount(Long amount) { + return amount / 100.0; + } + @Override public Long getId() { return channelId; @@ -96,12 +93,9 @@ public abstract class AbstractPayClient implemen return result; } - - protected abstract PayCommonResult doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable; - @Override public PayCommonResult unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { PayCommonResult resp; @@ -115,7 +109,6 @@ public abstract class AbstractPayClient implemen return resp; } - protected abstract PayCommonResult doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; } diff --git a/yudao-framework/yudao-spring-boot-starter-file/pom.xml b/yudao-framework/yudao-spring-boot-starter-file/pom.xml new file mode 100644 index 000000000..126bf9b77 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/pom.xml @@ -0,0 +1,69 @@ + + + + yudao-framework + cn.iocoder.boot + ${revision} + + 4.0.0 + yudao-spring-boot-starter-file + + ${project.artifactId} + 文件客户端,支持多种存储器 + 1. file:本地磁盘 + 2. ftp:FTP 服务器 + 3. db:数据库 + 4. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等 + + https://github.com/YunaiV/ruoyi-vue-pro + + + + cn.iocoder.boot + yudao-common + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.slf4j + slf4j-api + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + + + software.amazon.awssdk + s3 + 2.17.147 + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + test + + + + diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java new file mode 100644 index 000000000..60e0fc51b --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.framework.file.core.client; + +/** + * 文件客户端 + * + * @author 芋道源码 + */ +public interface FileClient { + + /** + * 获得客户端编号 + * + * @return 客户端编号 + */ + Long getId(); + + /** + * 上传文件 + * + * @param content 文件流 + * @param path 相对路径 + * @return 完整路径,即 HTTP 访问地址 + */ + String upload(byte[] content, String path); + + /** + * 删除文件 + * + * @param path 相对路径 + */ + void delete(String path); + + /** + * 获得文件的内容 + * + * @param path 相对路径 + * @return 文件的内容 + */ + byte[] getContent(String path); + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java new file mode 100644 index 000000000..9461c05d8 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClientConfig.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.framework.file.core.client; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * 文件客户端的配置 + * 不同实现的客户端,需要不同的配置,通过子类来定义 + * + * @author 芋道源码 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// @JsonTypeInfo 注解的作用,Jackson 多态 +// 1. 序列化到时数据库时,增加 @class 属性。 +// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 +public interface FileClientConfig { +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java new file mode 100644 index 000000000..13f104118 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.framework.file.core.client.impl; + +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件客户端的抽象类,提供模板方法,减少子类的冗余代码 + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractFileClient implements FileClient { + + /** + * 配置编号 + */ + private final Long id; + /** + * 文件配置 + */ + protected Config config; + + public AbstractFileClient(Long id, Config config) { + this.id = id; + this.config = config; + } + + /** + * 初始化 + */ + public final void init() { + doInit(); + log.info("[init][配置({}) 初始化完成]", config); + } + + /** + * 自定义初始化 + */ + protected abstract void doInit(); + + public final void refresh(Config config) { + // 判断是否更新 + if (config.equals(this.config)) { + return; + } + log.info("[refresh][配置({})发生变化,重新初始化]", config); + this.config = config; + // 初始化 + this.init(); + } + + @Override + public Long getId() { + return id; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java new file mode 100644 index 000000000..09d98398e --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.s3; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.net.URI; + +import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU; + +/** + * 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 + * + * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库 + * + * @author 芋道源码 + */ +public class S3FileClient extends AbstractFileClient { + + private S3Client client; + + public S3FileClient(Long id, S3FileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全 domain + if (StrUtil.isEmpty(config.getDomain())) { + config.setDomain(createDomain()); + } + // 初始化客户端 + client = S3Client.builder() + .serviceConfiguration(sb -> sb.pathStyleAccessEnabled(false) // 关闭路径风格 + .chunkedEncodingEnabled(false)) // 禁用 chunk + .endpointOverride(createURI()) // 上传地址 + .region(Region.of(config.getRegion())) // Region + .credentialsProvider(StaticCredentialsProvider.create( // 认证密钥 + AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()))) + .overrideConfiguration(cb -> cb.addExecutionInterceptor(new S3ModifyPathInterceptor(config.getBucket()))) + .build(); + } + + /** + * 基于 endpoint 构建调用云服务的 URI 地址 + * + * @return URI 地址 + */ + private URI createURI() { + String uri; + // 如果是七牛,无需拼接 bucket + if (config.getEndpoint().contains(ENDPOINT_QINIU)) { + uri = StrUtil.format("https://{}", config.getEndpoint()); + } else { + uri = StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); + } + return URI.create(uri); + } + + /** + * 基于 bucket + endpoint 构建访问的 Domain 地址 + * + * @return Domain 地址 + */ + private String createDomain() { + return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); + } + + @Override + public String upload(byte[] content, String path) { + // 执行上传 + PutObjectRequest.Builder request = PutObjectRequest.builder() + .bucket(config.getBucket()) // bucket 必须传递 + .key(path); // 相对路径作为 key + client.putObject(request.build(), RequestBody.fromBytes(content)); + // 拼接返回路径 + return config.getDomain() + "/" + path; + } + + @Override + public void delete(String path) { + + } + + @Override + public byte[] getContent(String path) { + return new byte[0]; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java new file mode 100644 index 000000000..858759c26 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClientConfig.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.s3; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.NotNull; + +/** + * S3 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class S3FileClientConfig implements FileClientConfig { + + public static final String ENDPOINT_QINIU = "qiniucs.com"; + + /** + * 节点地址 + * 1. MinIO: + * 2. 阿里云:https://help.aliyun.com/document_detail/31837.html + * 3. 腾讯云: + * 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname + * 5. 华为云: + */ + @NotNull(message = "endpoint 不能为空") + private String endpoint; + /** + * 自定义域名 + * 1. MinIO: + * 2. 阿里云:https://help.aliyun.com/document_detail/31836.html + * 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142 + * 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name + * 5. 华为云: + */ + @URL(message = "domain 必须是 URL 格式") + private String domain; + /** + * 区域 + * 1. MinIO: + * 2. 阿里云:https://help.aliyun.com/document_detail/31837.html + * 3. 腾讯云: + * 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname + * 5. 华为云: + */ + @NotNull(message = "region 不能为空") + private String region; + /** + * 存储 Bucket + */ + @NotNull(message = "bucket 不能为空") + private String bucket; + + /** + * 访问 Key + * 1. MinIO: + * 2. 阿里云: + * 3. 腾讯云:https://console.cloud.tencent.com/cam/capi + * 4. 七牛云:https://portal.qiniu.com/user/key + * 5. 华为云: + */ + @NotNull(message = "accessKey 不能为空") + private String accessKey; + /** + * 访问 Secret + */ + @NotNull(message = "accessSecret 不能为空") + private String accessSecret; + + @AssertTrue(message = "domain 不能为空") + @SuppressWarnings("RedundantIfStatement") + public boolean isDomainValid() { + // 如果是七牛,必须带有 domain + if (endpoint.contains(ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { + return false; + } + return true; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java new file mode 100644 index 000000000..2f4f19f84 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3ModifyPathInterceptor.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.framework.file.core.client.impl.s3; + +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * S3 修改路径的拦截器,移除多余的 Bucket 前缀。 + * 如果不使用该拦截器,希望上传的路径是 /tudou.jpg 时,会被添加成 /bucket/tudou.jpg + * + * @author 芋道源码 + */ +public class S3ModifyPathInterceptor implements ExecutionInterceptor { + + private final String bucket; + + public S3ModifyPathInterceptor(String bucket) { + this.bucket = "/" + bucket; + } + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + SdkHttpRequest request = context.httpRequest(); + SdkHttpRequest.Builder rb = SdkHttpRequest.builder().protocol(request.protocol()).host(request.host()).port(request.port()) + .method(request.method()).headers(request.headers()).rawQueryParameters(request.rawQueryParameters()); + // 移除 path 前的 bucket 路径 + if (request.encodedPath().startsWith(bucket)) { + rb.encodedPath(request.encodedPath().substring(bucket.length())); + } else { + rb.encodedPath(request.encodedPath()); + } + return rb.build(); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java new file mode 100644 index 000000000..113f3e5e5 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/config/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,避免 package 无法提交到 Git 仓库 + */ +package cn.iocoder.yudao.framework.file.config; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java new file mode 100644 index 000000000..b6c1cd4a9 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,避免 package 无法提交到 Git 仓库 + */ +package cn.iocoder.yudao.framework.file.core.client; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java new file mode 100644 index 000000000..5db3ac699 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.framework.file.core.client.s3; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClient; +import cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import javax.validation.Validation; + +public class S3FileClientTest { + + @Test + @Disabled // 阿里云 OSS,如果要集成测试,可以注释本行 + public void testAliyun() { + S3FileClientConfig config = new S3FileClientConfig(); + // 配置成你自己的 + config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY")); + config.setAccessSecret(System.getenv("ALIYUN_SECRET_KEY")); + config.setBucket("yunai-aoteman"); + config.setDomain(null); // 如果有自定义域名,则可以设置。http://ali-oss.iocoder.cn + // 默认北京的 endpoint + config.setEndpoint("oss-cn-beijing.aliyuncs.com"); + + // 执行上传 + testExecuteUpload(config); + } + + @Test + @Disabled // 腾讯云 COS,如果要集成测试,可以注释本行 + public void testQCloud() { + S3FileClientConfig config = new S3FileClientConfig(); + // 配置成你自己的 + config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY")); + config.setAccessSecret(System.getenv("QCLOUD_SECRET_KEY")); + config.setBucket("aoteman-1255880240"); + config.setDomain(null); // 如果有自定义域名,则可以设置。http://tengxun-oss.iocoder.cn + // 默认上海的 endpoint + config.setEndpoint("cos.ap-shanghai.myqcloud.com"); + config.setRegion("ap-shanghai"); + + // 执行上传 + testExecuteUpload(config); + } + + @Test + @Disabled // 七牛云存储,如果要集成测试,可以注释本行 + public void testQiniu() { + S3FileClientConfig config = new S3FileClientConfig(); + // 配置成你自己的 + config.setAccessKey(System.getenv("QINIU_ACCESS_KEY")); + config.setAccessSecret(System.getenv("QINIU_SECRET_KEY")); + config.setBucket("s3-test-yudao"); + config.setDomain("http://r8oo8po1q.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn + // 默认上海的 endpoint + config.setEndpoint("s3-cn-south-1.qiniucs.com"); + + // 执行上传 + testExecuteUpload(config); + } + + private void testExecuteUpload(S3FileClientConfig config) { + // 补全配置 + if (config.getRegion() == null) { + config.setRegion(StrUtil.subBefore(config.getEndpoint(), '.', false)); + } + ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); + // 创建 Client + S3FileClient client = new S3FileClient(0L, config); + client.init(); + // 上传文件 + String path = IdUtil.fastSimpleUUID() + ".jpg"; + byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + String fullPath = client.upload(content, path); + System.out.println("访问地址:" + fullPath); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java new file mode 100644 index 000000000..e1da5db23 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/enums/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,避免 package 无法提交到 Git 仓库 + */ +package cn.iocoder.yudao.framework.file.core.enums; diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg b/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg new file mode 100644 index 000000000..1447283cd Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-file/src/test/resources/file/erweima.jpg differ