Commit 18a5c4628480f7eab9a139a72873c3ec7e8dd181

Authored by YunaiV
1 parent 05d4aae6

完成 FileConfig 的后端模块

Showing 33 changed files with 1044 additions and 37 deletions
yudao-dependencies/pom.xml
@@ -53,7 +53,10 @@ @@ -53,7 +53,10 @@
53 <screw.version>1.0.5</screw.version> 53 <screw.version>1.0.5</screw.version>
54 <guava.version>30.1.1-jre</guava.version> 54 <guava.version>30.1.1-jre</guava.version>
55 <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version> 55 <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
  56 + <commons-net.version>3.8.0</commons-net.version>
  57 + <jsch.version>0.1.55</jsch.version>
56 <!-- 三方云服务相关 --> 58 <!-- 三方云服务相关 -->
  59 + <s3.version>2.17.147</s3.version>
57 <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version> 60 <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
58 <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version> 61 <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
59 <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> 62 <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
@@ -493,7 +496,28 @@ @@ -493,7 +496,28 @@
493 <version>${transmittable-thread-local.version}</version> 496 <version>${transmittable-thread-local.version}</version>
494 </dependency> 497 </dependency>
495 498
  499 + <dependency>
  500 + <groupId>commons-net</groupId>
  501 + <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
  502 + <version>${commons-net.version}</version>
  503 + </dependency>
  504 + <dependency>
  505 + <groupId>com.jcraft</groupId>
  506 + <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
  507 + <version>${jsch.version}</version>
  508 + </dependency>
  509 +
496 <!-- 三方云服务相关 --> 510 <!-- 三方云服务相关 -->
  511 + <dependency>
  512 + <groupId>cn.iocoder.boot</groupId>
  513 + <artifactId>yudao-spring-boot-starter-file</artifactId>
  514 + <version>${revision}</version>
  515 + </dependency>
  516 + <dependency>
  517 + <groupId>software.amazon.awssdk</groupId>
  518 + <artifactId>s3</artifactId>
  519 + <version>${s3.version}</version>
  520 + </dependency>
497 521
498 <!-- SMS SDK begin --> 522 <!-- SMS SDK begin -->
499 <dependency> 523 <dependency>
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; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.json;
2 2
3 import cn.hutool.core.util.ArrayUtil; 3 import cn.hutool.core.util.ArrayUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
  5 +import cn.hutool.json.JSONUtil;
5 import com.fasterxml.jackson.core.type.TypeReference; 6 import com.fasterxml.jackson.core.type.TypeReference;
6 import com.fasterxml.jackson.databind.JsonNode; 7 import com.fasterxml.jackson.databind.JsonNode;
7 import com.fasterxml.jackson.databind.ObjectMapper; 8 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -55,7 +56,6 @@ public class JsonUtils { @@ -55,7 +56,6 @@ public class JsonUtils {
55 if (StrUtil.isEmpty(text)) { 56 if (StrUtil.isEmpty(text)) {
56 return null; 57 return null;
57 } 58 }
58 -  
59 try { 59 try {
60 return objectMapper.readValue(text, clazz); 60 return objectMapper.readValue(text, clazz);
61 } catch (IOException e) { 61 } catch (IOException e) {
@@ -64,11 +64,26 @@ public class JsonUtils { @@ -64,11 +64,26 @@ public class JsonUtils {
64 } 64 }
65 } 65 }
66 66
  67 + /**
  68 + * 将字符串解析成指定类型的对象
  69 + * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
  70 + * 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
  71 + *
  72 + * @param text 字符串
  73 + * @param clazz 类型
  74 + * @return 对象
  75 + */
  76 + public static <T> T parseObject2(String text, Class<T> clazz) {
  77 + if (StrUtil.isEmpty(text)) {
  78 + return null;
  79 + }
  80 + return JSONUtil.toBean(text, clazz);
  81 + }
  82 +
67 public static <T> T parseObject(byte[] bytes, Class<T> clazz) { 83 public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
68 if (ArrayUtil.isEmpty(bytes)) { 84 if (ArrayUtil.isEmpty(bytes)) {
69 return null; 85 return null;
70 } 86 }
71 -  
72 try { 87 try {
73 return objectMapper.readValue(bytes, clazz); 88 return objectMapper.readValue(bytes, clazz);
74 } catch (IOException e) { 89 } catch (IOException e) {
@@ -90,7 +105,6 @@ public class JsonUtils { @@ -90,7 +105,6 @@ public class JsonUtils {
90 if (StrUtil.isEmpty(text)) { 105 if (StrUtil.isEmpty(text)) {
91 return new ArrayList<>(); 106 return new ArrayList<>();
92 } 107 }
93 -  
94 try { 108 try {
95 return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); 109 return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
96 } catch (IOException e) { 110 } catch (IOException e) {
yudao-framework/yudao-spring-boot-starter-file/pom.xml
@@ -54,20 +54,16 @@ @@ -54,20 +54,16 @@
54 <dependency> 54 <dependency>
55 <groupId>commons-net</groupId> 55 <groupId>commons-net</groupId>
56 <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 --> 56 <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
57 - <version>3.8.0</version>  
58 </dependency> 57 </dependency>
59 <dependency> 58 <dependency>
60 <groupId>com.jcraft</groupId> 59 <groupId>com.jcraft</groupId>
61 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 --> 60 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
62 - <version>0.1.55</version>  
63 </dependency> 61 </dependency>
64 62
65 -  
66 <!-- 三方云服务相关 --> 63 <!-- 三方云服务相关 -->
67 <dependency> 64 <dependency>
68 <groupId>software.amazon.awssdk</groupId> 65 <groupId>software.amazon.awssdk</groupId>
69 <artifactId>s3</artifactId> 66 <artifactId>s3</artifactId>
70 - <version>2.17.147</version>  
71 </dependency> 67 </dependency>
72 68
73 <!-- Test 测试相关 --> 69 <!-- Test 测试相关 -->
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 { @@ -5,10 +5,10 @@ public interface FileClientFactory {
5 /** 5 /**
6 * 获得文件客户端 6 * 获得文件客户端
7 * 7 *
8 - * @param channelId 渠道编号 8 + * @param configId 配置编号
9 * @return 文件客户端 9 * @return 文件客户端
10 */ 10 */
11 - FileClient getFileClient(Long channelId); 11 + FileClient getFileClient(Long configId);
12 12
13 /** 13 /**
14 * 创建文件客户端 14 * 创建文件客户端
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 { @@ -23,10 +23,10 @@ public class FileClientFactoryImpl implements FileClientFactory {
23 private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>(); 23 private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>();
24 24
25 @Override 25 @Override
26 - public FileClient getFileClient(Long channelId) {  
27 - AbstractFileClient<?> client = clients.get(channelId); 26 + public FileClient getFileClient(Long configId) {
  27 + AbstractFileClient<?> client = clients.get(configId);
28 if (client == null) { 28 if (client == null) {
29 - log.error("[getFileClient][配置编号({}) 找不到客户端]", channelId); 29 + log.error("[getFileClient][配置编号({}) 找不到客户端]", configId);
30 } 30 }
31 return client; 31 return client;
32 } 32 }
@@ -50,20 +50,7 @@ public class FileClientFactoryImpl implements FileClientFactory { @@ -50,20 +50,7 @@ public class FileClientFactoryImpl implements FileClientFactory {
50 FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage); 50 FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage);
51 Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum)); 51 Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum));
52 // 创建客户端 52 // 创建客户端
53 -// switch (storageEnum) {  
54 -// case WX_PUB: return (AbstractFileClient<Config>) new WXPubFileClient(channelId, (WXFileClientConfig) config);  
55 -// case WX_LITE: return (AbstractFileClient<Config>) new WXPubFileClient(channelId, (WXFileClientConfig) config);  
56 -// case WX_APP: return (AbstractFileClient<Config>) new WXPubFileClient(channelId, (WXFileClientConfig) config);  
57 -// case ALIPAY_WAP: return (AbstractFileClient<Config>) new AlipayWapFileClient(channelId, (AlipayFileClientConfig) config);  
58 -// case ALIPAY_QR: return (AbstractFileClient<Config>) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config);  
59 -// case ALIPAY_APP: return (AbstractFileClient<Config>) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config);  
60 -// case ALIPAY_PC: return (AbstractFileClient<Config>) new AlipayQrFileClient(channelId, (AlipayFileClientConfig) config);  
61 -// }  
62 return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config); 53 return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config);
63 -// storageEnum.getClientClass().newInstance()  
64 -// // 创建失败,错误日志 + 抛出异常  
65 -// log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config);  
66 -// throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config));  
67 } 54 }
68 55
69 } 56 }
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; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.file.core.client.s3;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; 4 import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
  5 +import com.fasterxml.jackson.annotation.JsonIgnore;
5 import lombok.Data; 6 import lombok.Data;
6 import org.hibernate.validator.constraints.URL; 7 import org.hibernate.validator.constraints.URL;
7 8
@@ -70,11 +71,12 @@ public class S3FileClientConfig implements FileClientConfig { @@ -70,11 +71,12 @@ public class S3FileClientConfig implements FileClientConfig {
70 @NotNull(message = "accessSecret 不能为空") 71 @NotNull(message = "accessSecret 不能为空")
71 private String accessSecret; 72 private String accessSecret;
72 73
73 - @AssertTrue(message = "domain 不能为空")  
74 @SuppressWarnings("RedundantIfStatement") 74 @SuppressWarnings("RedundantIfStatement")
  75 + @AssertTrue(message = "domain 不能为空")
  76 + @JsonIgnore
75 public boolean isDomainValid() { 77 public boolean isDomainValid() {
76 // 如果是七牛,必须带有 domain 78 // 如果是七牛,必须带有 domain
77 - if (endpoint.contains(ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { 79 + if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {
78 return false; 80 return false;
79 } 81 }
80 return true; 82 return true;
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 { @@ -49,10 +49,12 @@ public class S3FileClientTest {
49 public void testQiniu() { 49 public void testQiniu() {
50 S3FileClientConfig config = new S3FileClientConfig(); 50 S3FileClientConfig config = new S3FileClientConfig();
51 // 配置成你自己的 51 // 配置成你自己的
52 - config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));  
53 - config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));  
54 - config.setBucket("s3-test-yudao");  
55 - config.setDomain("http://r8oo8po1q.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn 52 +// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
  53 +// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));
  54 + config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8");
  55 + config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP");
  56 + config.setBucket("ruoyi-vue-pro");
  57 + config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn
56 // 默认上海的 endpoint 58 // 默认上海的 endpoint
57 config.setEndpoint("s3-cn-south-1.qiniucs.com"); 59 config.setEndpoint("s3-cn-south-1.qiniucs.com");
58 60
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 { @@ -42,8 +42,10 @@ public interface ErrorCodeConstants {
42 ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在"); 42 ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在");
43 ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变"); 43 ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变");
44 44
45 - // ========== 字典类型(测试) 1003000000 ==========  
46 - ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1003000000, "测试示例不存在"); 45 + // ========== 字典类型(测试)1001005000 ==========
  46 + ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1001005000, "测试示例不存在");
47 47
  48 + // ========== 文件配置 1001006000 ==========
  49 + ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在");
48 50
49 } 51 }
yudao-module-infra/yudao-module-infra-impl/pom.xml
@@ -109,6 +109,13 @@ @@ -109,6 +109,13 @@
109 <groupId>de.codecentric</groupId> 109 <groupId>de.codecentric</groupId>
110 <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 --> 110 <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
111 </dependency> 111 </dependency>
  112 +
  113 + <!-- 三方云服务相关 -->
  114 + <dependency>
  115 + <groupId>cn.iocoder.boot</groupId>
  116 + <artifactId>yudao-spring-boot-starter-file</artifactId>
  117 + </dependency>
  118 +
112 </dependencies> 119 </dependencies>
113 120
114 </project> 121 </project>
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.http 0 → 100644
  1 +### 请求 /infra/file-config/create 接口 => 成功
  2 +POST {{baseUrl}}/infra/file-config/create
  3 +Content-Type: application/json
  4 +tenant-id: {{adminTenentId}}
  5 +Authorization: Bearer {{token}}
  6 +
  7 +{
  8 + "name": "S3 - 七牛云",
  9 + "remark": "",
  10 + "storage": 20,
  11 + "config": {
  12 + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
  13 + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
  14 + "bucket": "ruoyi-vue-pro",
  15 + "endpoint": "s3-cn-south-1.qiniucs.com",
  16 + "domain": "http://test.yudao.iocoder.cn",
  17 + "region": "oss-cn-beijing"
  18 + }
  19 +}
  20 +
  21 +### 请求 /infra/file-config/update 接口 => 成功
  22 +PUT {{baseUrl}}/infra/file-config/update
  23 +Content-Type: application/json
  24 +tenant-id: {{adminTenentId}}
  25 +Authorization: Bearer {{token}}
  26 +
  27 +{
  28 + "id": 2,
  29 + "name": "S3 - 七牛云",
  30 + "remark": "",
  31 + "config": {
  32 + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
  33 + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
  34 + "bucket": "ruoyi-vue-pro",
  35 + "endpoint": "s3-cn-south-1.qiniucs.com",
  36 + "domain": "http://test.yudao.iocoder.cn",
  37 + "region": "oss-cn-beijing"
  38 + }
  39 +}
  40 +
  41 +### 请求 /infra/file-config/test 接口 => 成功
  42 +GET {{baseUrl}}/infra/file-config/test?id=2
  43 +Content-Type: application/json
  44 +tenant-id: {{adminTenentId}}
  45 +Authorization: Bearer {{token}}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileConfigController.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
  7 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigRespVO;
  8 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
  9 +import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
  10 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  11 +import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
  12 +import io.swagger.annotations.Api;
  13 +import io.swagger.annotations.ApiImplicitParam;
  14 +import io.swagger.annotations.ApiOperation;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import javax.annotation.Resource;
  20 +import javax.validation.Valid;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Api(tags = "管理后台 - 文件配置")
  25 +@RestController
  26 +@RequestMapping("/infra/file-config")
  27 +@Validated
  28 +public class FileConfigController {
  29 +
  30 + @Resource
  31 + private FileConfigService fileConfigService;
  32 +
  33 + @PostMapping("/create")
  34 + @ApiOperation("创建文件配置")
  35 + @PreAuthorize("@ss.hasPermission('infra:file-config:create')")
  36 + public CommonResult<Long> createFileConfig(@Valid @RequestBody FileConfigCreateReqVO createReqVO) {
  37 + return success(fileConfigService.createFileConfig(createReqVO));
  38 + }
  39 +
  40 + @PutMapping("/update")
  41 + @ApiOperation("更新文件配置")
  42 + @PreAuthorize("@ss.hasPermission('infra:file-config:update')")
  43 + public CommonResult<Boolean> updateFileConfig(@Valid @RequestBody FileConfigUpdateReqVO updateReqVO) {
  44 + fileConfigService.updateFileConfig(updateReqVO);
  45 + return success(true);
  46 + }
  47 +
  48 + @PutMapping("/update-master")
  49 + @ApiOperation("更新文件配置为 Master")
  50 + @PreAuthorize("@ss.hasPermission('infra:file-config:update')")
  51 + public CommonResult<Boolean> updateFileConfigMaster(@RequestParam("id") Long id) {
  52 + fileConfigService.updateFileConfigMaster(id);
  53 + return success(true);
  54 + }
  55 +
  56 + @DeleteMapping("/delete")
  57 + @ApiOperation("删除文件配置")
  58 + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
  59 + @PreAuthorize("@ss.hasPermission('infra:file-config:delete')")
  60 + public CommonResult<Boolean> deleteFileConfig(@RequestParam("id") Long id) {
  61 + fileConfigService.deleteFileConfig(id);
  62 + return success(true);
  63 + }
  64 +
  65 + @GetMapping("/get")
  66 + @ApiOperation("获得文件配置")
  67 + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
  68 + @PreAuthorize("@ss.hasPermission('infra:file-config:query')")
  69 + public CommonResult<FileConfigRespVO> getFileConfig(@RequestParam("id") Long id) {
  70 + FileConfigDO fileConfig = fileConfigService.getFileConfig(id);
  71 + return success(FileConfigConvert.INSTANCE.convert(fileConfig));
  72 + }
  73 +
  74 + @GetMapping("/page")
  75 + @ApiOperation("获得文件配置分页")
  76 + @PreAuthorize("@ss.hasPermission('infra:file-config:query')")
  77 + public CommonResult<PageResult<FileConfigRespVO>> getFileConfigPage(@Valid FileConfigPageReqVO pageVO) {
  78 + PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(pageVO);
  79 + return success(FileConfigConvert.INSTANCE.convertPage(pageResult));
  80 + }
  81 +
  82 + @GetMapping("/test")
  83 + @ApiOperation("测试文件配置是否正确")
  84 + @PreAuthorize("@ss.hasPermission('infra:file-config:query')")
  85 + public CommonResult<String> testFileConfig(@RequestParam("id") Long id) {
  86 + String url = fileConfigService.testFileConfig(id);
  87 + return success(url);
  88 + }
  89 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigBaseVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +/**
  9 +* 文件配置 Base VO,提供给添加、修改、详细的子 VO 使用
  10 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 +*/
  12 +@Data
  13 +public class FileConfigBaseVO {
  14 +
  15 + @ApiModelProperty(value = "配置名", required = true, example = "S3 - 阿里云")
  16 + @NotNull(message = "配置名不能为空")
  17 + private String name;
  18 +
  19 + @ApiModelProperty(value = "备注", example = "我是备注")
  20 + private String remark;
  21 +
  22 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigCreateReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +import java.util.Map;
  11 +
  12 +@ApiModel("管理后台 - 文件配置创建 Request VO")
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ToString(callSuper = true)
  16 +public class FileConfigCreateReqVO extends FileConfigBaseVO {
  17 +
  18 + @ApiModelProperty(value = "存储器", required = true, example = "1", notes = "参见 FileStorageEnum 枚举类")
  19 + @NotNull(message = "存储器不能为空")
  20 + private Integer storage;
  21 +
  22 + @ApiModelProperty(value = "存储配置", required = true, notes = "配置是动态参数,所以使用 Map 接收")
  23 + @NotNull(message = "存储配置不能为空")
  24 + private Map<String, Object> config;
  25 +
  26 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.util.Date;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@ApiModel("管理后台 - 文件配置分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +public class FileConfigPageReqVO extends PageParam {
  20 +
  21 + @ApiModelProperty(value = "配置名", example = "S3 - 阿里云")
  22 + private String name;
  23 +
  24 + @ApiModelProperty(value = "存储器", example = "1")
  25 + private Integer storage;
  26 +
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + @ApiModelProperty(value = "开始创建时间")
  29 + private Date beginCreateTime;
  30 +
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + @ApiModelProperty(value = "结束创建时间")
  33 + private Date endCreateTime;
  34 +
  35 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config;
  2 +
  3 +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +
  10 +import javax.validation.constraints.NotNull;
  11 +import java.util.Date;
  12 +
  13 +@ApiModel("管理后台 - 文件配置 Response VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +@ToString(callSuper = true)
  17 +public class FileConfigRespVO extends FileConfigBaseVO {
  18 +
  19 + @ApiModelProperty(value = "编号", required = true, example = "1")
  20 + private Long id;
  21 +
  22 + @ApiModelProperty(value = "存储配置", required = true)
  23 + private FileClientConfig config;
  24 +
  25 + @ApiModelProperty(value = "是否为主配置", required = true, example = "true")
  26 + @NotNull(message = "是否为主配置不能为空")
  27 + private Boolean master;
  28 +
  29 + @ApiModelProperty(value = "创建时间", required = true)
  30 + private Date createTime;
  31 +
  32 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigUpdateReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.config;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +import java.util.Map;
  11 +
  12 +@ApiModel("管理后台 - 文件配置更新 Request VO")
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ToString(callSuper = true)
  16 +public class FileConfigUpdateReqVO extends FileConfigBaseVO {
  17 +
  18 + @ApiModelProperty(value = "编号", required = true, example = "1")
  19 + @NotNull(message = "编号不能为空")
  20 + private Long id;
  21 +
  22 + @ApiModelProperty(value = "存储配置", required = true, notes = "配置是动态参数,所以使用 Map 接收")
  23 + @NotNull(message = "存储配置不能为空")
  24 + private Map<String, Object> config;
  25 +
  26 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConfigConvert.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.convert.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigRespVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
  7 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  8 +import org.mapstruct.Mapper;
  9 +import org.mapstruct.Mapping;
  10 +import org.mapstruct.factory.Mappers;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * 文件配置 Convert
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Mapper
  20 +public interface FileConfigConvert {
  21 +
  22 + FileConfigConvert INSTANCE = Mappers.getMapper(FileConfigConvert.class);
  23 +
  24 + @Mapping(target = "config", ignore = true)
  25 + FileConfigDO convert(FileConfigCreateReqVO bean);
  26 +
  27 + @Mapping(target = "config", ignore = true)
  28 + FileConfigDO convert(FileConfigUpdateReqVO bean);
  29 +
  30 + FileConfigRespVO convert(FileConfigDO bean);
  31 +
  32 + List<FileConfigRespVO> convertList(List<FileConfigDO> list);
  33 +
  34 + PageResult<FileConfigRespVO> convertPage(PageResult<FileConfigDO> page);
  35 +
  36 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileConfigDO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.dal.dataobject.file;
  2 +
  3 +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
  4 +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum;
  5 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  9 +import lombok.*;
  10 +
  11 +/**
  12 + * 文件配置表
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Data
  17 +@TableName(value = "infra_file_config", autoResultMap = true)
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class FileConfigDO extends BaseDO {
  24 +
  25 + /**
  26 + * 配置编号,数据库自增
  27 + */
  28 + private Long id;
  29 + /**
  30 + * 配置名
  31 + */
  32 + private String name;
  33 + /**
  34 + * 存储器
  35 + *
  36 + * 枚举 {@link FileStorageEnum}
  37 + */
  38 + private Integer storage;
  39 + /**
  40 + * 备注
  41 + */
  42 + private String remark;
  43 + /**
  44 + * 是否为主配置
  45 + *
  46 + * 由于我们可以配置多个文件配置,默认情况下,使用主配置进行文件的上传
  47 + */
  48 + private Boolean master;
  49 +
  50 + /**
  51 + * 支付渠道配置
  52 + */
  53 + @TableField(typeHandler = JacksonTypeHandler.class)
  54 + private FileClientConfig config;
  55 +
  56 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileContentDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.dal.dataobject.file;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import com.baomidou.mybatisplus.annotation.IdType;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.*;
  8 +
  9 +/**
  10 + * 文件内容表
  11 + *
  12 + * 专门用于存储 {@link cn.iocoder.yudao.framework.file.core.client.db.DBFileClient} 的文件内容
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Data
  17 +@TableName("infra_file_content")
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class FileContentDO extends BaseDO {
  24 +
  25 + /**
  26 + * 编号,数据库自增
  27 + */
  28 + @TableId(type = IdType.INPUT)
  29 + private String id;
  30 + /**
  31 + * 配置编号
  32 + *
  33 + * 关联 {@link FileConfigDO#getId()}
  34 + */
  35 + private Long configId;
  36 + /**
  37 + * 路径,即文件名
  38 + */
  39 + private String path;
  40 + /**
  41 + * 文件内容
  42 + */
  43 + private byte[] content;
  44 +
  45 +}
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; @@ -11,6 +11,7 @@ import java.io.InputStream;
11 11
12 /** 12 /**
13 * 文件表 13 * 文件表
  14 + * 每次文件上传,都会记录一条记录到该表中
14 * 15 *
15 * @author 芋道源码 16 * @author 芋道源码
16 */ 17 */
@@ -24,11 +25,21 @@ import java.io.InputStream; @@ -24,11 +25,21 @@ import java.io.InputStream;
24 public class FileDO extends BaseDO { 25 public class FileDO extends BaseDO {
25 26
26 /** 27 /**
27 - * 文件路径 28 + * 编号,数据库自增
28 */ 29 */
29 @TableId(type = IdType.INPUT) 30 @TableId(type = IdType.INPUT)
30 private String id; 31 private String id;
31 /** 32 /**
  33 + * 配置编号
  34 + *
  35 + * 关联 {@link FileConfigDO#getId()}
  36 + */
  37 + private Long configId;
  38 + /**
  39 + * 路径,即文件名
  40 + */
  41 + private String path;
  42 + /**
32 * 文件类型 43 * 文件类型
33 * 44 *
34 * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取 45 * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取
@@ -36,8 +47,17 @@ public class FileDO extends BaseDO { @@ -36,8 +47,17 @@ public class FileDO extends BaseDO {
36 @TableField(value = "`type`") 47 @TableField(value = "`type`")
37 private String type; 48 private String type;
38 /** 49 /**
  50 + * 访问地址
  51 + */
  52 + private String url;
  53 + /**
  54 + * 文件大小
  55 + */
  56 + private Integer size;
  57 + /**
39 * 文件内容 58 * 文件内容
40 */ 59 */
  60 + @Deprecated
41 private byte[] content; 61 private byte[] content;
42 62
43 } 63 }
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.dal.mysql.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
  7 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.util.Date;
  12 +
  13 +/**
  14 + * 文件配置 Mapper
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Mapper
  19 +public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
  20 +
  21 + default PageResult<FileConfigDO> selectPage(FileConfigPageReqVO reqVO) {
  22 + return selectPage(reqVO, new LambdaQueryWrapperX<FileConfigDO>()
  23 + .likeIfPresent(FileConfigDO::getName, reqVO.getName())
  24 + .eqIfPresent(FileConfigDO::getStorage, reqVO.getStorage())
  25 + .betweenIfPresent(FileConfigDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
  26 + .orderByDesc(FileConfigDO::getId));
  27 + }
  28 +
  29 + @Select("SELECT id FROM infra_file_config WHERE update_time > #{maxUpdateTime} LIMIT 1")
  30 + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
  31 +
  32 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.mq.consumer.file;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
  5 +import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link FileConfigRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class FileConfigRefreshConsumer extends AbstractChannelMessageListener<FileConfigRefreshMessage> {
  19 +
  20 + @Resource
  21 + private FileConfigService fileConfigService;
  22 +
  23 + @Override
  24 + public void onMessage(FileConfigRefreshMessage message) {
  25 + log.info("[onMessage][收到 FileConfig 刷新消息]");
  26 + fileConfigService.initFileClients();
  27 + }
  28 +
  29 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java deleted
1 -package cn.iocoder.yudao.module.infra.mq.consumer;  
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.mq.message.file;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 文件配置数据刷新 Message
  8 + */
  9 +@Data
  10 +public class FileConfigRefreshMessage extends AbstractChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "infra.file-config.refresh";
  15 + }
  16 +
  17 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java deleted
1 -package cn.iocoder.yudao.module.infra.mq.message;  
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.mq.producer.file;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
  4 +import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +import javax.annotation.Resource;
  8 +
  9 +/**
  10 + * 文件配置相关消息的 Producer
  11 + */
  12 +@Component
  13 +public class FileConfigProducer {
  14 +
  15 + @Resource
  16 + private RedisMQTemplate redisMQTemplate;
  17 +
  18 + /**
  19 + * 发送 {@link FileConfigRefreshMessage} 消息
  20 + */
  21 + public void sendFileConfigRefreshMessage() {
  22 + FileConfigRefreshMessage message = new FileConfigRefreshMessage();
  23 + redisMQTemplate.send(message);
  24 + }
  25 +
  26 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java deleted
1 -package cn.iocoder.yudao.module.infra.mq.producer;  
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.service.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
  7 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  8 +
  9 +import javax.validation.Valid;
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * 文件配置 Service 接口
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public interface FileConfigService {
  19 +
  20 + /**
  21 + * 初始化文件客户端
  22 + */
  23 + void initFileClients();
  24 +
  25 + /**
  26 + * 创建文件配置
  27 + *
  28 + * @param createReqVO 创建信息
  29 + * @return 编号
  30 + */
  31 + Long createFileConfig(@Valid FileConfigCreateReqVO createReqVO);
  32 +
  33 + /**
  34 + * 更新文件配置
  35 + *
  36 + * @param updateReqVO 更新信息
  37 + */
  38 + void updateFileConfig(@Valid FileConfigUpdateReqVO updateReqVO);
  39 +
  40 + /**
  41 + * 更新文件配置为 Master
  42 + *
  43 + * @param id 编号
  44 + */
  45 + void updateFileConfigMaster(Long id);
  46 +
  47 + /**
  48 + * 删除文件配置
  49 + *
  50 + * @param id 编号
  51 + */
  52 + void deleteFileConfig(Long id);
  53 +
  54 + /**
  55 + * 获得文件配置
  56 + *
  57 + * @param id 编号
  58 + * @return 文件配置
  59 + */
  60 + FileConfigDO getFileConfig(Long id);
  61 +
  62 + /**
  63 + * 获得文件配置列表
  64 + *
  65 + * @param ids 编号
  66 + * @return 文件配置列表
  67 + */
  68 + List<FileConfigDO> getFileConfigList(Collection<Long> ids);
  69 +
  70 + /**
  71 + * 获得文件配置分页
  72 + *
  73 + * @param pageReqVO 分页查询
  74 + * @return 文件配置分页
  75 + */
  76 + PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO);
  77 +
  78 + /**
  79 + * 测试文件配置是否正确,通过上传文件
  80 + *
  81 + * @param id 编号
  82 + * @return 文件 URL
  83 + */
  84 + String testFileConfig(Long id);
  85 +
  86 +}
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.service.file;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.io.resource.ResourceUtil;
  5 +import cn.hutool.core.util.IdUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  9 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  10 +import cn.iocoder.yudao.framework.file.core.client.FileClient;
  11 +import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
  12 +import cn.iocoder.yudao.framework.file.core.client.FileClientFactory;
  13 +import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum;
  14 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO;
  15 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
  16 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
  17 +import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
  18 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  19 +import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
  20 +import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.context.annotation.Lazy;
  23 +import org.springframework.scheduling.annotation.Scheduled;
  24 +import org.springframework.stereotype.Service;
  25 +import org.springframework.validation.annotation.Validated;
  26 +
  27 +import javax.annotation.PostConstruct;
  28 +import javax.annotation.Resource;
  29 +import javax.validation.Validator;
  30 +import java.util.Collection;
  31 +import java.util.Date;
  32 +import java.util.List;
  33 +import java.util.Map;
  34 +
  35 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  36 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
  37 +
  38 +/**
  39 + * 文件配置 Service 实现类
  40 + *
  41 + * @author 芋道源码
  42 + */
  43 +@Service
  44 +@Validated
  45 +@Slf4j
  46 +public class FileConfigServiceImpl implements FileConfigService {
  47 +
  48 + /**
  49 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  50 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  51 + */
  52 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  53 +
  54 + /**
  55 + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
  56 + */
  57 + private volatile Date maxUpdateTime;
  58 +
  59 + @Resource
  60 + private FileClientFactory fileClientFactory;
  61 + /**
  62 + * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
  63 + */
  64 + private FileClient masterFileClient;
  65 +
  66 + @Resource
  67 + private FileConfigMapper fileConfigMapper;
  68 +
  69 + @Resource
  70 + private FileConfigProducer fileConfigProducer;
  71 +
  72 + @Resource
  73 + private Validator validator;
  74 +
  75 + @Resource
  76 + @Lazy // 注入自己,所以延迟加载
  77 + private FileConfigService self;
  78 +
  79 + @Override
  80 + @PostConstruct
  81 + public void initFileClients() {
  82 + // 获取文件配置,如果有更新
  83 + List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
  84 + if (CollUtil.isEmpty(configs)) {
  85 + return;
  86 + }
  87 +
  88 + // 创建或更新支付 Client
  89 + configs.forEach(config -> {
  90 + fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
  91 + // 如果是 master,进行设置
  92 + if (Boolean.TRUE.equals(config.getMaster())) {
  93 + masterFileClient = fileClientFactory.getFileClient(config.getId());
  94 + }
  95 + });
  96 +
  97 + // 写入缓存
  98 + maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
  99 + log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
  100 + }
  101 +
  102 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  103 + public void schedulePeriodicRefresh() {
  104 + self.initFileClients();
  105 + }
  106 +
  107 + /**
  108 + * 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
  109 + * 如果未发生变化,则返回空
  110 + *
  111 + * @param maxUpdateTime 当前文件配置的最大更新时间
  112 + * @return 文件配置列表
  113 + */
  114 + private List<FileConfigDO> loadFileConfigIfUpdate(Date maxUpdateTime) {
  115 + // 第一步,判断是否要更新。
  116 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  117 + log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
  118 + } else { // 判断数据库中是否有更新的文件配置
  119 + if (fileConfigMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
  120 + return null;
  121 + }
  122 + log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
  123 + }
  124 + // 第二步,如果有更新,则从数据库加载所有文件配置
  125 + return fileConfigMapper.selectList();
  126 + }
  127 +
  128 + @Override
  129 + public Long createFileConfig(FileConfigCreateReqVO createReqVO) {
  130 + // 插入
  131 + FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)
  132 + .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
  133 + .setMaster(false); // 默认非 master
  134 + fileConfigMapper.insert(fileConfig);
  135 + // 发送刷新配置的消息
  136 + fileConfigProducer.sendFileConfigRefreshMessage();
  137 + // 返回
  138 + return fileConfig.getId();
  139 + }
  140 +
  141 + @Override
  142 + public void updateFileConfig(FileConfigUpdateReqVO updateReqVO) {
  143 + // 校验存在
  144 + FileConfigDO config = this.validateFileConfigExists(updateReqVO.getId());
  145 + // 更新
  146 + FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
  147 + .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
  148 + fileConfigMapper.updateById(updateObj);
  149 + // 发送刷新配置的消息
  150 + fileConfigProducer.sendFileConfigRefreshMessage();
  151 + }
  152 +
  153 + @Override
  154 + public void updateFileConfigMaster(Long id) {
  155 + // 校验存在
  156 + this.validateFileConfigExists(id);
  157 + // 更新
  158 + fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
  159 + // 发送刷新配置的消息
  160 + fileConfigProducer.sendFileConfigRefreshMessage();
  161 + }
  162 +
  163 + private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
  164 + // 获取配置类
  165 + Class<? extends FileClientConfig> configClass = FileStorageEnum.getByStorage(storage)
  166 + .getConfigClass();
  167 + FileClientConfig clientConfig = JsonUtils.parseObject2(JsonUtils.toJsonString(config), configClass);
  168 + // 参数校验
  169 + ValidationUtils.validate(validator, clientConfig);
  170 + // 设置参数
  171 + return clientConfig;
  172 + }
  173 +
  174 + @Override
  175 + public void deleteFileConfig(Long id) {
  176 + // 校验存在
  177 + this.validateFileConfigExists(id);
  178 + // 删除
  179 + fileConfigMapper.deleteById(id);
  180 + // 发送刷新配置的消息
  181 + fileConfigProducer.sendFileConfigRefreshMessage();
  182 + }
  183 +
  184 + private FileConfigDO validateFileConfigExists(Long id) {
  185 + FileConfigDO config = fileConfigMapper.selectById(id);
  186 + if (config == null) {
  187 + throw exception(FILE_CONFIG_NOT_EXISTS);
  188 + }
  189 + return config;
  190 + }
  191 +
  192 + @Override
  193 + public FileConfigDO getFileConfig(Long id) {
  194 + return fileConfigMapper.selectById(id);
  195 + }
  196 +
  197 + @Override
  198 + public List<FileConfigDO> getFileConfigList(Collection<Long> ids) {
  199 + return fileConfigMapper.selectBatchIds(ids);
  200 + }
  201 +
  202 + @Override
  203 + public PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO) {
  204 + return fileConfigMapper.selectPage(pageReqVO);
  205 + }
  206 +
  207 + @Override
  208 + public String testFileConfig(Long id) {
  209 + // 校验存在
  210 + this.validateFileConfigExists(id);
  211 + // 上传文件
  212 + byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
  213 + return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg");
  214 + }
  215 +
  216 +}
yudao-module-infra/yudao-module-infra-impl/src/test/resources/file/erweima.jpg renamed to yudao-module-infra/yudao-module-infra-impl/src/main/resources/file/erweima.jpg

18 KB

yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java 0 → 100755
  1 +package cn.iocoder.yudao.module.infra.service.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
  7 +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
  8 +import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
  9 +import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest;
  10 +import org.junit.jupiter.api.Disabled;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.springframework.context.annotation.Import;
  13 +
  14 +import javax.annotation.Resource;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  17 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  18 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  19 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  20 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  21 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
  22 +import static org.junit.jupiter.api.Assertions.*;
  23 +
  24 +/**
  25 +* {@link FileConfigServiceImpl} 的单元测试类
  26 +*
  27 +* @author 芋道源码
  28 +*/
  29 +@Import(FileConfigServiceImpl.class)
  30 +public class FileConfigServiceImplTest extends BaseDbUnitTest {
  31 +
  32 + @Resource
  33 + private FileConfigServiceImpl fileConfigService;
  34 +
  35 + @Resource
  36 + private FileConfigMapper fileConfigMapper;
  37 +
  38 + @Test
  39 + public void testCreateFileConfig_success() {
  40 + // 准备参数
  41 + FileConfigCreateReqVO reqVO = randomPojo(FileConfigCreateReqVO.class);
  42 +
  43 + // 调用
  44 + Long fileConfigId = fileConfigService.createFileConfig(reqVO);
  45 + // 断言
  46 + assertNotNull(fileConfigId);
  47 + // 校验记录的属性是否正确
  48 + FileConfigDO fileConfig = fileConfigMapper.selectById(fileConfigId);
  49 + assertPojoEquals(reqVO, fileConfig);
  50 + }
  51 +
  52 + @Test
  53 + public void testUpdateFileConfig_success() {
  54 + // mock 数据
  55 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class);
  56 + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
  57 + // 准备参数
  58 + FileConfigUpdateReqVO reqVO = randomPojo(FileConfigUpdateReqVO.class, o -> {
  59 + o.setId(dbFileConfig.getId()); // 设置更新的 ID
  60 + });
  61 +
  62 + // 调用
  63 + fileConfigService.updateFileConfig(reqVO);
  64 + // 校验是否更新正确
  65 + FileConfigDO fileConfig = fileConfigMapper.selectById(reqVO.getId()); // 获取最新的
  66 + assertPojoEquals(reqVO, fileConfig);
  67 + }
  68 +
  69 + @Test
  70 + public void testUpdateFileConfig_notExists() {
  71 + // 准备参数
  72 + FileConfigUpdateReqVO reqVO = randomPojo(FileConfigUpdateReqVO.class);
  73 +
  74 + // 调用, 并断言异常
  75 + assertServiceException(() -> fileConfigService.updateFileConfig(reqVO), FILE_CONFIG_NOT_EXISTS);
  76 + }
  77 +
  78 + @Test
  79 + public void testDeleteFileConfig_success() {
  80 + // mock 数据
  81 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class);
  82 + fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
  83 + // 准备参数
  84 + Long id = dbFileConfig.getId();
  85 +
  86 + // 调用
  87 + fileConfigService.deleteFileConfig(id);
  88 + // 校验数据不存在了
  89 + assertNull(fileConfigMapper.selectById(id));
  90 + }
  91 +
  92 + @Test
  93 + public void testDeleteFileConfig_notExists() {
  94 + // 准备参数
  95 + Long id = randomLongId();
  96 +
  97 + // 调用, 并断言异常
  98 + assertServiceException(() -> fileConfigService.deleteFileConfig(id), FILE_CONFIG_NOT_EXISTS);
  99 + }
  100 +
  101 + @Test
  102 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  103 + public void testGetFileConfigPage() {
  104 + // mock 数据
  105 + FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class, o -> { // 等会查询到
  106 + o.setName(null);
  107 + o.setStorage(null);
  108 + o.setCreateTime(null);
  109 + });
  110 + fileConfigMapper.insert(dbFileConfig);
  111 + // 测试 name 不匹配
  112 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName(null)));
  113 + // 测试 storage 不匹配
  114 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(null)));
  115 + // 测试 createTime 不匹配
  116 + fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(null)));
  117 + // 准备参数
  118 + FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
  119 + reqVO.setName(null);
  120 + reqVO.setStorage(null);
  121 + reqVO.setBeginCreateTime(null);
  122 + reqVO.setEndCreateTime(null);
  123 +
  124 + // 调用
  125 + PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
  126 + // 断言
  127 + assertEquals(1, pageResult.getTotal());
  128 + assertEquals(1, pageResult.getList().size());
  129 + assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
  130 + }
  131 +
  132 +}
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 { @@ -127,6 +127,7 @@ public class PayChannelServiceImpl implements PayChannelService {
127 PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO); 127 PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO);
128 settingConfigAndCheckParam(channel, reqVO.getConfig()); 128 settingConfigAndCheckParam(channel, reqVO.getConfig());
129 channelMapper.insert(channel); 129 channelMapper.insert(channel);
  130 + // TODO 芋艿:缺少刷新本地缓存的机制
130 return channel.getId(); 131 return channel.getId();
131 } 132 }
132 133
@@ -138,6 +139,7 @@ public class PayChannelServiceImpl implements PayChannelService { @@ -138,6 +139,7 @@ public class PayChannelServiceImpl implements PayChannelService {
138 PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO); 139 PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO);
139 settingConfigAndCheckParam(channel, updateReqVO.getConfig()); 140 settingConfigAndCheckParam(channel, updateReqVO.getConfig());
140 channelMapper.updateById(channel); 141 channelMapper.updateById(channel);
  142 + // TODO 芋艿:缺少刷新本地缓存的机制
141 } 143 }
142 144
143 @Override 145 @Override
@@ -146,6 +148,7 @@ public class PayChannelServiceImpl implements PayChannelService { @@ -146,6 +148,7 @@ public class PayChannelServiceImpl implements PayChannelService {
146 this.validateChannelExists(id); 148 this.validateChannelExists(id);
147 // 删除 149 // 删除
148 channelMapper.deleteById(id); 150 channelMapper.deleteById(id);
  151 + // TODO 芋艿:缺少刷新本地缓存的机制
149 } 152 }
150 153
151 private void validateChannelExists(Long id) { 154 private void validateChannelExists(Long id) {
@@ -224,6 +227,7 @@ public class PayChannelServiceImpl implements PayChannelService { @@ -224,6 +227,7 @@ public class PayChannelServiceImpl implements PayChannelService {
224 if (ObjectUtil.isNull(payClass)) { 227 if (ObjectUtil.isNull(payClass)) {
225 throw exception(CHANNEL_NOT_EXISTS); 228 throw exception(CHANNEL_NOT_EXISTS);
226 } 229 }
  230 + // TODO @芋艿:不要使用 hutool 的 json 工具,用项目的
227 PayClientConfig config = JSONUtil.toBean(configStr, payClass); 231 PayClientConfig config = JSONUtil.toBean(configStr, payClass);
228 232
229 // 验证参数 233 // 验证参数
yudao-server/src/main/resources/application.yaml
@@ -110,7 +110,9 @@ yudao: @@ -110,7 +110,9 @@ yudao:
110 - tables 110 - tables
111 - columns 111 - columns
112 - infra_config 112 - infra_config
  113 + - infra_file_config
113 - infra_file 114 - infra_file
  115 + - infra_file_content
114 - infra_job 116 - infra_job
115 - infra_job_log 117 - infra_job_log
116 - infra_job_log 118 - infra_job_log