diff --git a/.run/ruoyi-auth.run.xml b/.run/ruoyi-auth.run.xml index 5c82d89b0e65e80237d2c16c3138dbc57aac2e6b..25b2b9886aed91b695c75487b728475e22b255dd 100644 --- a/.run/ruoyi-auth.run.xml +++ b/.run/ruoyi-auth.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-gateway.run.xml b/.run/ruoyi-gateway.run.xml index d981e28e257a28695e9f5378bcab23e80f6c3ce3..a5a6bfaff552fa659e3dec8acee90041fe1863ae 100644 --- a/.run/ruoyi-gateway.run.xml +++ b/.run/ruoyi-gateway.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-gen.run.xml b/.run/ruoyi-gen.run.xml index 10a03ce71dccf13373b911bb794883936885a0b2..afb8186c392435ba3ec5020fa8564bdad5fe8f40 100644 --- a/.run/ruoyi-gen.run.xml +++ b/.run/ruoyi-gen.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-job.run.xml b/.run/ruoyi-job.run.xml index 3f64c8c55f7db83a4eed50ddd1aad4bd5cbbf9a4..062c592813580fca37e6823be0fc1e35a1b944fb 100644 --- a/.run/ruoyi-job.run.xml +++ b/.run/ruoyi-job.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-monitor.run.xml b/.run/ruoyi-monitor.run.xml index aa2b96337383f4be9a078ad2b222b56a37c16bac..a55209b4a51e5faf2a4992d7754710cfb9653d6a 100644 --- a/.run/ruoyi-monitor.run.xml +++ b/.run/ruoyi-monitor.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-nacos.run.xml b/.run/ruoyi-nacos.run.xml index e311a8a03e8bdd2e90f411750f3ea2c88033f15c..fec0d82dbaebfb6ee5266576fcac9074e9d56c86 100644 --- a/.run/ruoyi-nacos.run.xml +++ b/.run/ruoyi-nacos.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-resource.run.xml b/.run/ruoyi-resource.run.xml index 9ddcb1a6f214ddeeb81f4e6b6eeef9bea1641fdb..223bdd0abd1395977b427bdee83bd5ee58e6cc9e 100644 --- a/.run/ruoyi-resource.run.xml +++ b/.run/ruoyi-resource.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-seata-server.run.xml b/.run/ruoyi-seata-server.run.xml index adf938ea5a9a69fd9da045aede129dd02502b83c..59849d3b3e228b8fabd9d492cb51c0ce79b36502 100644 --- a/.run/ruoyi-seata-server.run.xml +++ b/.run/ruoyi-seata-server.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-sentinel-dashboard.run.xml b/.run/ruoyi-sentinel-dashboard.run.xml deleted file mode 100644 index 4c08241b80799defa5d50c31803281bdbfc011b4..0000000000000000000000000000000000000000 --- a/.run/ruoyi-sentinel-dashboard.run.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/.run/ruoyi-snailjob-server.run.xml b/.run/ruoyi-snailjob-server.run.xml index 42a250fab3f0b75e1a7462a2b6affc20f39a8d05..678ecc1222fb3fbc01b9c5080131c998a12fda84 100644 --- a/.run/ruoyi-snailjob-server.run.xml +++ b/.run/ruoyi-snailjob-server.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-system.run.xml b/.run/ruoyi-system.run.xml index 709d4540e119c4cdad272da9f13eb4440cc5e09c..49b74deafabe6c9e05f428f6a758a298fbc82751 100644 --- a/.run/ruoyi-system.run.xml +++ b/.run/ruoyi-system.run.xml @@ -2,7 +2,7 @@ - diff --git a/.run/ruoyi-workflow.run.xml b/.run/ruoyi-workflow.run.xml index 4c0e419d86bead1d002c414135d102d3d2ad521a..080c11f1ca28eb8bf668350e08741be2c3f95f69 100644 --- a/.run/ruoyi-workflow.run.xml +++ b/.run/ruoyi-workflow.run.xml @@ -2,7 +2,7 @@ - diff --git a/README.md b/README.md index 5b28e71ec2700d9bcbbddeb108ae194b8ce963d2..34adf0246059d566925b94273f532a744173ae4c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus/blob/2.X/LICENSE) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Cloud-Plus)
-[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.4.1-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus) +[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.5.0-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]() [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]() @@ -27,7 +27,7 @@ > 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)
-> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网) +> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top) ## 赞助商 @@ -37,7 +37,10 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc
**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/**
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11
-[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group) +aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong
+Ruoyi-Plus-Uniapp - https://ruoyi.plus
+ +[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group) # 本框架与RuoYi的功能差异 diff --git a/pom.xml b/pom.xml index 3eeb91eccf52597d270c7e6729edb0ef40159b43..cdde03f4eab5ef6770be221187f54554488ed327 100644 --- a/pom.xml +++ b/pom.xml @@ -13,33 +13,33 @@ Dromara RuoYi-Cloud-Plus微服务系统 - 2.4.1 + 2.5.0 UTF-8 UTF-8 17 - 3.4.7 - 2024.0.0 - 3.4.7 + 3.5.6 + 2025.0.0 + 3.5.3 3.5.16 - 3.5.12 + 3.5.14 3.9.1 4.3.1 2.3 - 2.2.30 - 2.8.8 + 2.2.36 + 2.8.13 0.15.0 - 1.2.0 - 5.8.38 - 3.50.0 + 1.3.0 + 5.8.40 + 3.51.0 2.2.7 - 1.5.0 + 1.8.0 1.44.0 - 1.18.36 + 1.18.40 7.4 3.0.0 9.3.0 1.80 - 1.4.8 + 1.5.0 0.2.0 1.16.7 @@ -53,9 +53,9 @@ 8.7.2-20250603 - 1.7.4 + 1.8.1 - 2.3.0 + 2.3.4 3.14.0 @@ -137,13 +137,6 @@ import - - - me.zhyd.oauth - JustAuth - ${justauth.version} - - org.dromara @@ -324,6 +317,13 @@ ${sms4j.version} + + + me.zhyd.oauth + JustAuth + ${justauth.version} + + org.lionsoul diff --git a/ruoyi-api/ruoyi-api-bom/pom.xml b/ruoyi-api/ruoyi-api-bom/pom.xml index a1bb7214a169c47e6355540ee73fe9a371bbf60a..96177272b8a7854caaa3df85aead46a69073697b 100644 --- a/ruoyi-api/ruoyi-api-bom/pom.xml +++ b/ruoyi-api/ruoyi-api-bom/pom.xml @@ -15,7 +15,7 @@ - 2.4.1 + 2.5.0 diff --git a/ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java b/ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java index 7140fe638ef1376acd3ecf9fb018210b231d8d74..406435acfd00d7dbe2ad08bd1d859dca6c993d93 100644 --- a/ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java +++ b/ruoyi-api/ruoyi-api-resource/src/main/java/org/dromara/resource/api/domain/RemoteFile.java @@ -41,4 +41,9 @@ public class RemoteFile implements Serializable { */ private String fileSuffix; + /** + * 扩展字段 + */ + private String ext1; + } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java index 2ece92817c9620b37155989bac59db7ef952ba20..a8ffb5d45b471a454058164f8293873aa37160b1 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java @@ -3,6 +3,7 @@ package org.dromara.system.api; import org.dromara.system.api.domain.vo.RemoteDeptVo; import java.util.List; +import java.util.Map; /** * 部门服务 @@ -34,4 +35,12 @@ public interface RemoteDeptService { */ List selectDeptsByList(); + /** + * 根据部门 ID 列表查询部门名称映射关系 + * + * @param deptIds 部门 ID 列表 + * @return Map,其中 key 为部门 ID,value 为对应的部门名称 + */ + Map selectDeptNamesByIds(List deptIds); + } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemotePostService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemotePostService.java new file mode 100644 index 0000000000000000000000000000000000000000..2e5ad1a82f1d13cd42f36ad0e76e2297948aad3d --- /dev/null +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemotePostService.java @@ -0,0 +1,21 @@ +package org.dromara.system.api; + +import java.util.List; +import java.util.Map; + +/** + * 岗位服务 + * + * @author Lion Li + */ +public interface RemotePostService { + + /** + * 根据岗位 ID 列表查询岗位名称映射关系 + * + * @param postIds 岗位 ID 列表 + * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 + */ + Map selectPostNamesByIds(List postIds); + +} diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteRoleService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteRoleService.java new file mode 100644 index 0000000000000000000000000000000000000000..9c976ee6de5be56b3458a0187218f2aff166b694 --- /dev/null +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteRoleService.java @@ -0,0 +1,21 @@ +package org.dromara.system.api; + +import java.util.List; +import java.util.Map; + +/** + * 角色服务 + * + * @author Lion Li + */ +public interface RemoteRoleService { + + /** + * 根据角色 ID 列表查询角色名称映射关系 + * + * @param roleIds 角色 ID 列表 + * @return Map,其中 key 为角色 ID,value 为对应的角色名称 + */ + Map selectRoleNamesByIds(List roleIds); + +} diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java index 3e8b54850d29da7c74a3b9506491934154869f04..0bda9538eb4f2756f51d9c6ae5f7ab8456512c17 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java @@ -165,28 +165,4 @@ public interface RemoteUserService { */ Map selectUserNamesByIds(List userIds); - /** - * 根据角色 ID 列表查询角色名称映射关系 - * - * @param roleIds 角色 ID 列表 - * @return Map,其中 key 为角色 ID,value 为对应的角色名称 - */ - Map selectRoleNamesByIds(List roleIds); - - /** - * 根据部门 ID 列表查询部门名称映射关系 - * - * @param deptIds 部门 ID 列表 - * @return Map,其中 key 为部门 ID,value 为对应的部门名称 - */ - Map selectDeptNamesByIds(List deptIds); - - /** - * 根据岗位 ID 列表查询岗位名称映射关系 - * - * @param postIds 岗位 ID 列表 - * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 - */ - Map selectPostNamesByIds(List postIds); - } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java index 5f59d8c0dd50166af385eed21d06884cecdd7fa6..aba8bde929c48146c2d560dd22e529d9751e2da3 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaskAssigneeVo.java @@ -52,17 +52,17 @@ public class RemoteTaskAssigneeVo implements Serializable { */ public static List convertToHandlerList( List sourceList, - Function storageId, + Function storageId, Function handlerCode, Function handlerName, - Function groupName, + Function groupName, Function createTimeMapper) { return sourceList.stream() .map(item -> new TaskHandler( - String.valueOf(storageId.apply(item)), + storageId.apply(item), handlerCode.apply(item), handlerName.apply(item), - groupName != null ? String.valueOf(groupName.apply(item)) : null, + groupName.apply(item), createTimeMapper.apply(item) )).collect(Collectors.toList()); } diff --git a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java index aa21a3fb66e48bd2ccd8d5db542af690e3cd9bd1..eedd2fcb2d517b8c74448c2c528fba4325a589b0 100644 --- a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java +++ b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteCompleteTask.java @@ -50,6 +50,11 @@ public class RemoteCompleteTask implements Serializable { */ private String notice; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量 */ diff --git a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java index f7f12a531f0f08eef2cac89a5b7f3e9b9d876e6d..c7e9c109d2fa28f73549a2372ff660354960caff 100644 --- a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java +++ b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/domain/RemoteStartProcess.java @@ -30,6 +30,11 @@ public class RemoteStartProcess implements Serializable { */ private String flowCode; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} */ diff --git a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java index 937a93115775badb67ecb354bc338e28ae0c3fba..b98e1fb2cd8749fb9b4a061fbcb855372047a2b2 100644 --- a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java +++ b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessEvent.java @@ -30,6 +30,11 @@ public class ProcessEvent extends RemoteApplicationEvent { */ private String flowCode; + /** + * 实例id + */ + private Long instanceId; + /** * 业务id */ diff --git a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java index f53c8defbc7414712cd93b4b2be292fbd5fa345c..fed057603191d709301954d129ef63981ccfa6fa 100644 --- a/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java +++ b/ruoyi-api/ruoyi-api-workflow/src/main/java/org/dromara/workflow/api/event/ProcessTaskEvent.java @@ -6,6 +6,7 @@ import org.dromara.common.core.utils.SpringUtils; import org.springframework.cloud.bus.event.RemoteApplicationEvent; import java.io.Serial; +import java.util.Map; /** * 流程任务监听 @@ -49,6 +50,11 @@ public class ProcessTaskEvent extends RemoteApplicationEvent { */ private Long taskId; + /** + * 实例id + */ + private Long instanceId; + /** * 业务id */ @@ -59,6 +65,11 @@ public class ProcessTaskEvent extends RemoteApplicationEvent { */ private String status; + /** + * 办理参数 + */ + private Map params; + public ProcessTaskEvent() { super(new Object(), SpringUtils.getApplicationName(), DEFAULT_DESTINATION_FACTORY.getDestination(null)); } diff --git a/ruoyi-auth/Dockerfile b/ruoyi-auth/Dockerfile index c24c9e8a8bbb7de216cb21185815354d4d2f7dcc..b82eb3ce74d339f6f4b204e909bb44e791fc7c35 100644 --- a/ruoyi-auth/Dockerfile +++ b/ruoyi-auth/Dockerfile @@ -1,6 +1,6 @@ # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ -FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds -#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds +FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds +#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds #FROM findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-auth/pom.xml b/ruoyi-auth/pom.xml index 86bf0b99d945b72fd787b2df468576d1b4b848e6..89085a0dfa2f7c3f770316ebd16388a7179054fd 100644 --- a/ruoyi-auth/pom.xml +++ b/ruoyi-auth/pom.xml @@ -26,11 +26,6 @@ hutool-captcha - - org.dromara - ruoyi-common-sentinel - - org.dromara diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java b/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java deleted file mode 100644 index feb4cdf439fdcb09e98e5152ae008f3fb85fc06d..0000000000000000000000000000000000000000 --- a/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.dromara.auth.captcha; - -import cn.hutool.captcha.generator.CodeGenerator; -import cn.hutool.core.math.Calculator; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.RandomUtil; -import org.dromara.common.core.utils.StringUtils; - -import java.io.Serial; - -/** - * 无符号计算生成器 - * - * @author Lion Li - */ -public class UnsignedMathGenerator implements CodeGenerator { - - @Serial - private static final long serialVersionUID = -5514819971774091076L; - - private static final String OPERATORS = "+-*"; - - /** - * 参与计算数字最大长度 - */ - private final int numberLength; - - /** - * 构造 - */ - public UnsignedMathGenerator() { - this(2); - } - - /** - * 构造 - * - * @param numberLength 参与计算最大数字位数 - */ - public UnsignedMathGenerator(int numberLength) { - this.numberLength = numberLength; - } - - @Override - public String generate() { - final int limit = getLimit(); - int a = RandomUtil.randomInt(limit); - int b = RandomUtil.randomInt(limit); - String max = Integer.toString(Math.max(a,b)); - String min = Integer.toString(Math.min(a,b)); - max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE); - min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE); - - return max + RandomUtil.randomChar(OPERATORS) + min + '='; - } - - @Override - public boolean verify(String code, String userInputCode) { - int result; - try { - result = Integer.parseInt(userInputCode); - } catch (NumberFormatException e) { - // 用户输入非数字 - return false; - } - - final int calculateResult = (int) Calculator.conversion(code); - return result == calculateResult; - } - - /** - * 获取验证码长度 - * - * @return 验证码长度 - */ - public int getLength() { - return this.numberLength * 2 + 2; - } - - /** - * 根据长度获取参与计算数字最大值 - * - * @return 最大值 - */ - private int getLimit() { - return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength)); - } -} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java index 3a2476a183c0a7e34bef94e233972dbd2aa7957d..6f341a45ca684bb0ee9b74690cec37c354bc93d0 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java @@ -64,15 +64,18 @@ public class CaptchaController { String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; // 生成验证码 CaptchaType captchaType = captchaProperties.getType(); - boolean isMath = CaptchaType.MATH == captchaType; - Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength(); - CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length); + CodeGenerator codeGenerator; + if (CaptchaType.MATH == captchaType) { + codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false); + } else { + codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength()); + } AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); captcha.setGenerator(codeGenerator); captcha.createCode(); // 如果是数学验证码,使用SpEL表达式处理验证码结果 String code = captcha.getCode(); - if (isMath) { + if (CaptchaType.MATH == captchaType) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(StringUtils.remove(code, "=")); code = exp.getValue(String.class); diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java b/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java index 416f7a4922058aca33ec771cd33ff0cd5a9db878..cd9f8d61a4120b78c2a3f036a71c7ecba24278de 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java @@ -25,6 +25,8 @@ import org.dromara.common.core.domain.model.LoginBody; import org.dromara.common.core.utils.*; import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.ratelimiter.annotation.RateLimiter; +import org.dromara.common.ratelimiter.enums.LimitType; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.social.config.properties.SocialLoginConfigProperties; import org.dromara.common.social.config.properties.SocialProperties; @@ -190,6 +192,7 @@ public class TokenController { * * @return 租户列表 */ + @RateLimiter(time = 60, count = 20, limitType = LimitType.IP) @GetMapping("/tenant/list") public R tenantList(HttpServletRequest request) throws Exception { // 返回对象 diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java index b66334599848e0d7f7060905c9f182ab4cc7ab63..afe5d364165d0682cb128e9ca97dfa09749ab778 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java @@ -1,8 +1,8 @@ package org.dromara.auth.enums; import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.MathGenerator; import cn.hutool.captcha.generator.RandomGenerator; -import org.dromara.auth.captcha.UnsignedMathGenerator; import lombok.AllArgsConstructor; import lombok.Getter; @@ -18,7 +18,7 @@ public enum CaptchaType { /** * 数字 */ - MATH(UnsignedMathGenerator.class), + MATH(MathGenerator.class), /** * 字符 diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java index 31086ab4d720d08391d16abf7dceb07c6f0c033a..44777b0232ebd8a23d56b8f1c03f96d98a0fa1bd 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java @@ -27,6 +27,7 @@ public class PasswordLoginBody extends LoginBody { */ @NotBlank(message = "{user.password.not.blank}") @Length(min = 5, max = 30, message = "{user.password.length.valid}") +// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}") private String password; } diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java index 8b0a0474960ed6ae4f4ab08937056c2944ae7bed..65ee435436839a4a280839e87ec6905a4199eb4c 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java @@ -27,6 +27,7 @@ public class RegisterBody extends LoginBody { */ @NotBlank(message = "{user.password.not.blank}") @Length(min = 5, max = 30, message = "{user.password.length.valid}") +// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}") private String password; /** diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java index 59af7ccca1f85786c3b02144038dcb7ade77fb8d..da36aaee1379b059273229985ae5f7182a9724db 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java @@ -174,7 +174,7 @@ public class SysLoginService { recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } - if (!code.equalsIgnoreCase(captcha)) { + if (!StringUtils.equalsIgnoreCase(code, captcha)) { recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java index 53476f939a8a7f96466c6779b68f018db426b7ea..627678a4e19a8d84c5a7dfa7c49a58cdfdf5d74e 100644 --- a/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java @@ -98,7 +98,7 @@ public class PasswordAuthStrategy implements IAuthStrategy { loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } - if (!code.equalsIgnoreCase(captcha)) { + if (!StringUtils.equalsIgnoreCase(code, captcha)) { loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index a17a0914da247aa7fd37278164c5a726016a972a..29222e0be72076099cda7c6eec4acc016160eaf5 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -32,7 +32,6 @@ ruoyi-common-sms ruoyi-common-logstash ruoyi-common-elasticsearch - ruoyi-common-sentinel ruoyi-common-skylog ruoyi-common-prometheus ruoyi-common-translation diff --git a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml index 51a935abb75862d07ad9611d4735fc199f053893..c7eb6247b514410d2855981d3cb4fd4a8fb12ef1 100644 --- a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml @@ -14,10 +14,9 @@ - 2.4.1 + 2.5.0 2023.0.3.3 - 1.8.8 - 2.4.0 + 2.5.0 2.5.1 3.3.5 3.3.1 @@ -35,111 +34,20 @@ com.alibaba.nacos nacos-client ${nacos.client.version} - - - com.alibaba.csp - sentinel-core - ${sentinel.version} - - - com.alibaba.csp - sentinel-parameter-flow-control - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-extension - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-apollo - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-zookeeper - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-nacos - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-redis - ${sentinel.version} - - - com.alibaba.csp - sentinel-datasource-consul - ${sentinel.version} - - - com.alibaba.csp - sentinel-web-servlet - ${sentinel.version} - - - com.alibaba.csp - sentinel-spring-cloud-gateway-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-transport-simple-http - ${sentinel.version} - - - com.alibaba.csp - sentinel-annotation-aspectj - ${sentinel.version} - - - com.alibaba.csp - sentinel-reactor-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-cluster-server-default - ${sentinel.version} - - - com.alibaba.csp - sentinel-cluster-client-default - ${sentinel.version} - - - com.alibaba.csp - sentinel-spring-webflux-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-api-gateway-adapter-common - ${sentinel.version} - - - com.alibaba.csp - sentinel-spring-webmvc-v6x-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-dubbo-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-apache-dubbo-adapter - ${sentinel.version} - - - com.alibaba.csp - sentinel-apache-dubbo3-adapter - ${sentinel.version} + + + com.alibaba.nacos + nacos-log4j2-adapter + + + com.alibaba.nacos + nacos-logback-adapter-12 + + + com.alibaba.nacos + logback-adapter + + org.apache.seata diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 449a1a7f41455eb09b6c773bda3132df8ae4c6b2..2b8a06b960d3be6b896a1ec7815934b980a002fc 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -14,7 +14,7 @@ - 2.4.1 + 2.5.0 @@ -166,13 +166,6 @@ ${revision} - - - org.dromara - ruoyi-common-sentinel - ${revision} - - org.dromara diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java deleted file mode 100644 index cd01e33d51ee0e5260ad40339677dca1ecfd6d4d..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.dromara.common.core.config; - -import cn.hutool.core.util.ArrayUtil; -import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.utils.SpringUtils; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.core.task.VirtualThreadTaskExecutor; -import org.springframework.scheduling.annotation.AsyncConfigurer; - -import java.util.Arrays; -import java.util.concurrent.Executor; - -/** - * 异步配置 - *

- * 如果未使用虚拟线程则生效 - * - * @author Lion Li - */ -@AutoConfiguration -public class AsyncConfig implements AsyncConfigurer { - - /** - * 自定义 @Async 注解使用系统线程池 - */ - @Override - public Executor getAsyncExecutor() { - if(SpringUtils.isVirtual()) { - return new VirtualThreadTaskExecutor("async-"); - } - return SpringUtils.getBean("scheduledExecutorService"); - } - - /** - * 异步执行异常处理 - */ - @Override - public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { - return (throwable, method, objects) -> { - throwable.printStackTrace(); - StringBuilder sb = new StringBuilder(); - sb.append("Exception message - ").append(throwable.getMessage()) - .append(", Method name - ").append(method.getName()); - if (ArrayUtil.isNotEmpty(objects)) { - sb.append(", Parameter value - ").append(Arrays.toString(objects)); - } - throw new ServiceException(sb.toString()); - }; - } - -} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java index d031921bd7a2ae265697c8867a6ca26a3b680ece..b4550e4cb1592bb8434ae98f0b769a6e7c152373 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java @@ -72,5 +72,10 @@ public interface Constants { */ Long TOP_PARENT_ID = 0L; + /** + * 加密头 + */ + String ENCRYPT_HEADER = "ENC_"; + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java index 4fb097a88e16210e161227e53c720ff7a2551325..90f5752b1bcabf92dc6ce6f3229009817017250f 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java @@ -1,5 +1,6 @@ package org.dromara.common.core.exception; +import cn.hutool.core.text.StrFormatter; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -8,7 +9,7 @@ import lombok.NoArgsConstructor; import java.io.Serial; /** - * 业务异常 + * 业务异常(支持占位符 {} ) * * @author ruoyi */ @@ -45,8 +46,8 @@ public final class ServiceException extends RuntimeException { this.code = code; } - public String getDetailMessage() { - return detailMessage; + public ServiceException(String message, Object... args) { + this.message = StrFormatter.format(message, args); } @Override @@ -54,10 +55,6 @@ public final class ServiceException extends RuntimeException { return message; } - public Integer getCode() { - return code; - } - public ServiceException setMessage(String message) { this.message = message; return this; diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java index b52d95e16762eec906dac5bcb6fa159fdfe73a0d..b4d1462422a0076c604656ee0f4b14f6e35c0f30 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java @@ -293,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { // 校验时间跨度不超过最大限制 if (diff > maxValue) { - throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase()); + throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase()); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java index bd1aab80817036916a63df0dda036831520011da..509026f72249f0cdbf60637ec35b430a67852a91 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java @@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil { public static Map getParamMap(ServletRequest request) { Map params = new HashMap<>(); for (Map.Entry entry : getParams(request).entrySet()) { - params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR)); + params.put(entry.getKey(), StringUtils.joinComma(entry.getValue())); } return params; } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java index f9e53a508304dcf5361bd0179e1680ef50bb87ef..c5487c0b60673f62b20d145b6b9494a23ef5b688 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java @@ -30,8 +30,10 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return CollUtil.newArrayList(); } - // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 - return collection.stream().filter(function).collect(Collectors.toList()); + return collection.stream() + .filter(function) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); } /** @@ -39,13 +41,26 @@ public class StreamUtils { * * @param collection 需要查询的集合 * @param function 过滤方法 - * @return 找到符合条件的第一个元素,没有则返回null + * @return 找到符合条件的第一个元素,没有则返回 Optional.empty() */ - public static E findFirst(Collection collection, Predicate function) { + public static Optional findFirst(Collection collection, Predicate function) { if (CollUtil.isEmpty(collection)) { - return null; + return Optional.empty(); } - return collection.stream().filter(function).findFirst().orElse(null); + return collection.stream() + .filter(function) + .findFirst(); + } + + /** + * 找到流中满足条件的第一个元素值 + * + * @param collection 需要查询的集合 + * @param function 过滤方法 + * @return 找到符合条件的第一个元素,没有则返回 null + */ + public static E findFirstValue(Collection collection, Predicate function) { + return findFirst(collection,function).orElse(null); } /** @@ -53,13 +68,26 @@ public class StreamUtils { * * @param collection 需要查询的集合 * @param function 过滤方法 - * @return 找到符合条件的任意一个元素,没有则返回null + * @return 找到符合条件的任意一个元素,没有则返回 Optional.empty() */ public static Optional findAny(Collection collection, Predicate function) { if (CollUtil.isEmpty(collection)) { return Optional.empty(); } - return collection.stream().filter(function).findAny(); + return collection.stream() + .filter(function) + .findAny(); + } + + /** + * 找到流中任意一个满足条件的元素值 + * + * @param collection 需要查询的集合 + * @param function 过滤方法 + * @return 找到符合条件的任意一个元素,没有则返回null + */ + public static E findAnyValue(Collection collection, Predicate function) { + return findAny(collection,function).orElse(null); } /** @@ -85,7 +113,10 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return StringUtils.EMPTY; } - return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter)); + return collection.stream() + .map(function) + .filter(Objects::nonNull) + .collect(Collectors.joining(delimiter)); } /** @@ -99,8 +130,11 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return CollUtil.newArrayList(); } - // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 - return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList()); + return collection.stream() + .filter(Objects::nonNull) + .sorted(comparing) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); } /** @@ -117,7 +151,9 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return MapUtil.newHashMap(); } - return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); + return collection.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); } /** @@ -136,7 +172,25 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return MapUtil.newHashMap(); } - return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l)); + return collection.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(key, value, (l, r) -> l)); + } + + /** + * 获取 map 中的数据作为新 Map 的 value ,key 不变 + * @param map 需要处理的map + * @param take 取值函数 + * @param map中的key类型 + * @param map中的value类型 + * @param 新map中的value类型 + * @return 新的map + */ + public static Map toMap(Map map, BiFunction take) { + if (CollUtil.isEmpty(map)) { + return MapUtil.newHashMap(); + } + return toMap(map.entrySet(), Map.Entry::getKey, entry -> take.apply(entry.getKey(), entry.getValue())); } /** @@ -153,8 +207,8 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return MapUtil.newHashMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); } @@ -174,8 +228,8 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return MapUtil.newHashMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList()))); } @@ -192,11 +246,11 @@ public class StreamUtils { * @return 分类后的map */ public static Map> group2Map(Collection collection, Function key1, Function key2) { - if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { + if (CollUtil.isEmpty(collection)) { return MapUtil.newHashMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l))); } @@ -214,8 +268,7 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return CollUtil.newArrayList(); } - return collection - .stream() + return collection.stream() .map(function) .filter(Objects::nonNull) // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 @@ -233,11 +286,10 @@ public class StreamUtils { * @return 转化后的Set */ public static Set toSet(Collection collection, Function function) { - if (CollUtil.isEmpty(collection) || function == null) { + if (CollUtil.isEmpty(collection)) { return CollUtil.newHashSet(); } - return collection - .stream() + return collection.stream() .map(function) .filter(Objects::nonNull) .collect(Collectors.toSet()); @@ -257,26 +309,20 @@ public class StreamUtils { * @return 合并后的map */ public static Map merge(Map map1, Map map2, BiFunction merge) { - if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) { + if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) { + // 如果两个 map 都为空,则直接返回空的 map return MapUtil.newHashMap(); - } else if (MapUtil.isEmpty(map1)) { - map1 = MapUtil.newHashMap(); - } else if (MapUtil.isEmpty(map2)) { - map2 = MapUtil.newHashMap(); - } - Set key = new HashSet<>(); - key.addAll(map1.keySet()); - key.addAll(map2.keySet()); - Map map = new HashMap<>(); - for (K t : key) { - X x = map1.get(t); - Y y = map2.get(t); - V z = merge.apply(x, y); - if (z != null) { - map.put(t, z); - } + } else if (CollUtil.isEmpty(map1)) { + // 如果 map1 为空,则直接处理返回 map2 + return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue())); + } else if (CollUtil.isEmpty(map2)) { + // 如果 map2 为空,则直接处理返回 map1 + return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null)); } - return map; + Set keySet = new HashSet<>(); + keySet.addAll(map1.keySet()); + keySet.addAll(map2.keySet()); + return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key))); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java index 7165734313dbcf905e6f999257507ad00820e97f..f2d6461e00ec71c2ae22c03a6ca3ed68be682af4 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java @@ -260,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { if (s != null) { final int len = s.length(); if (s.length() <= size) { - sb.append(String.valueOf(c).repeat(size - len)); + sb.append(Convert.toStr(c).repeat(size - len)); sb.append(s); } else { return s.substring(len - size, len); } } else { - sb.append(String.valueOf(c).repeat(Math.max(0, size))); + sb.append(Convert.toStr(c).repeat(Math.max(0, size))); } return sb.toString(); } @@ -362,4 +362,24 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { } } + /** + * 将可迭代对象中的元素使用逗号拼接成字符串 + * + * @param iterable 可迭代对象,如 List、Set 等 + * @return 拼接后的字符串 + */ + public static String joinComma(Iterable iterable) { + return StringUtils.join(iterable, SEPARATOR); + } + + /** + * 将数组中的元素使用逗号拼接成字符串 + * + * @param array 任意类型的数组 + * @return 拼接后的字符串 + */ + public static String joinComma(Object[] array) { + return StringUtils.join(array, SEPARATOR); + } + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index b82846e208130a4b10dd1441059993a071f85246..35882667e4fbcaf51de4369b86c39961f1a023c6 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,4 +2,3 @@ org.dromara.common.core.utils.SpringUtils org.dromara.common.core.config.ApplicationConfig org.dromara.common.core.config.ValidatorConfig org.dromara.common.core.config.ThreadPoolConfig -org.dromara.common.core.config.AsyncConfig diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties index cce11c85db4b198a7af3a27f0818407742406c46..f2777f77be245b3cbccec78b48b2d3cf84accb6b 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties @@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间 user.password.not.blank=用户密码不能为空 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.not.valid=* 5-50个字符 +user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符 user.email.not.valid=邮箱格式错误 user.email.not.blank=邮箱不能为空 user.phonenumber.not.blank=用户手机号不能为空 diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties index f948c4ab899e67ef8b9d446844e0f43ca7e1b828..306a48f6a7a08a9724629e9a0aeb899daa89abf7 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties @@ -17,6 +17,7 @@ user.username.length.valid=Account length must be between {min} and {max} charac user.password.not.blank=Password cannot be empty user.password.length.valid=Password length must be between {min} and {max} characters user.password.not.valid=* 5-50 characters +user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character user.email.not.valid=Mailbox format error user.email.not.blank=Mailbox cannot be blank user.phonenumber.not.blank=Phone number cannot be blank diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties index cce11c85db4b198a7af3a27f0818407742406c46..f2777f77be245b3cbccec78b48b2d3cf84accb6b 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties @@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间 user.password.not.blank=用户密码不能为空 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.not.valid=* 5-50个字符 +user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符 user.email.not.valid=邮箱格式错误 user.email.not.blank=邮箱不能为空 user.phonenumber.not.blank=用户手机号不能为空 diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java index ce80bcefa8727f7486a119cfeca89724c148902f..0c9c14dcfca01902b68bdcbd32cf3a905a699c7b 100644 --- a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java @@ -57,14 +57,15 @@ public class SpringDocAutoConfiguration { openApi.externalDocs(properties.getExternalDocs()); openApi.tags(properties.getTags()); openApi.paths(properties.getPaths()); - openApi.components(properties.getComponents()); - Set keySet = properties.getComponents().getSecuritySchemes().keySet(); - List list = new ArrayList<>(); - SecurityRequirement securityRequirement = new SecurityRequirement(); - keySet.forEach(securityRequirement::addList); - list.add(securityRequirement); - openApi.security(list); - + if (properties.getComponents() != null) { + openApi.components(properties.getComponents()); + Set keySet = properties.getComponents().getSecuritySchemes().keySet(); + List list = new ArrayList<>(); + SecurityRequirement securityRequirement = new SecurityRequirement(); + keySet.forEach(securityRequirement::addList); + list.add(securityRequirement); + openApi.security(list); + } return openApi; } diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml index 7909215178627859839b77726ab74bf6ddfed3f9..9f9ed6ea732950ebdcc5ac680a886b885cda3deb 100644 --- a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml +++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml @@ -20,18 +20,14 @@ dubbo: parameters: namespace: ${spring.profiles.active} metadata-report: - address: redis://${spring.data.redis.host}:${spring.data.redis.port} + address: redis://${spring.data.redis.host:localhost}:${spring.data.redis.port:6379} group: DUBBO_GROUP username: dubbo password: ${spring.data.redis.password} - # 集群开关 - cluster: false parameters: namespace: ${spring.profiles.active} database: ${spring.data.redis.database} timeout: ${spring.data.redis.timeout} - # 集群地址 cluster 为 true 生效 - backup: 127.0.0.1:6379,127.0.0.1:6381 # 消费者相关配置 consumer: # 结果缓存(LRU算法) diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/spring/config/EasyEsConfiguration.java b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/spring/config/EasyEsConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..4a2f89acafe934a19835706b297c7e23b9d0588a --- /dev/null +++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/spring/config/EasyEsConfiguration.java @@ -0,0 +1,102 @@ +package org.dromara.easyes.spring.config; + +import lombok.NonNull; +import lombok.Setter; +import org.dromara.easyes.common.constants.BaseEsConstants; +import org.dromara.easyes.common.property.EasyEsDynamicProperties; +import org.dromara.easyes.common.property.EasyEsProperties; +import org.dromara.easyes.common.strategy.AutoProcessIndexStrategy; +import org.dromara.easyes.common.utils.EsClientUtils; +import org.dromara.easyes.core.index.AutoProcessIndexNotSmoothlyStrategy; +import org.dromara.easyes.core.index.AutoProcessIndexSmoothlyStrategy; +import org.dromara.easyes.spring.factory.IndexStrategyFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +import java.util.Map; + +/** + * Easy-Es Spring配置类 + * @author MoJie + * @since 2.0 + */ +@ConditionalOnProperty(value = "easy-es.enable", havingValue = "true") +@Configuration +public class EasyEsConfiguration implements InitializingBean, EnvironmentAware { + + private Environment environment; + + @Setter + @Autowired(required = false) + private EasyEsProperties easyEsProperties; + + @Setter + @Autowired(required = false) + private EasyEsDynamicProperties easyEsDynamicProperties; + + @Override + public void setEnvironment(@NonNull Environment environment) { + this.environment = environment; + } + + /** + * 当当前配置类注册为bean完成后触发,校验easy-es配置是否存在, + * 如果easy-es.enable: false, 那么不进行校验和抛出异常 + * 默认情况下引入了easy-es是需要配置的,即easy-es.enable:true + * 如果不需要easy-es,那么自行配置为false + * @author MoJie + */ + @Override + public void afterPropertiesSet() { + Boolean enable = environment.getProperty(BaseEsConstants.ENABLE_PREFIX, Boolean.class, Boolean.TRUE); + if (enable) { + Assert.notNull(this.easyEsProperties, "easyEsProperties must is A bean. easy-es配置类必须给配置一个bean"); + } + } + + @Bean + public IndexStrategyFactory indexStrategyFactory() { + return new IndexStrategyFactory(); + } + + @Bean + public EsClientUtils esClientUtils() { + EsClientUtils esClientUtils = new EsClientUtils(); + if (this.easyEsDynamicProperties == null) { + this.easyEsDynamicProperties = new EasyEsDynamicProperties(); + } + Map datasourceMap = this.easyEsDynamicProperties.getDatasource(); + if (datasourceMap.isEmpty()) { + // 设置默认数据源,兼容不使用多数据源配置场景的老用户使用习惯 + datasourceMap.put(EsClientUtils.DEFAULT_DS, this.easyEsProperties); + } + for (String key : datasourceMap.keySet()) { + EasyEsProperties easyEsConfigProperties = datasourceMap.get(key); + EsClientUtils.registerClient(key, () -> EsClientUtils.buildClient(easyEsConfigProperties)); + } + return esClientUtils; + } + + /** + * 索引策略注册 + * + * @return {@link AutoProcessIndexStrategy} + * @author MoJie + */ + @Bean + public AutoProcessIndexStrategy autoProcessIndexSmoothlyStrategy() { + return new AutoProcessIndexSmoothlyStrategy(); + } + + @Bean + public AutoProcessIndexStrategy autoProcessIndexNotSmoothlyStrategy() { + return new AutoProcessIndexNotSmoothlyStrategy(); + } + +} diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/starter/config/GeneratorConfiguration.java b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/starter/config/GeneratorConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..3ab45eabbf0d9f9a8fd0dc249866a349bc8b66ef --- /dev/null +++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/easyes/starter/config/GeneratorConfiguration.java @@ -0,0 +1,27 @@ +package org.dromara.easyes.starter.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.dromara.easyes.core.config.GeneratorConfig; +import org.dromara.easyes.core.toolkit.Generator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 代码生成注册 + * @author MoJie + * @since 2.0 + */ +@ConditionalOnProperty(value = "easy-es.enable", havingValue = "true") +@Component +public class GeneratorConfiguration extends Generator { + + @Autowired + private ElasticsearchClient client; + + @Override + public Boolean generate(GeneratorConfig config) { + super.generateEntity(config, this.client); + return Boolean.TRUE; + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java index 098f6bc8d306e8ecaf545b792dd7af491d6e6404..38b22f388f294c5c2c3cc4050170db6f93cb93d0 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java @@ -6,6 +6,7 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -20,13 +21,14 @@ import org.springframework.context.annotation.Bean; public class ApiDecryptAutoConfiguration { @Bean - public FilterRegistrationBean cryptoFilterRegistration(ApiDecryptProperties properties) { - FilterRegistrationBean registration = new FilterRegistrationBean<>(); - registration.setDispatcherTypes(DispatcherType.REQUEST); - registration.setFilter(new CryptoFilter(properties)); - registration.addUrlPatterns("/*"); - registration.setName("cryptoFilter"); - registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); - return registration; + @FilterRegistration( + name = "cryptoFilter", + urlPatterns = "/*", + order = FilterRegistrationBean.HIGHEST_PRECEDENCE, + dispatcherTypes = DispatcherType.REQUEST + ) + public CryptoFilter cryptoFilter(ApiDecryptProperties properties) { + return new CryptoFilter(properties); } + } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java index b5f194d76da1a5eb62fb87cf348a2c855ae36c14..5e2c7318e86bf21c6fc86b23693f9bc3c80a0d74 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ReflectUtil; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; +import org.dromara.common.core.constant.Constants; import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.annotation.EncryptField; @@ -92,8 +93,12 @@ public class EncryptorManager { * @param encryptContext 加密相关的配置信息 */ public String encrypt(String value, EncryptContext encryptContext) { + if (StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) { + return value; + } IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); - return encryptor.encrypt(value, encryptContext.getEncode()); + String encrypt = encryptor.encrypt(value, encryptContext.getEncode()); + return Constants.ENCRYPT_HEADER + encrypt; } /** @@ -103,8 +108,12 @@ public class EncryptorManager { * @param encryptContext 加密相关的配置信息 */ public String decrypt(String value, EncryptContext encryptContext) { + if (!StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) { + return value; + } IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); - return encryptor.decrypt(value); + String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER); + return encryptor.decrypt(str); } /** diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..204a88e25f8bd6a8de38342dccd116bc76f091cd --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java @@ -0,0 +1,200 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.SneakyThrows; +import org.apache.poi.ss.util.CellRangeAddress; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.excel.annotation.CellMerge; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * 单元格合并处理器 + * + * @author Lion Li + */ +public class CellMergeHandler { + + private final boolean hasTitle; + private int rowIndex; + + private CellMergeHandler(final boolean hasTitle) { + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + } + + @SneakyThrows + public List handle(List rows) { + // 如果入参为空集合则返回空集 + if (CollUtil.isEmpty(rows)) { + return Collections.emptyList(); + } + + // 获取有合并注解的字段 + Map mergeFields = getFieldColumnIndexMap(rows.get(0).getClass()); + // 如果没有需要合并的字段则返回空集 + if (CollUtil.isEmpty(mergeFields)) { + return Collections.emptyList(); + } + + // 结果集 + List result = new ArrayList<>(); + + // 生成两两合并单元格 + Map rowRepeatCellMap = new HashMap<>(); + for (Map.Entry item : mergeFields.entrySet()) { + Field field = item.getKey(); + FieldColumnIndex itemValue = item.getValue(); + int colNum = itemValue.colIndex(); + CellMerge cellMerge = itemValue.cellMerge(); + + for (int i = 0; i < rows.size(); i++) { + // 当前行数据 + Object currentRowObj = rows.get(i); + // 当前行数据字段值 + Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName()); + + // 空值跳过不处理 + if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) { + continue; + } + + // 单元格合并Map是否存在数据,如果不存在则添加当前行的字段值 + if (!rowRepeatCellMap.containsKey(field)) { + rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i)); + continue; + } + + // 获取 单元格合并Map 中字段值 + RepeatCell repeatCell = rowRepeatCellMap.get(field); + Object cellValue = repeatCell.value(); + int current = repeatCell.current(); + + // 检查是否满足合并条件 + // currentRowObj 当前行数据 + // rows.get(i - 1) 上一行数据 注:由于 if (!rowRepeatCellMap.containsKey(field)) 条件的存在,所以该 i 必不可能小于1 + // cellMerge 当前行字段合并注解 + boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge); + + // 是否添加到结果集 + boolean isAddResult = false; + // 最新行 + int lastRow = i + rowIndex - 1; + + // 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换 + if (!currentRowObjFieldVal.equals(cellValue) || !merge) { + rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i)); + isAddResult = true; + } + + // 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后 + if (i == rows.size() - 1) { + isAddResult = true; + if (i > current) { + lastRow = i + rowIndex; + } + } + + if (isAddResult && i > current) { + result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum)); + } + } + } + return result; + } + + /** + * 获取带有合并注解的字段列索引和合并注解信息Map集 + */ + private Map getFieldColumnIndexMap(Class clazz) { + boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class); + Field[] fields = ReflectUtils.getFields(clazz, field -> { + if ("serialVersionUID".equals(field.getName())) { + return false; + } + if (field.isAnnotationPresent(ExcelIgnore.class)) { + return false; + } + return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class); + }); + + // 有注解的字段 + Map mergeFields = new HashMap<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (!field.isAnnotationPresent(CellMerge.class)) { + continue; + } + CellMerge cm = field.getAnnotation(CellMerge.class); + int index = cm.index() == -1 ? i : cm.index(); + mergeFields.put(field, FieldColumnIndex.of(index, cm)); + + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + return mergeFields; + } + + private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) { + final String[] mergeBy = cellMerge.mergeBy(); + if (StrUtil.isAllNotBlank(mergeBy)) { + //比对当前行和上一行的各个属性值一一比对 如果全为真 则为真 + for (String fieldName : mergeBy) { + final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName); + final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName); + if (!Objects.equals(valPre, valCurrent)) { + //依赖字段如有任一不等值,则标记为不可合并 + return false; + } + } + } + return true; + } + + /** + * 单元格合并 + */ + record RepeatCell(Object value, int current) { + static RepeatCell of(Object value, int current) { + return new RepeatCell(value, current); + } + } + + /** + * 字段列索引和合并注解信息 + */ + record FieldColumnIndex(int colIndex, CellMerge cellMerge) { + static FieldColumnIndex of(int colIndex, CellMerge cellMerge) { + return new FieldColumnIndex(colIndex, cellMerge); + } + } + + /** + * 创建一个单元格合并处理器实例 + * + * @param hasTitle 是否合并标题 + * @return 单元格合并处理器 + */ + public static CellMergeHandler of(final boolean hasTitle) { + return new CellMergeHandler(hasTitle); + } + + /** + * 创建一个单元格合并处理器实例(默认不合并标题) + * + * @return 单元格合并处理器 + */ + public static CellMergeHandler of() { + return new CellMergeHandler(false); + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java index 515f68e1b80eeefd22d73d6ba56be2b2782b3fe0..34bf4a42f7c6e80449b92c0177f3246180dab995 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java @@ -1,24 +1,15 @@ package org.dromara.common.excel.core; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.metadata.Head; import cn.idev.excel.write.handler.WorkbookWriteHandler; import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext; import cn.idev.excel.write.merge.AbstractMergeStrategy; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; -import org.dromara.common.core.utils.reflect.ReflectUtils; -import org.dromara.common.excel.annotation.CellMerge; -import java.lang.reflect.Field; import java.util.*; /** @@ -30,134 +21,39 @@ import java.util.*; public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler { private final List cellList; - private final boolean hasTitle; - private int rowIndex; + + public CellMergeStrategy(List cellList) { + this.cellList = cellList; + } public CellMergeStrategy(List list, boolean hasTitle) { - this.hasTitle = hasTitle; - // 行合并开始下标 - this.rowIndex = hasTitle ? 1 : 0; - this.cellList = handle(list, hasTitle); + this.cellList = CellMergeHandler.of(hasTitle).handle(list); } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + if (CollUtil.isEmpty(cellList)){ + return; + } //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空 final int rowIndex = cell.getRowIndex(); - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress cellAddresses : cellList) { - final int firstRow = cellAddresses.getFirstRow(); - if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ - cell.setBlank(); - } + for (CellRangeAddress cellAddresses : cellList) { + final int firstRow = cellAddresses.getFirstRow(); + if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ + cell.setBlank(); } } } @Override public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) { - //当前表格写完后,统一写入 - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress item : cellList) { - context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); - } - } - } - - @SneakyThrows - private List handle(List list, boolean hasTitle) { - List cellList = new ArrayList<>(); - if (CollUtil.isEmpty(list)) { - return cellList; - } - Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName())); - - // 有注解的字段 - List mergeFields = new ArrayList<>(); - List mergeFieldsIndex = new ArrayList<>(); - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - if (field.isAnnotationPresent(CellMerge.class)) { - CellMerge cm = field.getAnnotation(CellMerge.class); - mergeFields.add(field); - mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); - if (hasTitle) { - ExcelProperty property = field.getAnnotation(ExcelProperty.class); - rowIndex = Math.max(rowIndex, property.value().length); - } - } - } - - Map map = new HashMap<>(); - // 生成两两合并单元格 - for (int i = 0; i < list.size(); i++) { - for (int j = 0; j < mergeFields.size(); j++) { - Field field = mergeFields.get(j); - Object val = ReflectUtils.invokeGetter(list.get(i), field.getName()); - - int colNum = mergeFieldsIndex.get(j); - if (!map.containsKey(field)) { - map.put(field, new RepeatCell(val, i)); - } else { - RepeatCell repeatCell = map.get(field); - Object cellValue = repeatCell.getValue(); - if (cellValue == null || "".equals(cellValue)) { - // 空值跳过不合并 - continue; - } - - if (!cellValue.equals(val)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } else if (i == list.size() - 1) { - if (!isMerge(list, i, field)) { - // 如果最后一行不能合并,检查之前的数据是否需要合并 - if (i - repeatCell.getCurrent() > 1) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - } else if (i > repeatCell.getCurrent()) { - // 如果最后一行可以合并,则直接合并到最后 - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); - } - } else if (!isMerge(list, i, field)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } - } - } + if (CollUtil.isEmpty(cellList)){ + return; } - return cellList; - } - - private boolean isMerge(List list, int i, Field field) { - boolean isMerge = true; - CellMerge cm = field.getAnnotation(CellMerge.class); - final String[] mergeBy = cm.mergeBy(); - if (StrUtil.isAllNotBlank(mergeBy)) { - //比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真 - for (String fieldName : mergeBy) { - final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName); - final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName); - if (!Objects.equals(valPre, valCurrent)) { - //依赖字段如有任一不等值,则标记为不可合并 - isMerge = false; - } - } + //当前表格写完后,统一写入 + for (CellRangeAddress item : cellList) { + context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); } - return isMerge; } - @Data - @AllArgsConstructor - static class RepeatCell { - - private Object value; - - private int current; - - } } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java index 8b53a0ce02c728599de884bc643becf06fd19bfd..7cdb5c5c0b40609c2582793a836368b1b79ceff7 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java @@ -1,5 +1,6 @@ package org.dromara.common.excel.core; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Data; @@ -65,7 +66,7 @@ public class DropDownOptions { StringBuilder stringBuffer = new StringBuilder(); String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$"; for (int i = 0; i < vars.length; i++) { - String var = StrUtil.trimToEmpty(String.valueOf(vars[i])); + String var = StrUtil.trimToEmpty(Convert.toStr(vars[i])); if (!var.matches(regex)) { throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字"); } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java index f3b641545a1b036098835c7e39906d0f73698def..13972839097933ab1c8ff29c78f893135fd7a37f 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java @@ -1,6 +1,7 @@ package org.dromara.common.excel.core; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.ObjectUtil; @@ -103,7 +104,7 @@ public class ExcelDownHandler implements SheetWriteHandler { if (StringUtils.isNotBlank(dictType)) { // 如果传递了字典名,则依据字典建立下拉 Collection values = Optional.ofNullable(dictService.getAllDictByDictType(dictType)) - .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType))) + .orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType)) .values(); options = new ArrayList<>(values); } else if (StringUtils.isNotBlank(converterExp)) { @@ -115,7 +116,7 @@ public class ExcelDownHandler implements SheetWriteHandler { // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑 ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class); List values = EnumUtil.getFieldValues(format.enumClass(), format.textField()); - options = StreamUtils.toList(values, String::valueOf); + options = StreamUtils.toList(values, Convert::toStr); } if (ObjectUtil.isNotEmpty(options)) { // 仅当下拉可选项不为空时执行 diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java index 1b35e559add934d10fda40864bcaf6167f0419bc..74dbccbac059930614c4273b4babb212e5a7ab91 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java @@ -27,6 +27,7 @@ import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * Excel相关处理 @@ -203,6 +204,44 @@ public class ExcelUtil { builder.doWrite(list); } + /** + * 导出excel + * + * @param headType 带Excel注解的类型 + * @param os 输出流 + * @param options Excel下拉可选项 + * @param consumer 导出助手消费函数 + */ + public static void exportExcel(Class headType, OutputStream os, List options, Consumer> consumer) { + try (ExcelWriter writer = FastExcel.write(os, headType) + .autoCloseStream(false) + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + // 批注必填项处理 + .registerWriteHandler(new DataWriteHandler(headType)) + // 添加下拉框操作 + .registerWriteHandler(new ExcelDownHandler(options)) + .build()) { + // 执行消费函数 + consumer.accept(ExcelWriterWrapper.of(writer)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出excel + * + * @param headType 带Excel注解的类型 + * @param os 输出流 + * @param consumer 导出助手消费函数 + */ + public static void exportExcel(Class headType, OutputStream os, Consumer> consumer) { + exportExcel(headType, os, null, consumer); + } + /** * 单表多数据模板导出 模板格式为 {.属性} * diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..396f3713e4e0b33d629577c5c9a6b2b43f6930ff --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java @@ -0,0 +1,127 @@ +package org.dromara.common.excel.utils; + +import cn.idev.excel.ExcelWriter; +import cn.idev.excel.FastExcel; +import cn.idev.excel.context.WriteContext; +import cn.idev.excel.write.builder.ExcelWriterSheetBuilder; +import cn.idev.excel.write.builder.ExcelWriterTableBuilder; +import cn.idev.excel.write.metadata.WriteSheet; +import cn.idev.excel.write.metadata.WriteTable; +import cn.idev.excel.write.metadata.fill.FillConfig; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * ExcelWriterWrapper Excel写出包装器 + *
+ * 提供了一组与 ExcelWriter 一一对应的写出方法,避免直接提供 ExcelWriter 而导致的一些不可控问题(比如提前关闭了IO流等) + * + * @author 秋辞未寒 + * @see ExcelWriter + */ +public record ExcelWriterWrapper(ExcelWriter excelWriter) { + + public void write(Collection data, WriteSheet writeSheet) { + excelWriter.write(data, writeSheet); + } + + public void write(Supplier> supplier, WriteSheet writeSheet) { + excelWriter.write(supplier.get(), writeSheet); + } + + public void write(Collection data, WriteSheet writeSheet, WriteTable writeTable) { + excelWriter.write(data, writeSheet, writeTable); + } + + public void write(Supplier> supplier, WriteSheet writeSheet, WriteTable writeTable) { + excelWriter.write(supplier.get(), writeSheet, writeTable); + } + + public void fill(Object data, WriteSheet writeSheet) { + excelWriter.fill(data, writeSheet); + } + + public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) { + excelWriter.fill(data, fillConfig, writeSheet); + } + + public void fill(Supplier supplier, WriteSheet writeSheet) { + excelWriter.fill(supplier, writeSheet); + } + + public void fill(Supplier supplier, FillConfig fillConfig, WriteSheet writeSheet) { + excelWriter.fill(supplier, fillConfig, writeSheet); + } + + public WriteContext writeContext() { + return excelWriter.writeContext(); + } + + /** + * 创建一个 ExcelWriterWrapper + * + * @param excelWriter ExcelWriter + * @return ExcelWriterWrapper + */ + public static ExcelWriterWrapper of(ExcelWriter excelWriter) { + return new ExcelWriterWrapper<>(excelWriter); + } + + // -------------------------------- sheet start + + public static WriteSheet buildSheet(Integer sheetNo, String sheetName) { + return sheetBuilder(sheetNo, sheetName).build(); + } + + public static WriteSheet buildSheet(Integer sheetNo) { + return sheetBuilder(sheetNo).build(); + } + + public static WriteSheet buildSheet(String sheetName) { + return sheetBuilder(sheetName).build(); + } + + public static WriteSheet buildSheet() { + return sheetBuilder().build(); + } + + public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo, String sheetName) { + return FastExcel.writerSheet(sheetNo, sheetName); + } + + public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo) { + return FastExcel.writerSheet(sheetNo); + } + + public static ExcelWriterSheetBuilder sheetBuilder(String sheetName) { + return FastExcel.writerSheet(sheetName); + } + + public static ExcelWriterSheetBuilder sheetBuilder() { + return FastExcel.writerSheet(); + } + + // -------------------------------- sheet end + + // -------------------------------- table start + + public static WriteTable buildTable(Integer tableNo) { + return tableBuilder(tableNo).build(); + } + + public static WriteTable buildTable() { + return tableBuilder().build(); + } + + public static ExcelWriterTableBuilder tableBuilder(Integer tableNo) { + return FastExcel.writerTable(tableNo); + } + + public static ExcelWriterTableBuilder tableBuilder() { + return FastExcel.writerTable(); + } + + // -------------------------------- table end + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java index 77cf833816f7fbf806aeeba21ba4b785a416e6b0..0392573871ed8e3ebf5d4dc9024e8996cfdb9841 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java @@ -1,5 +1,6 @@ package org.dromara.common.json.config; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; @@ -28,20 +29,24 @@ import java.util.TimeZone; @AutoConfiguration(before = JacksonAutoConfiguration.class) public class JacksonConfig { + @Bean + public Module registerJavaTimeModule() { + // 全局配置序列化返回 JSON 处理 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer()); + return javaTimeModule; + } + @Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { - // 全局配置序列化返回 JSON 处理 - JavaTimeModule javaTimeModule = new JavaTimeModule(); - javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); - javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); - javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer()); - builder.modules(javaTimeModule); builder.timeZone(TimeZone.getDefault()); log.info("初始化 jackson 配置"); }; diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java index 069b924f0b680ef160d7dfbb373954682822891e..21c6a6aa6aae60939f1e8bd4d79df0964bb645d3 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java @@ -1,9 +1,11 @@ package org.dromara.common.json.handler; +import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import org.dromara.common.core.utils.ObjectUtils; import java.io.IOException; import java.util.Date; @@ -25,7 +27,11 @@ public class CustomDateDeserializer extends JsonDeserializer { */ @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DateUtil.parse(p.getText()); + DateTime parse = DateUtil.parse(p.getText()); + if (ObjectUtils.isNull(parse)) { + return null; + } + return parse.toJdkDate(); } } diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java index b4fb847dedb3968df9d194b1a021ecd6a2129872..5f9102a40d1dbeea132faecba78c76cad88b26ed 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java @@ -27,9 +27,7 @@ import org.springframework.http.HttpMethod; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; -import java.util.Collection; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; /** * 操作日志记录处理 @@ -177,14 +175,28 @@ public class LogAspect { if (ArrayUtil.isEmpty(paramsArray)) { return params.toString(); } + String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES); for (Object o : paramsArray) { if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { - String str = JsonUtils.toJsonString(o); - Dict dict = JsonUtils.parseMap(str); - if (MapUtil.isNotEmpty(dict)) { - MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); - MapUtil.removeAny(dict, excludeParamNames); - str = JsonUtils.toJsonString(dict); + String str = ""; + if (o instanceof List list) { + List list1 = new ArrayList<>(); + for (Object obj : list) { + String str1 = JsonUtils.toJsonString(obj); + Dict dict = JsonUtils.parseMap(str1); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, exclude); + list1.add(dict); + } + } + str = JsonUtils.toJsonString(list1); + } else { + str = JsonUtils.toJsonString(o); + Dict dict = JsonUtils.parseMap(str); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, exclude); + str = JsonUtils.toJsonString(dict); + } } params.add(str); } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..54d5ad41a254f9ecb7a995e6f59eab1d64980930 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java @@ -0,0 +1,54 @@ +package org.dromara.common.mybatis.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.helper.DataPermissionHelper; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限注解Advice + * + * @author 秋辞未寒 + */ +@Slf4j +public class DataPermissionAdvice implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + Object target = invocation.getThis(); + Method method = invocation.getMethod(); + Object[] args = invocation.getArguments(); + // 设置权限注解 + DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args)); + try { + // 执行代理方法 + return invocation.proceed(); + } finally { + // 清除权限注解 + DataPermissionHelper.removePermission(); + } + } + + /** + * 获取数据权限注解 + */ + private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){ + DataPermission dataPermission = method.getAnnotation(DataPermission.class); + // 优先获取方法上的注解 + if (dataPermission != null) { + return dataPermission; + } + // 方法上没有注解,则获取类上的注解 + Class targetClass = target.getClass(); + // 如果是 JDK 动态代理,则获取真实的Class实例 + if (Proxy.isProxyClass(targetClass)) { + targetClass = targetClass.getInterfaces()[0]; + } + dataPermission = targetClass.getAnnotation(DataPermission.class); + return dataPermission; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java deleted file mode 100644 index 1c83cc392f8d8d65bac632e0d90732410ee84278..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.dromara.common.mybatis.aspect; - -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.dromara.common.mybatis.annotation.DataPermission; -import org.dromara.common.mybatis.helper.DataPermissionHelper; - -/** - * 数据权限处理 - * - * @author Lion Li - */ -@Slf4j -@Aspect -public class DataPermissionAspect { - - /** - * 处理请求前执行 - */ - @Before(value = "@annotation(dataPermission)") - public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) { - DataPermissionHelper.setPermission(dataPermission); - } - - /** - * 处理完请求后执行 - * - * @param joinPoint 切点 - */ - @AfterReturning(pointcut = "@annotation(dataPermission)") - public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) { - DataPermissionHelper.removePermission(); - } - - /** - * 拦截异常操作 - * - * @param joinPoint 切点 - * @param e 异常 - */ - @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) { - DataPermissionHelper.removePermission(); - } - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java new file mode 100644 index 0000000000000000000000000000000000000000..4b7d945a3b3d47ca1c46757271439fc20db439a6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java @@ -0,0 +1,39 @@ +package org.dromara.common.mybatis.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.springframework.aop.support.StaticMethodMatcherPointcut; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限匹配切点 + * + * @author 秋辞未寒 + */ +@Slf4j +@SuppressWarnings("all") +public class DataPermissionPointcut extends StaticMethodMatcherPointcut { + + @Override + public boolean matches(Method method, Class targetClass) { + // 优先匹配方法 + // 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口 + if (method.isAnnotationPresent(DataPermission.class)) { + return true; + } + + // MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理 + Class targetClassRef = targetClass; + if (Proxy.isProxyClass(targetClassRef)) { + // 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。 + // 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解 + // 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass + targetClassRef = targetClass.getInterfaces()[0]; + + } + return targetClassRef.isAnnotationPresent(DataPermission.class); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java new file mode 100644 index 0000000000000000000000000000000000000000..351288ce4ac1d8fc93ae9a2a533f2b5879d4cf51 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java @@ -0,0 +1,33 @@ +package org.dromara.common.mybatis.aspect; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; + +/** + * 数据权限注解切面定义 + * + * @author 秋辞未寒 + */ +@SuppressWarnings("all") +public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor { + + private final Advice advice; + private final Pointcut pointcut; + + public DataPermissionPointcutAdvisor() { + this.advice = new DataPermissionAdvice(); + this.pointcut = new DataPermissionPointcut(); + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this.advice; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java index 18d8b75df9cbac281a81d12a377ebd9b9846b136..c11ca8ea551facf60ae3227dc8a2ac0ebd085989 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java @@ -11,7 +11,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import org.dromara.common.core.factory.YmlPropertySourceFactory; import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.mybatis.aspect.DataPermissionAspect; +import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.handler.MybatisExceptionHandler; import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler; @@ -19,9 +19,11 @@ import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.dromara.common.mybatis.service.SysDataScopeService; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.Role; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -30,6 +32,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * @author Lion Li */ @AutoConfiguration +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @EnableTransactionManagement(proxyTargetClass = true) @MapperScan("${mybatis-plus.mapperPackage}") @PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) @@ -57,17 +60,17 @@ public class MybatisPlusConfiguration { * 数据权限拦截器 */ public PlusDataPermissionInterceptor dataPermissionInterceptor() { - return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); + return new PlusDataPermissionInterceptor(); } /** * 数据权限切面处理器 */ @Bean - public DataPermissionAspect dataPermissionAspect() { - return new DataPermissionAspect(); + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() { + return new DataPermissionPointcutAdvisor(); } - /** * 分页插件,自动识别数据库类型 */ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java index 8db4b3c552a309e59a4145507f01dbfc8b316e69..1fe2b3ef142478ec7598ff1ca0a9388b65ccc6b2 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java @@ -51,6 +51,8 @@ public class TableDataInfo implements Serializable { public TableDataInfo(List list, long total) { this.rows = list; this.total = total; + this.code = HttpStatus.HTTP_OK; + this.msg = "查询成功"; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java index 5084424eb8b6842483f98f3fff81d3d06c179635..2d5244bcf26fc7742aa04f39b55f598c38a191c2 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java @@ -42,17 +42,46 @@ public enum DataBaseType { * 根据数据库产品名称查找对应的数据库类型 * * @param databaseProductName 数据库产品名称 - * @return 对应的数据库类型枚举值,如果未找到则返回 null + * @return 对应的数据库类型枚举值 */ public static DataBaseType find(String databaseProductName) { if (StringUtils.isBlank(databaseProductName)) { - return null; + return MY_SQL; } for (DataBaseType type : values()) { if (type.getType().equals(databaseProductName)) { return type; } } - return null; + return MY_SQL; } + + /** + * 判断是否为 MySQL 类型 + */ + public boolean isMySql() { + return this == MY_SQL; + } + + /** + * 判断是否为 Oracle 类型 + */ + public boolean isOracle() { + return this == ORACLE; + } + + /** + * 判断是否为 PostgreSQL 类型 + */ + public boolean isPostgreSql() { + return this == POSTGRE_SQL; + } + + /** + * 判断是否为 SQL Server 类型 + */ + public boolean isSqlServer() { + return this == SQL_SERVER; + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java index 0acd10a5ddcb93f62dc988c83bc6c022384f6bf1..29a70d092f421c890bb1c9d12a096ceda31bfd50 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.handler; -import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; @@ -10,7 +9,6 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; -import org.apache.ibatis.io.Resources; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StreamUtils; @@ -22,22 +20,13 @@ import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.system.api.model.LoginUser; import org.dromara.system.api.model.RoleDTO; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.core.type.ClassMetadata; -import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.expression.*; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.ClassUtils; -import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; /** @@ -49,11 +38,6 @@ import java.util.function.Function; @Slf4j public class PlusDataPermissionHandler { - /** - * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行) - */ - private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); - /** * spel 解析器 */ @@ -64,27 +48,17 @@ public class PlusDataPermissionHandler { */ private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); - /** - * 构造方法,扫描指定包下的 Mapper 类并初始化缓存 - * - * @param mapperPackage Mapper 类所在的包路径 - */ - public PlusDataPermissionHandler(String mapperPackage) { - scanMapperClasses(mapperPackage); - } - /** * 获取数据过滤条件的 SQL 片段 * * @param where 原始的查询条件表达式 - * @param mappedStatementId Mapper 方法的 ID * @param isSelect 是否为查询语句 * @return 数据过滤条件的 SQL 片段 */ - public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { + public Expression getSqlSegment(Expression where, boolean isSelect) { try { // 获取数据权限配置 - DataPermission dataPermission = getDataPermission(mappedStatementId); + DataPermission dataPermission = getDataPermission(); // 获取当前登录用户信息 LoginUser currentUser = DataPermissionHelper.getVariable("user"); if (ObjectUtil.isNull(currentUser)) { @@ -206,92 +180,22 @@ public class PlusDataPermissionHandler { return StringUtils.EMPTY; } - /** - * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类 - * - * @param mapperPackage Mapper 类所在的包路径 - */ - private void scanMapperClasses(String mapperPackage) { - // 创建资源解析器和元数据读取工厂 - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); - // 将 Mapper 包路径按分隔符拆分为数组 - String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); - String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; - try { - for (String packagePattern : packagePatternArray) { - // 将包路径转换为资源路径 - String path = ClassUtils.convertClassNameToResourcePath(packagePattern); - // 获取指定路径下的所有 .class 文件资源 - Resource[] resources = resolver.getResources(classpath + path + "/*.class"); - for (Resource resource : resources) { - // 获取资源的类元数据 - ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata(); - // 获取资源对应的类对象 - Class clazz = Resources.classForName(classMetadata.getClassName()); - // 查找类中的特定注解 - findAnnotation(clazz); - } - } - } catch (Exception e) { - log.error("初始化数据安全缓存时出错:{}", e.getMessage()); - } - } - - /** - * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中 - * - * @param clazz 要查找的类 - */ - private void findAnnotation(Class clazz) { - DataPermission dataPermission; - for (Method method : clazz.getMethods()) { - if (method.isDefault() || method.isVarArgs()) { - continue; - } - String mappedStatementId = clazz.getName() + "." + method.getName(); - if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); - dataPermissionCacheMap.put(mappedStatementId, dataPermission); - } - } - if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); - dataPermissionCacheMap.put(clazz.getName(), dataPermission); - } - } - /** * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象 * - * @param mapperId 映射语句 ID * @return DataPermission 注解对象,如果不存在则返回 null */ - public DataPermission getDataPermission(String mapperId) { - // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象 - if (DataPermissionHelper.getPermission() != null) { - return DataPermissionHelper.getPermission(); - } - // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象 - if (dataPermissionCacheMap.containsKey(mapperId)) { - return dataPermissionCacheMap.get(mapperId); - } - // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找 - String clazzName = mapperId.substring(0, mapperId.lastIndexOf(".")); - if (dataPermissionCacheMap.containsKey(clazzName)) { - return dataPermissionCacheMap.get(clazzName); - } - return null; + public DataPermission getDataPermission() { + return DataPermissionHelper.getPermission(); } /** * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象 * - * @param mapperId 映射语句 ID * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true */ - public boolean invalid(String mapperId) { - return getDataPermission(mapperId) == null; + public boolean invalid() { + return getDataPermission() == null; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java index bf40756b21aba058c1176f5fd67d6070b81214a1..487ffd375178f22fc778e9bd92d41f1df5db0028 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java @@ -26,7 +26,14 @@ public class DataBaseHelper { private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); /** - * 获取当前数据库类型 + * 获取当前数据源对应的数据库类型 + *

+ * 通过 DynamicRoutingDataSource 获取当前线程绑定的数据源, + * 然后从数据源获取数据库连接,利用连接的元数据获取数据库产品名称, + * 最后调用 DataBaseType.find 方法将数据库名称转换为对应的枚举类型 + * + * @return 当前数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL + * @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常 */ public static DataBaseType getDataBaseType() { DataSource dataSource = DS.determineDataSource(); @@ -39,37 +46,31 @@ public class DataBaseHelper { } } - public static boolean isMySql() { - return DataBaseType.MY_SQL == getDataBaseType(); - } - - public static boolean isOracle() { - return DataBaseType.ORACLE == getDataBaseType(); - } - - public static boolean isPostgerSql() { - return DataBaseType.POSTGRE_SQL == getDataBaseType(); - } - - public static boolean isSqlServer() { - return DataBaseType.SQL_SERVER == getDataBaseType(); - } - + /** + * 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段 + *

+ * 用于判断指定值是否存在于逗号分隔的字符串列中,SQL写法根据不同数据库方言自动切换: + * - Oracle 使用 instr 函数 + * - PostgreSQL 使用 strpos 函数 + * - SQL Server 使用 charindex 函数 + * - 其他默认使用 MySQL 的 find_in_set 函数 + * + * @param var1 要查找的值(支持任意类型,内部会转换成字符串) + * @param var2 存储逗号分隔值的数据库列名 + * @return 适用于当前数据库的 SQL 条件字符串,通常用于 where 或 apply 中拼接 + */ public static String findInSet(Object var1, String var2) { - DataBaseType dataBasyType = getDataBaseType(); String var = Convert.toStr(var1); - if (dataBasyType == DataBaseType.SQL_SERVER) { - // charindex(',100,' , ',0,100,101,') <> 0 - return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); - } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { - // (select strpos(',0,100,101,' , ',100,')) <> 0 - return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); - } else if (dataBasyType == DataBaseType.ORACLE) { + return switch (getDataBaseType()) { // instr(',0,100,101,' , ',100,') <> 0 - return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); - } - // find_in_set('100' , '0,100,101') - return "find_in_set('%s' , %s) <> 0".formatted(var, var2); + case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); + // (select strpos(',0,100,101,' , ',100,')) <> 0 + case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); + // charindex(',100,' , ',0,100,101,') <> 0 + case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); + // find_in_set(100 , '0,100,101') + default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2); + }; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java index 85a4d0abc6ee88668988719eef98f112e3dd9201..b37d96ed19f253c2d55c7f9f3e055c286ec75cce 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java @@ -35,16 +35,7 @@ import java.util.List; @Slf4j public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor { - private final PlusDataPermissionHandler dataPermissionHandler; - - /** - * 构造函数,初始化 PlusDataPermissionHandler 实例 - * - * @param mapperPackage 扫描的映射器包 - */ - public PlusDataPermissionInterceptor(String mapperPackage) { - this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); - } + private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); /** * 在执行查询之前,检查并处理数据权限相关逻辑 @@ -64,7 +55,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } // 解析 sql 分配对应方法 @@ -92,7 +83,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); @@ -128,7 +119,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto */ @Override protected void processUpdate(Update update, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false); if (null != sqlSegment) { update.setWhere(sqlSegment); } @@ -144,7 +135,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto */ @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false); if (null != sqlSegment) { delete.setWhere(sqlSegment); } @@ -157,7 +148,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto * @param mappedStatementId 映射语句的 ID */ protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true); if (null != sqlSegment) { plainSelect.setWhere(sqlSegment); } diff --git a/ruoyi-common/ruoyi-common-nacos/pom.xml b/ruoyi-common/ruoyi-common-nacos/pom.xml index dab39bf5c782d042a9cfbbc2071065040fa260a9..9ec5d3e4a7b6f9367a03753ea6d0ff77636785fe 100644 --- a/ruoyi-common/ruoyi-common-nacos/pom.xml +++ b/ruoyi-common/ruoyi-common-nacos/pom.xml @@ -26,18 +26,6 @@ com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config - - - com.alibaba.nacos - logback-adapter - 1.1.3 - - - com.alibaba.nacos - nacos-common - - - diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml index f9583aa1cf4c670dd93ca2bbc8c6bc2a5a33c697..190dc5d4624ef10e211dd9691bbaf0c179ac4f65 100644 --- a/ruoyi-common/ruoyi-common-oss/pom.xml +++ b/ruoyi-common/ruoyi-common-oss/pom.xml @@ -31,11 +31,6 @@ software.amazon.awssdk s3 - - - software.amazon.awssdk - netty-nio-client - software.amazon.awssdk diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java index 99bc294eddcbc9b096200891005995d5d7a5d570..b9a90dc752b6b5adb093e914ec1542e76a53d889 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java @@ -2,6 +2,7 @@ package org.dromara.common.oss.core; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; +import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.constant.Constants; import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.StringUtils; @@ -13,9 +14,7 @@ import org.dromara.common.oss.exception.OssException; import org.dromara.common.oss.properties.OssProperties; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.async.AsyncResponseTransformer; -import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; +import software.amazon.awssdk.core.async.*; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; @@ -29,9 +28,12 @@ import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener; import java.io.*; import java.net.URI; import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Optional; import java.util.function.Consumer; /** @@ -40,6 +42,7 @@ import java.util.function.Consumer; * * @author AprilWind */ +@Slf4j public class OssClient { /** @@ -177,12 +180,12 @@ public class OssClient { // 创建异步请求体(length如果为空会报错) BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder() .contentLength(length) - .subscribeTimeout(Duration.ofSeconds(30)) + .subscribeTimeout(Duration.ofSeconds(120)) .build(); // 使用 transferManager 进行上传 Upload upload = transferManager.upload( - x -> x.requestBody(body) + x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create()) .putObjectRequest( y -> y.bucket(properties.getBucketName()) .key(key) @@ -236,30 +239,62 @@ public class OssClient { * * @param key 文件在 Amazon S3 中的对象键 * @param out 输出流 - * @return 输出流中写入的字节数(长度) + * @param consumer 自定义处理逻辑 * @throws OssException 如果下载失败,抛出自定义异常 */ public void download(String key, OutputStream out, Consumer consumer) { + try { + this.download(key, consumer).writeTo(out); + } catch (Exception e) { + throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); + } + } + + /** + * 下载文件从 Amazon S3 到 输出流 + * + * @param key 文件在 Amazon S3 中的对象键 + * @param contentLengthConsumer 文件大小消费者函数 + * @return 写出订阅器 + * @throws OssException 如果下载失败,抛出自定义异常 + */ + public WriteOutSubscriber download(String key, Consumer contentLengthConsumer) { try { // 构建下载请求 - DownloadRequest> downloadRequest = DownloadRequest.builder() + DownloadRequest> publisherDownloadRequest = DownloadRequest.builder() // 文件对象 .getObjectRequest(y -> y.bucket(properties.getBucketName()) .key(key) .build()) .addTransferListener(LoggingTransferListener.create()) - // 使用订阅转换器 - .responseTransformer(AsyncResponseTransformer.toBlockingInputStream()) + // 使用发布订阅转换器 + .responseTransformer(AsyncResponseTransformer.toPublisher()) .build(); + // 使用 S3TransferManager 下载文件 - Download> responseFuture = transferManager.download(downloadRequest); - // 输出到流中 - try (ResponseInputStream responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream - if (consumer != null) { - consumer.accept(responseStream.response().contentLength()); + Download> publisherDownload = transferManager.download(publisherDownloadRequest); + // 获取下载发布订阅转换器 + ResponsePublisher publisher = publisherDownload.completionFuture().join().result(); + // 执行文件大小消费者函数 + Optional.ofNullable(contentLengthConsumer) + .ifPresent(lengthConsumer -> lengthConsumer.accept(publisher.response().contentLength())); + + // 构建写出订阅器对象 + return out -> { + // 创建可写入的字节通道 + try(WritableByteChannel channel = Channels.newChannel(out)){ + // 订阅数据 + publisher.subscribe(byteBuffer -> { + while (byteBuffer.hasRemaining()) { + try { + channel.write(byteBuffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }).join(); } - responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread - } + }; } catch (Exception e) { throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java new file mode 100644 index 0000000000000000000000000000000000000000..d3a9841a1b698eff66fdf2d2b7b8c40fd11cc417 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java @@ -0,0 +1,15 @@ +package org.dromara.common.oss.core; + +import java.io.IOException; + +/** + * 写出订阅器 + * + * @author 秋辞未寒 + */ +@FunctionalInterface +public interface WriteOutSubscriber { + + void writeTo(T out) throws IOException; + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java index 355cd29316b89b92a88ffb6e9a82d7b36aad4fcf..c433bffad865bc72bd22bb4de56538515d709be6 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java @@ -129,9 +129,9 @@ public class RedisUtils { } catch (Exception e) { long timeToLive = bucket.remainTimeToLive(); if (timeToLive == -1) { - setCacheObject(key, value); + bucket.set(value); } else { - setCacheObject(key, value, Duration.ofMillis(timeToLive)); + bucket.set(value, Duration.ofMillis(timeToLive)); } } } else { @@ -147,11 +147,8 @@ public class RedisUtils { * @param duration 时间 */ public static void setCacheObject(final String key, final T value, final Duration duration) { - RBatch batch = CLIENT.createBatch(); - RBucketAsync bucket = batch.getBucket(key); - bucket.setAsync(value); - bucket.expireAsync(duration); - batch.execute(); + RBucket bucket = CLIENT.getBucket(key); + bucket.set(value, duration); } /** diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java index 657dbbc073f5d8dc78aa165cd9064be834b1f37e..a3d0badc0bdc7840159bd7e8b49b3cae1c356738 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java @@ -1,7 +1,7 @@ package org.dromara.common.redis.utils; +import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.common.core.utils.SpringUtils; @@ -10,6 +10,10 @@ import org.redisson.api.RIdGenerator; import org.redisson.api.RedissonClient; import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; /** * 发号器工具类 @@ -23,12 +27,12 @@ public class SequenceUtils { /** * 默认初始值 */ - public static final Long DEFAULT_INIT_VALUE = 1L; + public static final long DEFAULT_INIT_VALUE = 1L; /** * 默认步长 */ - public static final Long DEFAULT_STEP_VALUE = 1L; + public static final long DEFAULT_STEP_VALUE = 1L; /** * 默认过期时间-天 @@ -40,6 +44,11 @@ public class SequenceUtils { */ public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1); + /** + * 默认最小ID容量位数 - 6位数(即至少可以生成的ID为999999个) + */ + public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6; + /** * 获取Redisson客户端实例 */ @@ -54,14 +63,11 @@ public class SequenceUtils { * @param stepValue ID步长 * @return ID生成器 */ - private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) { - if (initValue == null || initValue <= 0) { - initValue = DEFAULT_INIT_VALUE; - } - if (stepValue == null || stepValue <= 0) { - stepValue = DEFAULT_STEP_VALUE; - } + public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) { RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key); + // 初始值和步长不能小于等于0 + initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue; + stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue; // 设置初始值和步长 idGenerator.tryInit(initValue, stepValue); // 设置过期时间 @@ -69,6 +75,17 @@ public class SequenceUtils { return idGenerator; } + /** + * 获取ID生成器 + * + * @param key 业务key + * @param expireTime 过期时间 + * @return ID生成器 + */ + public static RIdGenerator getIdGenerator(String key, Duration expireTime) { + return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); + } + /** * 获取指定业务key的唯一id * @@ -78,32 +95,32 @@ public class SequenceUtils { * @param stepValue ID步长 * @return 唯一id */ - public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) { + public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) { return getIdGenerator(key, expireTime, initValue, stepValue).nextId(); } /** - * 获取指定业务key的唯一id字符串 + * 获取指定业务key的唯一id (ID初始值=1,ID步长=1) * * @param key 业务key * @param expireTime 过期时间 - * @param initValue ID初始值 - * @param stepValue ID步长 * @return 唯一id */ - public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) { - return String.valueOf(nextId(key, expireTime, initValue, stepValue)); + public static long getNextId(String key, Duration expireTime) { + return getIdGenerator(key, expireTime).nextId(); } /** - * 获取指定业务key的唯一id (ID初始值=1,ID步长=1) + * 获取指定业务key的唯一id字符串 * * @param key 业务key * @param expireTime 过期时间 + * @param initValue ID初始值 + * @param stepValue ID步长 * @return 唯一id */ - public static long nextId(String key, Duration expireTime) { - return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); + public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) { + return Convert.toStr(getNextId(key, expireTime, initValue, stepValue)); } /** @@ -113,8 +130,8 @@ public class SequenceUtils { * @param expireTime 过期时间 * @return 唯一id */ - public static String nextIdStr(String key, Duration expireTime) { - return String.valueOf(nextId(key, expireTime)); + public static String getNextIdString(String key, Duration expireTime) { + return Convert.toStr(getNextId(key, expireTime)); } /** @@ -125,56 +142,210 @@ public class SequenceUtils { * @param width 位数,不足左补0 * @return 补零后的唯一id字符串 */ - public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) { - return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0'); + public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) { + return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0'); } /** - * 获取 yyyyMMdd 开头的唯一id + * 获取 yyyyMMdd 格式的唯一id * * @return 唯一id + * @deprecated 请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)},确保不同业务的ID连续性 */ - public static String nextIdDate() { - return nextIdDate(""); + @Deprecated + public static String getDateId() { + return getDateId(""); } /** - * 获取 prefix + yyyyMMdd 开头的唯一id + * 获取 prefix + yyyyMMdd 格式的唯一id * * @param prefix 业务前缀 * @return 唯一id */ - public static String nextIdDate(String prefix) { - // 前缀+日期 构建 prefixKey - String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER)); - // 获取下一个id - long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); - // 返回完整id - return StringUtils.format("{}{}", prefixKey, nextId); + public static String getDateId(String prefix) { + return getDateId(prefix, true); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix) { + return getDateId(prefix, isWithPrefix, -1); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}) + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getPaddedDateId(String prefix, boolean isWithPrefix) { + return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) { + return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now()); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) { + return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); } /** - * 获取 yyyyMMddHHmmss 开头的唯一id + * 获取 prefix + yyyyMMdd 格式的唯一id * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @param initValue ID初始值 + * @param stepValue ID步长 * @return 唯一id */ - public static String nextIdDateTime() { - return nextIdDateTime(""); + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) { + return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue); } /** - * 获取 prefix + yyyyMMddHHmmss 开头的唯一id + * 获取 yyyyMMddHHmmss 格式的唯一id + * + * @return 唯一id + * @deprecated 请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)},确保不同业务的ID连续性 + */ + @Deprecated + public static String getDateTimeId() { + return getDateTimeId("", false); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id * * @param prefix 业务前缀 * @return 唯一id */ - public static String nextIdDateTime(String prefix) { - // 前缀+日期时间 构建 prefixKey - String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER)); - // 获取下一个id - long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); - // 返回完整id - return StringUtils.format("{}{}", prefixKey, nextId); + public static String getDateTimeId(String prefix) { + return getDateTimeId(prefix, true); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix) { + return getDateTimeId(prefix, isWithPrefix, -1); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}) + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) { + return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) { + return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now()); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) { + return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); } + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param initValue ID初始值 + * @param stepValue ID步长 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) { + return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue); + } + + /** + * 获取指定业务key的指定时间格式的ID + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param temporalAccessor 时间访问器 + * @param timeFormatter 时间格式 + * @param expireTime 过期时间 + * @param initValue ID初始值 + * @param stepValue ID步长 + * @return 唯一id + */ + private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) { + // 时间前缀 + String timePrefix = timeFormatter.format(temporalAccessor); + // 业务前缀 + 时间前缀 构建 prefixKey + String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix); + + // 获取id,例 -> 1 + String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue); + + // minIdCapacityBits 大于0,且 nextId 的长度小于 minIdCapacityBits,则左补0 + if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) { + nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0'); + } + + // 是否携带业务前缀 + if (isWithPrefix) { + // 例 -> P202507031 + // 其中 P 为业务前缀,202507031 为 yyyyMMdd 格式时间, 1 为nextId + return StringUtils.format("{}{}", prefixKey, nextId); + } + // 例 -> 202507031 + // 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId + return StringUtils.format("{}{}", timePrefix, nextId); + } } diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java index 46c61c4c8eba006c58c6254d04b9ac17fb1ac145..14abf896a1949b3ffd48f3772d8a966a0a07c779 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java @@ -53,11 +53,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { if (timeout == NEVER_EXPIRE) { RedisUtils.setCacheObject(key, value); } else { - if (RedisUtils.hasKey(key)) { - RedisUtils.setCacheObject(key, value, true); - } else { - RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); - } + RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); } CAFFEINE.invalidate(key); } @@ -78,7 +74,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { */ @Override public void delete(String key) { - RedisUtils.deleteObject(key); + if (RedisUtils.deleteObject(key)) { + CAFFEINE.invalidate(key); + } } /** @@ -134,11 +132,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { if (timeout == NEVER_EXPIRE) { RedisUtils.setCacheObject(key, object); } else { - if (RedisUtils.hasKey(key)) { - RedisUtils.setCacheObject(key, object, true); - } else { - RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); - } + RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); } CAFFEINE.invalidate(key); } @@ -159,7 +153,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { */ @Override public void deleteObject(String key) { - RedisUtils.deleteObject(key); + if (RedisUtils.deleteObject(key)) { + CAFFEINE.invalidate(key); + } } /** diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java index e6b0404f801281a69c156fd9396db94137a46c84..730a14a3e8b7b975580b5ebd8a6139573aca3327 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java @@ -1,6 +1,7 @@ package org.dromara.common.satoken.core.service; import cn.dev33.satoken.stp.StpInterface; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import org.dromara.common.core.enums.UserType; import org.dromara.common.core.exception.ServiceException; @@ -39,8 +40,12 @@ public class SaPermissionImpl implements StpInterface { if (userType == UserType.APP_USER) { // 其他端 自行根据业务编写 } - // SYS_USER 默认返回权限 - return new ArrayList<>(loginUser.getMenuPermission()); + if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) { + // SYS_USER 默认返回权限 + return new ArrayList<>(loginUser.getMenuPermission()); + } else { + return new ArrayList<>(); + } } /** @@ -62,8 +67,12 @@ public class SaPermissionImpl implements StpInterface { if (userType == UserType.APP_USER) { // 其他端 自行根据业务编写 } - // SYS_USER 默认返回权限 - return new ArrayList<>(loginUser.getRolePermission()); + if (CollUtil.isNotEmpty(loginUser.getRolePermission())) { + // SYS_USER 默认返回权限 + return new ArrayList<>(loginUser.getRolePermission()); + } else { + return new ArrayList<>(); + } } private PermissionService getPermissionService() { diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java index 7d2136e6697e1682099be181eb84c4e194cf1bb1..1bb39d4f6fd24d0013b45c51bac38729df0a6e4e 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java @@ -207,7 +207,8 @@ public class LoginHelper { */ public static boolean isLogin() { try { - return getLoginUser() != null; + StpUtil.checkLogin(); + return true; } catch (Exception e) { return false; } diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java index 995dcbd96e4eb39297f3f620366ddb5cd0955ff0..c822898fbe986db029a5da202678e81de9aa0da8 100644 --- a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java @@ -1,5 +1,6 @@ package org.dromara.common.sensitive.core; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.DesensitizedUtil; import lombok.AllArgsConstructor; @@ -52,7 +53,7 @@ public enum SensitiveStrategy { /** * 用户ID */ - USER_ID(s -> String.valueOf(DesensitizedUtil.userId())), + USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())), /** * 密码 diff --git a/ruoyi-common/ruoyi-common-sentinel/pom.xml b/ruoyi-common/ruoyi-common-sentinel/pom.xml deleted file mode 100644 index 3185baf7ae7d751da51ae1c3c238a0f3f2669fc6..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-sentinel/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - org.dromara - ruoyi-common - ${revision} - - 4.0.0 - - ruoyi-common-sentinel - - - ruoyi-common-sentinel 限流模块 - - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-sentinel - - - - - com.alibaba.csp - sentinel-datasource-nacos - - - com.alibaba.csp - sentinel-apache-dubbo3-adapter - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - true - - - - org.dromara - ruoyi-common-core - - - - org.springframework.boot - spring-boot-autoconfigure - - - - diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomAutoConfiguration.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomAutoConfiguration.java deleted file mode 100644 index ad48924aeaec205427f2d26eb9b25f15c6c726bb..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomAutoConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.dromara.common.sentinel.config; - -import com.alibaba.cloud.commons.lang.StringUtils; -import com.alibaba.cloud.sentinel.SentinelProperties; -import com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration; -import com.alibaba.csp.sentinel.init.InitExecutor; -import com.alibaba.csp.sentinel.transport.config.TransportConfig; -import org.dromara.common.core.utils.StreamUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.context.annotation.Bean; - -import java.util.List; - -/** - * @author Lion Li - */ -@AutoConfiguration(before = SentinelAutoConfiguration.class) -@EnableConfigurationProperties({SentinelProperties.class, SentinelCustomProperties.class}) -public class SentinelCustomAutoConfiguration { - - @Autowired - private SentinelProperties properties; - @Autowired - private SentinelCustomProperties customProperties; - @Autowired - private DiscoveryClient discoveryClient; - - @Bean - public Object sentinelInit() { - if (StringUtils.isNotBlank(customProperties.getServerName())) { - List instances = discoveryClient.getInstances(customProperties.getServerName()); - String serverList = StreamUtils.join(instances, instance -> - String.format("http://%s:%s", instance.getHost(), instance.getPort())); - System.setProperty(TransportConfig.CONSOLE_SERVER, serverList); - } else { - if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER)) - && StringUtils.isNotBlank(properties.getTransport().getDashboard())) { - System.setProperty(TransportConfig.CONSOLE_SERVER, - properties.getTransport().getDashboard()); - } - } - // 手动初始化 sentinel - InitExecutor.doInit(); - return new Object(); - } - - -} diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomProperties.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomProperties.java deleted file mode 100644 index 0cd3982c46fa9ab5e9967aca68d166800404a6a7..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/SentinelCustomProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.dromara.common.sentinel.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * sentinel自定义配置类 - * - * @author Lion Li - */ -@Data -@ConfigurationProperties(prefix = "spring.cloud.sentinel.transport") -public class SentinelCustomProperties { - - private String serverName; - -} diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 68693d42760801a0376c247ea0aa5e9639f75c51..0000000000000000000000000000000000000000 --- a/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -org.dromara.common.sentinel.config.SentinelCustomAutoConfiguration diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java index e8f401e9fb94b97cbcc5c9749993b31a19b32ed8..59de70a576653f8976decff57dbb30e9d7dd9ba8 100644 --- a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java @@ -14,6 +14,9 @@ import org.dromara.common.social.gitea.AuthGiteaRequest; import org.dromara.common.social.maxkey.AuthMaxKeyRequest; import org.dromara.common.social.topiam.AuthTopIamRequest; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + /** * 认证授权工具类 * @@ -40,7 +43,7 @@ public class SocialUtils { AuthConfig.AuthConfigBuilder builder = AuthConfig.builder() .clientId(obj.getClientId()) .clientSecret(obj.getClientSecret()) - .redirectUri(obj.getRedirectUri()) + .redirectUri(URLEncoder.encode(obj.getRedirectUri(), StandardCharsets.UTF_8)) .scopes(obj.getScopes()); return switch (source.toLowerCase()) { case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE); diff --git a/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java b/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java index a11b5bb5165fb546839b00c226e5aa7d9872e550..b1c3e272c6b27a42da8dac833b8f6c206fa04f67 100644 --- a/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java +++ b/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java @@ -5,7 +5,6 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.sse.core.SseEmitterManager; -import org.dromara.common.sse.dto.SseMessageDto; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; @@ -13,8 +12,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.util.List; - /** * SSE 控制器 * @@ -32,7 +29,9 @@ public class SseController implements DisposableBean { */ @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter connect() { - StpUtil.checkLogin(); + if (!StpUtil.isLogin()) { + return null; + } String tokenValue = StpUtil.getTokenValue(); Long userId = LoginHelper.getUserId(); return sseEmitterManager.connect(userId, tokenValue); @@ -49,31 +48,32 @@ public class SseController implements DisposableBean { return R.ok(); } - /** - * 向特定用户发送消息 - * - * @param userId 目标用户的 ID - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/send") - public R send(Long userId, String msg) { - SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(List.of(userId)); - dto.setMessage(msg); - sseEmitterManager.publishMessage(dto); - return R.ok(); - } - - /** - * 向所有用户发送消息 - * - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/sendAll") - public R send(String msg) { - sseEmitterManager.publishAll(msg); - return R.ok(); - } + // 以下为demo仅供参考 禁止使用 请在业务逻辑中使用工具发送而不是用接口发送 +// /** +// * 向特定用户发送消息 +// * +// * @param userId 目标用户的 ID +// * @param msg 要发送的消息内容 +// */ +// @GetMapping(value = "${sse.path}/send") +// public R send(Long userId, String msg) { +// SseMessageDto dto = new SseMessageDto(); +// dto.setUserIds(List.of(userId)); +// dto.setMessage(msg); +// sseEmitterManager.publishMessage(dto); +// return R.ok(); +// } +// +// /** +// * 向所有用户发送消息 +// * +// * @param msg 要发送的消息内容 +// */ +// @GetMapping(value = "${sse.path}/sendAll") +// public R send(String msg) { +// sseEmitterManager.publishAll(msg); +// return R.ok(); +// } /** * 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误 diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java index 1faf5931384a874860ad3ec750ca9535afcba302..52f946ea16f7900107cb70d8845e9ae6afc5021b 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java @@ -6,6 +6,7 @@ import org.dromara.common.web.filter.XssFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -20,14 +21,14 @@ public class FilterConfig { @Bean @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") - public FilterRegistrationBean xssFilterRegistration() { - FilterRegistrationBean registration = new FilterRegistrationBean<>(); - registration.setDispatcherTypes(DispatcherType.REQUEST); - registration.setFilter(new XssFilter()); - registration.addUrlPatterns("/*"); - registration.setName("xssFilter"); - registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1); - return registration; + @FilterRegistration( + name = "xssFilter", + urlPatterns = "/*", + order = FilterRegistrationBean.HIGHEST_PRECEDENCE + 1, + dispatcherTypes = DispatcherType.REQUEST + ) + public XssFilter xssFilter() { + return new XssFilter(); } } diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java index 8dbb589dc0185798c816a1a8c2d9e1b24008300e..13ee3759da5ddcf97cae5d5f7771c5259a3e2681 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java @@ -1,6 +1,8 @@ package org.dromara.common.web.config; +import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.web.handler.GlobalExceptionHandler; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -29,10 +31,11 @@ public class ResourcesConfig implements WebMvcConfigurer { public void addFormatters(FormatterRegistry registry) { // 全局日期格式转换配置 registry.addConverter(String.class, Date.class, source -> { - if (StringUtils.isBlank(source)) { + DateTime parse = DateUtil.parse(source); + if (ObjectUtils.isNull(parse)) { return null; } - return DateUtil.parse(source); + return parse.toJdkDate(); }); } diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/ip2region.xdb b/ruoyi-common/ruoyi-common-web/src/main/resources/ip2region.xdb index 7052c057104ddd0c57f3ec9a8a457e4ec8f7139d..6f86c7d9b5ea27ecbe808e2be198bac1aaee0b92 100644 Binary files a/ruoyi-common/ruoyi-common-web/src/main/resources/ip2region.xdb and b/ruoyi-common/ruoyi-common-web/src/main/resources/ip2region.xdb differ diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml index 89eaa97ec1c3a44aadbea91e1f484db2de7fdff4..5ebdb9813933e2a1258e588b81074515dc905bd2 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml +++ b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml @@ -29,7 +29,7 @@ - ${log.path}/info.%d{yyyy-MM-dd}.log + ${log.path}/info.%d{yyyy-MM-dd}.log.gz 60 @@ -51,7 +51,7 @@ - ${log.path}/error.%d{yyyy-MM-dd}.log + ${log.path}/error.%d{yyyy-MM-dd}.log.gz 60 diff --git a/ruoyi-example/ruoyi-demo/pom.xml b/ruoyi-example/ruoyi-demo/pom.xml index 9fca13693eeaa47f8fc1e67315dbca13d72443de..d6ed90eff384dd21b1c1085f2b37b3b1d9623b30 100644 --- a/ruoyi-example/ruoyi-demo/pom.xml +++ b/ruoyi-example/ruoyi-demo/pom.xml @@ -22,11 +22,6 @@ ruoyi-common-nacos - - org.dromara - ruoyi-common-sentinel - - org.dromara ruoyi-common-log diff --git a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java similarity index 77% rename from ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java rename to ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java index ac60723de3366414f4b33f00f2e1afdcb38083e5..3c7a7b31125ca0aa8e3ea725ac959a8675a5c9a4 100644 --- a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java +++ b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java @@ -21,7 +21,7 @@ import java.util.Arrays; @RequiredArgsConstructor @RestController @RequestMapping("/mail") -public class MailController { +public class MailSendController { /** * 发送邮件 @@ -39,14 +39,14 @@ public class MailController { /** * 发送邮件(带附件) * - * @param to 接收人 - * @param subject 标题 - * @param text 内容 - * @param filePath 附件路径 + * @param to 接收人 + * @param subject 标题 + * @param text 内容 */ @GetMapping("/sendMessageWithAttachment") - public R sendMessageWithAttachment(String to, String subject, String text, String filePath) { - MailUtils.sendText(to, subject, text, new File(filePath)); + public R sendMessageWithAttachment(String to, String subject, String text) { + // 附件路径 禁止前端传递 有任意读取系统文件风险 + MailUtils.sendText(to, subject, text, new File("/xxx/xxx")); return R.ok(); } @@ -56,10 +56,11 @@ public class MailController { * @param to 接收人 * @param subject 标题 * @param text 内容 - * @param paths 附件路径 */ @GetMapping("/sendMessageWithAttachments") - public R sendMessageWithAttachments(String to, String subject, String text, String[] paths) { + public R sendMessageWithAttachments(String to, String subject, String text) { + // 附件路径 禁止前端传递 有任意读取系统文件风险 + String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"}; File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new); MailUtils.sendText(to, subject, text, array); return R.ok(); diff --git a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java index d82ea3b0624cbd9a2d240b5f230e122e1e378fc9..3747da4d1480a95845c70a6fa01867996c29c361 100644 --- a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java +++ b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java @@ -14,6 +14,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -94,6 +95,16 @@ public class TestExcelController { exportExcelService.exportWithOptions(response); } + /** + * 自定义导出 + * + * @param response / + */ + @GetMapping("/customExport") + public void customExport(HttpServletResponse response) throws IOException { + exportExcelService.customExport(response); + } + /** * 多个sheet导出 */ diff --git a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java index 4dfa5effa2ba3af3b2ad4e6cc64e97b2310f5dff..ad2392b979a3f6fe2d2f6574b5f59494aed9c2f7 100644 --- a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java +++ b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java @@ -2,6 +2,8 @@ package org.dromara.demo.service; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * 导出下拉框Excel示例 * @@ -15,4 +17,11 @@ public interface IExportExcelService { * @param response / */ void exportWithOptions(HttpServletResponse response); + + /** + * 自定义导出 + * + * @param response / + */ + void customExport(HttpServletResponse response) throws IOException; } diff --git a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java index ebb6ac68848b207d941a6c8bca56478415ed67f4..cbc627668caafb03a77639f64ec466a61af52114 100644 --- a/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java +++ b/ruoyi-example/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java @@ -2,17 +2,22 @@ package org.dromara.demo.service.impl; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.idev.excel.write.metadata.WriteSheet; import jakarta.servlet.http.HttpServletResponse; import lombok.Data; import lombok.RequiredArgsConstructor; +import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.enums.UserStatus; import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.excel.core.DropDownOptions; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelWriterWrapper; import org.dromara.demo.domain.vo.ExportDemoVo; import org.dromara.demo.service.IExportExcelService; import org.springframework.stereotype.Service; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -233,4 +238,60 @@ public class ExportExcelServiceImpl implements IExportExcelService { this.name = name; } } + + @Override + public void customExport(HttpServletResponse response) throws IOException { + String filename = ExcelUtil.encodingFilename("自定义导出"); + FileUtils.setAttachmentResponseHeader(response, filename); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); + + ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> { + // 创建表格数据,业务中一般通过数据库查询 + List excelDataList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + // 模拟数据库中的一条数据 + ExportDemoVo everyRowData = new ExportDemoVo(); + everyRowData.setNickName("用户-" + i); + everyRowData.setUserStatus(SystemConstants.NORMAL); + everyRowData.setGender("1"); + everyRowData.setPhoneNumber(String.format("175%08d", i)); + everyRowData.setEmail(String.format("175%08d", i) + "@163.com"); + everyRowData.setProvinceId(i); + everyRowData.setCityId(i); + everyRowData.setAreaId(i); + excelDataList.add(everyRowData); + } + + // 创建表格 + WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("自定义导出demo") + // 合并单元格 + // .registerWriteHandler(new CellMergeStrategy(excelDataList, true)) + .build(); + + + wrapper.write(excelDataList, sheet); + + List excelDataList2 = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + int index = 1000 + i; + // 模拟数据库中的一条数据 + ExportDemoVo everyRowData = new ExportDemoVo(); + everyRowData.setNickName("用户-" + index); + everyRowData.setUserStatus(SystemConstants.NORMAL); + everyRowData.setGender("1"); + everyRowData.setPhoneNumber(String.format("175%08d", index)); + everyRowData.setEmail(String.format("175%08d", index) + "@163.com"); + everyRowData.setProvinceId(index); + everyRowData.setCityId(index); + everyRowData.setAreaId(index); + excelDataList2.add(everyRowData); + } + + wrapper.write(excelDataList2, sheet); + + // 或者在同一个excel中创建多个表格 + // WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("自定义导出demo2").build(); + // wrapper.write(excelDataList2, sheet2); + }); + } } diff --git a/ruoyi-example/ruoyi-test-mq/pom.xml b/ruoyi-example/ruoyi-test-mq/pom.xml index dc29d327d88f7fd402f24b59bbcc0444f262aed7..98d7ebc2aeac4950f8c7f3c036d0b38642c0f5ee 100644 --- a/ruoyi-example/ruoyi-test-mq/pom.xml +++ b/ruoyi-example/ruoyi-test-mq/pom.xml @@ -34,11 +34,6 @@ spring-kafka - - org.dromara - ruoyi-common-sentinel - - org.dromara ruoyi-common-security diff --git a/ruoyi-gateway/Dockerfile b/ruoyi-gateway/Dockerfile index 01eeb0378e872130293b99da9e645d3ce6ffc735..576d9955a543aaba4e8f886b891a7961dbce0398 100644 --- a/ruoyi-gateway/Dockerfile +++ b/ruoyi-gateway/Dockerfile @@ -1,6 +1,6 @@ # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ -FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds -#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds +FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds +#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds #FROM findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-gateway/pom.xml b/ruoyi-gateway/pom.xml index 0f287d85704b27d5948e509665c6d9e1ce51ddb8..de58ac0ba056f5ab8d2dd17b1f9db094a339a404 100644 --- a/ruoyi-gateway/pom.xml +++ b/ruoyi-gateway/pom.xml @@ -19,7 +19,7 @@ org.springframework.cloud - spring-cloud-starter-gateway + spring-cloud-starter-gateway-server-webflux @@ -37,12 +37,6 @@ ruoyi-common-nacos - - - com.alibaba.cloud - spring-cloud-alibaba-sentinel-gateway - - org.springframework.boot @@ -61,17 +55,6 @@ ${satoken.version} - - org.dromara - ruoyi-common-sentinel - - - com.alibaba.csp - sentinel-apache-dubbo3-adapter - - - - org.dromara ruoyi-common-satoken diff --git a/ruoyi-gateway/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java b/ruoyi-gateway/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java deleted file mode 100644 index 409d4f00e4954239820a592065323ca57d215dcf..0000000000000000000000000000000000000000 --- a/ruoyi-gateway/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 1999-2019 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; - -import org.springframework.http.HttpStatus; -import org.springframework.http.InvalidMediaTypeException; -import org.springframework.http.MediaType; -import org.springframework.util.MimeTypeUtils; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -import java.util.List; - -import static org.springframework.web.reactive.function.BodyInserters.fromObject; - -// https://github.com/alibaba/Sentinel/issues/3298 -// 临时解决 sentinel 限流插件 jdk17 报错问题 -/** - * The default implementation of {@link BlockRequestHandler}. - * Compatible with Spring WebFlux and Spring Cloud Gateway. - * - * @author Eric Zhao - */ -public class DefaultBlockRequestHandler implements BlockRequestHandler { - - private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: "; - - @Override - public Mono handleRequest(ServerWebExchange exchange, Throwable ex) { - if (acceptsHtml(exchange)) { - return htmlErrorResponse(ex); - } - // JSON result by default. - return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) - .contentType(MediaType.APPLICATION_JSON_UTF8) - .body(fromObject(buildErrorResult(ex))); - } - - private Mono htmlErrorResponse(Throwable ex) { - return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) - .contentType(MediaType.TEXT_PLAIN) - .syncBody(DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); - } - - private ErrorResult buildErrorResult(Throwable ex) { - return new ErrorResult(HttpStatus.TOO_MANY_REQUESTS.value(), - DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); - } - - /** - * Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot. - */ - private boolean acceptsHtml(ServerWebExchange exchange) { - try { - List acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept(); - acceptedMediaTypes.remove(MediaType.ALL); - MimeTypeUtils. sortBySpecificity(acceptedMediaTypes); - return acceptedMediaTypes.stream() - .anyMatch(MediaType.TEXT_HTML::isCompatibleWith); - } catch (InvalidMediaTypeException ex) { - return false; - } - } - - private static class ErrorResult { - private final int code; - private final String message; - - ErrorResult(int code, String message) { - this.code = code; - this.message = message; - } - - public int getCode() { - return code; - } - - public String getMessage() { - return message; - } - } -} diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java index 4f644533d09f3d6f9faf902126d27e0ec29b8c1c..a20dfe450add05ad0b03a8eaeeb10077e4d43387 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java @@ -13,8 +13,6 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class RuoYiGatewayApplication { public static void main(String[] args) { - // 标记 sentinel 类型为 网关 - System.setProperty("csp.sentinel.app.type", "1"); SpringApplication application = new SpringApplication(RuoYiGatewayApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java index d3b40dd4d8d7f0cb4bf7e5e2e9e755394db3c6e8..99211d9a25211cb2454b2e4cfef5bd4a1a5b486f 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java @@ -1,10 +1,6 @@ package org.dromara.gateway.config; -import org.dromara.gateway.handler.SentinelFallbackHandler; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; /** * 网关限流配置 @@ -13,9 +9,5 @@ import org.springframework.core.annotation.Order; */ @Configuration public class GatewayConfig { - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - public SentinelFallbackHandler sentinelGatewayExceptionHandler() { - return new SentinelFallbackHandler(); - } + } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java deleted file mode 100644 index 0aec08826c8a276b57c1a7b75c73ac95f7827628..0000000000000000000000000000000000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.dromara.gateway.handler; - -import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import org.dromara.gateway.utils.WebFluxUtils; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebExceptionHandler; -import reactor.core.publisher.Mono; - -/** - * 自定义限流异常处理 - * - * @author ruoyi - */ -public class SentinelFallbackHandler implements WebExceptionHandler { - private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) { - return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试"); - } - - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - ex.printStackTrace(); - if (exchange.getResponse().isCommitted()) { - return Mono.error(ex); - } - if (!BlockException.isBlockException(ex)) { - return Mono.error(ex); - } - return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); - } - - private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { - return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); - } -} diff --git a/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java b/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..45bf5e01672530fb44cc7899516b3db36599db1b --- /dev/null +++ b/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.filter.factory; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.List; + +import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; + +/** + * This filter removes the first part of the path, known as the prefix, from the request + * before sending it downstream. + * + * @author Ryan Baxter + */ +public class StripPrefixGatewayFilterFactory + extends AbstractGatewayFilterFactory { + + /** + * Parts key. + */ + public static final String PARTS_KEY = "parts"; + + public StripPrefixGatewayFilterFactory() { + super(Config.class); + } + + @Override + public List shortcutFieldOrder() { + return Arrays.asList(PARTS_KEY); + } + + @Override + public GatewayFilter apply(Config config) { + return new GatewayFilter() { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + addOriginalRequestUrl(exchange, request.getURI()); + String path = request.getURI().getRawPath(); + String[] originalParts = StringUtils.tokenizeToStringArray(path, "/"); + + // all new paths start with / + StringBuilder newPath = new StringBuilder("/"); + for (int i = 0; i < originalParts.length; i++) { + if (i >= config.getParts()) { + // only append slash if this is the second part or greater + if (newPath.length() > 1) { + newPath.append('/'); + } + newPath.append(originalParts[i]); + } + } + if (newPath.length() > 1 && path.endsWith("/")) { + newPath.append('/'); + } + // 增加doc前缀传递 + String prefix = "/" + originalParts[config.getParts() - 1]; + + ServerHttpRequest newRequest = request.mutate() + .header("X-Forwarded-Prefix", prefix) + .path(newPath.toString()) + .build(); + + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI()); + + return chain.filter(exchange.mutate().request(newRequest).build()); + } + + @Override + public String toString() { + return filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()) + .toString(); + } + }; + } + + public static class Config { + + private int parts = 1; + + public int getParts() { + return parts; + } + + public void setParts(int parts) { + this.parts = parts; + } + + } + +} diff --git a/ruoyi-modules/ruoyi-gen/Dockerfile b/ruoyi-modules/ruoyi-gen/Dockerfile index 4427cc22638365f36bed442ebd97c690cbea9970..5e8b6f7430c947f64e3edf450bce4f7e418ce1ef 100644 --- a/ruoyi-modules/ruoyi-gen/Dockerfile +++ b/ruoyi-modules/ruoyi-gen/Dockerfile @@ -1,6 +1,6 @@ # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ -FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds -#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds +FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds +#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds #FROM findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-modules/ruoyi-gen/pom.xml b/ruoyi-modules/ruoyi-gen/pom.xml index 997aaa99b32cd47322680f578da74b2443f45df7..316848d537fd122234f298756208234e5cecc240 100644 --- a/ruoyi-modules/ruoyi-gen/pom.xml +++ b/ruoyi-modules/ruoyi-gen/pom.xml @@ -34,6 +34,11 @@ ruoyi-common-log + + org.dromara + ruoyi-common-idempotent + + org.dromara ruoyi-common-doc diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/controller/GenController.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/controller/GenController.java index 160374945e9fffdb40e732d1e8e3444cb53e8912..06610ce2f0544b8fda9d152ce1a2caa041daef9f 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/controller/GenController.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/controller/GenController.java @@ -3,9 +3,11 @@ package org.dromara.gen.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; +import com.baomidou.lock.annotation.Lock4j; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -50,6 +52,7 @@ public class GenController extends BaseController { * * @param tableId 表ID */ + @RepeatSubmit() @SaCheckPermission("tool:gen:query") @GetMapping(value = "/{tableId}") public R> getInfo(@PathVariable Long tableId) { @@ -89,6 +92,7 @@ public class GenController extends BaseController { * * @param tables 表名串 */ + @RepeatSubmit() @SaCheckPermission("tool:gen:import") @Log(title = "代码生成", businessType = BusinessType.IMPORT) @PostMapping("/importTable") @@ -103,6 +107,7 @@ public class GenController extends BaseController { /** * 修改保存代码生成业务 */ + @RepeatSubmit() @SaCheckPermission("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) @PutMapping @@ -170,6 +175,7 @@ public class GenController extends BaseController { */ @SaCheckPermission("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @Lock4j @GetMapping("/synchDb/{tableId}") public R synchDb(@PathVariable("tableId") Long tableId) { genTableService.synchDb(tableId); diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java index 569b2683d015daed3d79a060ade00dac675e8fba..3aa61c2f6a6eff50e2f8ef08d84b0a4be9cdefdc 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java @@ -302,7 +302,7 @@ public class GenTableServiceImpl implements IGenTableService { tableColumn.setColumnComment(column.getComment()); tableColumn.setColumnType(column.getOriginType().toLowerCase()); tableColumn.setSort(column.getPosition()); - tableColumn.setIsRequired(column.isNullable() ? "1" : "0"); + tableColumn.setIsRequired(column.isNullable() ? "0" : "1"); tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0"); tableColumns.add(tableColumn); }); diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/VelocityUtils.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/VelocityUtils.java index 61cd55e9fb3133ce0c3a73972ca34121c577db53..5f4eecc71207e6e84bdf6207ac98e098bb2cacd1 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/VelocityUtils.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/VelocityUtils.java @@ -9,6 +9,7 @@ import org.apache.velocity.VelocityContext; import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.mybatis.enums.DataBaseType; import org.dromara.common.mybatis.helper.DataBaseHelper; import org.dromara.gen.constant.GenConstants; import org.dromara.gen.domain.GenTable; @@ -118,10 +119,13 @@ public class VelocityUtils { templates.add("vm/java/serviceImpl.java.vm"); templates.add("vm/java/controller.java.vm"); templates.add("vm/xml/mapper.xml.vm"); - if (DataBaseHelper.isOracle()) { + DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(); + if (dataBaseType.isOracle()) { templates.add("vm/sql/oracle/sql.vm"); - } else if (DataBaseHelper.isPostgerSql()) { + } else if (dataBaseType.isPostgreSql()) { templates.add("vm/sql/postgres/sql.vm"); + } else if (dataBaseType.isSqlServer()) { + templates.add("vm/sql/sqlserver/sql.vm"); } else { templates.add("vm/sql/sql.vm"); } diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/ts/types.ts.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/ts/types.ts.vm index 35a468e801a2d6620adf83d39bdc3e831511c042..2b87e625e012a8594910bb35bd4e0951f847a051 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/ts/types.ts.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/ts/types.ts.vm @@ -54,10 +54,10 @@ export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{ #end #end #end - /** - * 日期范围参数 - */ - params?: any; + /** + * 日期范围参数 + */ + params?: any; } diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm index 4570d46db21e57ef58ac3081c4b896f362c87ea7..fa4e05f63eeace8fedf8d5b4a1b8636cf6527b3a 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm @@ -123,7 +123,7 @@ #end #end #end - +