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