Przeglądaj źródła

!826 发布 5.5.3 版本 提前祝大家新年快乐
Merge pull request !826 from 疯狂的狮子Li/dev

疯狂的狮子Li 5 miesięcy temu
rodzic
commit
91ba3869e7
37 zmienionych plików z 383 dodań i 383 usunięć
  1. 1 1
      .run/ruoyi-monitor-admin.run.xml
  2. 1 1
      .run/ruoyi-server.run.xml
  3. 1 1
      .run/ruoyi-snailjob-server.run.xml
  4. 3 5
      README.md
  5. 11 11
      pom.xml
  6. 12 9
      ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
  7. 1 3
      ruoyi-admin/src/main/resources/application.yml
  8. 3 3
      ruoyi-admin/src/main/resources/i18n/messages.properties
  9. 3 3
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  10. 1 1
      ruoyi-common/ruoyi-common-bom/pom.xml
  11. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java
  12. 14 18
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
  13. 5 5
      ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
  14. 3 3
      ruoyi-common/ruoyi-common-oss/pom.xml
  15. 39 28
      ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
  16. 0 49
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java
  17. 1 8
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
  18. 197 0
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java
  19. 0 35
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaCategory.java
  20. 0 29
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java
  21. 0 146
      ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/common/register/ServerRegister.java
  22. 4 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java
  23. 5 5
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java
  24. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java
  25. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
  26. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java
  27. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java
  28. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
  29. 9 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java
  30. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
  31. 51 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java
  32. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
  33. 4 4
      script/docker/docker-compose.yml
  34. 1 1
      script/sql/oracle/oracle_ry_vue_5.X.sql
  35. 1 1
      script/sql/postgres/postgres_ry_vue_5.X.sql
  36. 1 1
      script/sql/ry_vue_5.X.sql
  37. 1 1
      script/sql/sqlserver/sqlserver_ry_vue_5.X.sql

+ 1 - 1
.run/ruoyi-monitor-admin.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.2" />
+        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.3" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-server.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-server:5.5.2" />
+        <option name="imageTag" value="ruoyi/ruoyi-server:5.5.3" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-snailjob-server.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.2" />
+        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.3" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
       </settings>

+ 3 - 5
README.md

@@ -8,10 +8,9 @@
 [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
 [![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
 [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
-[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 <br>
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.5.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
-[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.5.3-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-blue.svg)]()
 [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
 [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
 
@@ -33,9 +32,8 @@
 
 MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
 CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
-数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
+数舵科技 软件定制开发APP小程序等 - https://www.shuduokeji.com/ <br>
 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
-<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
 Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
 aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
 Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>

+ 11 - 11
pom.xml

@@ -13,32 +13,32 @@
     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 
     <properties>
-        <revision>5.5.2</revision>
-        <spring-boot.version>3.5.9</spring-boot.version>
+        <revision>5.5.3</revision>
+        <spring-boot.version>3.5.10</spring-boot.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>17</java.version>
-        <mybatis.version>3.5.16</mybatis.version>
-        <springdoc.version>2.8.14</springdoc.version>
+        <mybatis.version>3.5.19</mybatis.version>
+        <springdoc.version>2.8.15</springdoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
         <fastexcel.version>1.3.0</fastexcel.version>
         <velocity.version>2.3</velocity.version>
         <satoken.version>1.44.0</satoken.version>
-        <mybatis-plus.version>3.5.14</mybatis-plus.version>
+        <mybatis-plus.version>3.5.16</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
-        <hutool.version>5.8.40</hutool.version>
-        <spring-boot-admin.version>3.5.5</spring-boot-admin.version>
+        <hutool.version>5.8.43</hutool.version>
+        <spring-boot-admin.version>3.5.6</spring-boot-admin.version>
         <redisson.version>3.52.0</redisson.version>
         <lock4j.version>2.2.7</lock4j.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
         <snailjob.version>1.9.0</snailjob.version>
         <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
-        <lombok.version>1.18.40</lombok.version>
+        <lombok.version>1.18.42</lombok.version>
         <bouncycastle.version>1.80</bouncycastle.version>
         <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
-        <ip2region.version>3.3.1</ip2region.version>
+        <ip2region.version>3.3.2</ip2region.version>
         <!-- OSS 配置 -->
         <aws.sdk.version>2.28.22</aws.sdk.version>
         <!-- SMS 配置 -->
@@ -226,13 +226,13 @@
                 <artifactId>s3</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
-            <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+            <!-- 客户端的性能增强传输管理器 -->
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>s3-transfer-manager</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
-            <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+            <!-- 适用于 Netty 的客户端 -->
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>netty-nio-client</artifactId>

+ 12 - 9
ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java

@@ -1,8 +1,9 @@
 package org.dromara.web.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
-import cn.hutool.captcha.AbstractCaptcha;
 import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.MathGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.RandomUtil;
 import jakarta.validation.constraints.NotBlank;
@@ -14,14 +15,13 @@ import org.dromara.common.core.domain.R;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.core.utils.reflect.ReflectUtils;
 import org.dromara.common.mail.config.properties.MailProperties;
 import org.dromara.common.mail.utils.MailUtils;
 import org.dromara.common.ratelimiter.annotation.RateLimiter;
 import org.dromara.common.ratelimiter.enums.LimitType;
 import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.web.core.WaveAndCircleCaptcha;
 import org.dromara.common.web.config.properties.CaptchaProperties;
-import org.dromara.common.web.enums.CaptchaType;
 import org.dromara.sms4j.api.SmsBlend;
 import org.dromara.sms4j.api.entity.SmsResponse;
 import org.dromara.sms4j.core.factory.SmsFactory;
@@ -33,6 +33,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.awt.*;
 import java.time.Duration;
 import java.util.LinkedHashMap;
 
@@ -130,19 +131,21 @@ public class CaptchaController {
         String uuid = IdUtil.simpleUUID();
         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
         // 生成验证码
-        CaptchaType captchaType = captchaProperties.getType();
+        String captchaType = captchaProperties.getType();
         CodeGenerator codeGenerator;
-        if (CaptchaType.MATH == captchaType) {
-            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
+        if ("math".equals(captchaType)) {
+            codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
         } else {
-            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
+            codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
         }
-        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
+        // captcha.setBackground(Color.WHITE); // 不设置就是透明底
+        captcha.setFont(new Font("Arial", Font.BOLD, 45));
         captcha.setGenerator(codeGenerator);
         captcha.createCode();
         // 如果是数学验证码,使用SpEL表达式处理验证码结果
         String code = captcha.getCode();
-        if (CaptchaType.MATH == captchaType) {
+        if ("math".equals(captchaType)) {
             ExpressionParser parser = new SpelExpressionParser();
             Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
             code = exp.getValue(String.class);

+ 1 - 3
ruoyi-admin/src/main/resources/application.yml

@@ -24,9 +24,7 @@ captcha:
   # 是否启用验证码校验
   enable: true
   # 验证码类型 math 数组计算 char 字符验证
-  type: MATH
-  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
-  category: CIRCLE
+  type: math
   # 数字验证码位数
   numberLength: 1
   # 字符验证码长度

+ 3 - 3
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
 user.not.exists=对不起, 您的账号:{0} 不存在.
 user.password.not.match=用户不存在/密码错误
 user.password.retry.limit.count=密码输入错误{0}次
-user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
+user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
 user.password.delete=对不起,您的账号:{0} 已被删除
 user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 role.blocked=角色已封禁,请联系管理员
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
 rate.limiter.message=访问过于频繁,请稍候再试
 sms.code.not.blank=短信验证码不能为空
 sms.code.retry.limit.count=短信验证码输入错误{0}次
-sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
 email.code.not.blank=邮箱验证码不能为空
 email.code.retry.limit.count=邮箱验证码输入错误{0}次
-email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
 xcx.code.not.blank=小程序[code]不能为空
 social.source.not.blank=第三方登录平台[source]不能为空
 social.code.not.blank=第三方登录平台[code]不能为空

+ 3 - 3
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
 user.not.exists=对不起, 您的账号:{0} 不存在.
 user.password.not.match=用户不存在/密码错误
 user.password.retry.limit.count=密码输入错误{0}次
-user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
+user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
 user.password.delete=对不起,您的账号:{0} 已被删除
 user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 role.blocked=角色已封禁,请联系管理员
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
 rate.limiter.message=访问过于频繁,请稍候再试
 sms.code.not.blank=短信验证码不能为空
 sms.code.retry.limit.count=短信验证码输入错误{0}次
-sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
 email.code.not.blank=邮箱验证码不能为空
 email.code.retry.limit.count=邮箱验证码输入错误{0}次
-email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
 xcx.code.not.blank=小程序[code]不能为空
 social.source.not.blank=第三方登录平台[source]不能为空
 social.code.not.blank=第三方登录平台[code]不能为空

+ 1 - 1
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -14,7 +14,7 @@
     </description>
 
     <properties>
-        <revision>5.5.2</revision>
+        <revision>5.5.3</revision>
     </properties>
 
     <dependencyManagement>

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java

@@ -61,7 +61,7 @@ public class UserDTO implements Serializable {
     private String sex;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
     private String status;
 

+ 14 - 18
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java

@@ -1,6 +1,5 @@
 package org.dromara.common.core.utils.ip;
 
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.resource.ResourceUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.ServiceException;
@@ -9,7 +8,6 @@ import org.lionsoul.ip2region.service.Config;
 import org.lionsoul.ip2region.service.Ip2Region;
 import org.lionsoul.ip2region.xdb.Util;
 
-import java.io.File;
 import java.io.InputStream;
 import java.time.Duration;
 
@@ -31,6 +29,11 @@ public class RegionUtils {
     // 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
     public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
 
+    // 默认缓存切片大小为15MB(仅针对BufferCache全量读取有效,如果你的xdb数据库很大,合理设置该值可以有效提升BufferCache模式下的查询效率,具体可以查看Ip2Region的README)
+    // 注意:设置过大的值可能会申请内存时,因内存不足而导致OOM,请合理设置该值。
+    // README:https://gitee.com/lionsoul/ip2region/tree/master/binding/java
+    public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
+
     // 未知地址
     public static final String UNKNOWN_ADDRESS = "未知";
 
@@ -43,20 +46,18 @@ public class RegionUtils {
             // 注意:Ip2Region 的xdb文件加载策略 CachePolicy 有三种,分别是:BufferCache(全量读取xdb到内存中)、VIndexCache(默认策略,按需读取并缓存)、NoCache(实时读取)
             // 本项目工具使用的 CachePolicy 为 BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
             // 因为加载整个xdb文件会耗费非常大的内存,如果你不希望加载整个xdb到内存中,更推荐使用 VIndexCache 或 NoCache(即实时读取文件)策略和 setXdbPath/setXdbFile 加载方法(需要注意的一点,setXdbPath 和 setXdbFile 不支持读取ClassPath(即源码和resource目录)中的文件)。
-            // 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 NoCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
+            // 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 VIndexCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
 
-            // TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM,这里先用临时文件的方案解决,等后续 Ip2Region 更新解决方案
-            // 创建临时文件
-            File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
+            InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
 
             // IPv4配置
             Config v4Config = Config.custom()
                 .setCachePolicy(Config.BufferCache)
-                .setXdbFile(v4TempXdb)
-//                .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
+                //.setXdbFile(v4TempXdb)
+                .setXdbInputStream(v4InputStream)
+                //
+                .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
                 .asV4();
-            // 删除临时文件
-            v4TempXdb.delete();
 
             // IPv6配置
             Config v6Config = null;
@@ -64,17 +65,12 @@ public class RegionUtils {
             if (v6XdbInputStream == null) {
                 log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
             } else {
-                // 创建临时文件
-                File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
-
                 v6Config = Config.custom()
                     .setCachePolicy(Config.BufferCache)
-                    .setXdbFile(v6TempXdb)
-//                    .setXdbInputStream(v6XdbInputStream)
+                    //.setXdbFile(v6TempXdb)
+                    .setXdbInputStream(v6XdbInputStream)
+                    .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
                     .asV6();
-
-                // 删除临时文件
-                v6TempXdb.delete();
             }
 
             // 初始化Ip2Region实例

+ 5 - 5
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java

@@ -192,7 +192,7 @@ public class MailUtils {
     /**
      * 发送邮件给多人
      *
-     * @param mailAccount 邮件户信息
+     * @param mailAccount 邮件户信息
      * @param tos         收件人列表
      * @param subject     标题
      * @param content     正文
@@ -207,7 +207,7 @@ public class MailUtils {
     /**
      * 发送邮件给多人
      *
-     * @param mailAccount 邮件户信息
+     * @param mailAccount 邮件户信息
      * @param tos         收件人列表
      * @param ccs         抄送人列表,可以为null或空
      * @param bccs        密送人列表,可以为null或空
@@ -343,7 +343,7 @@ public class MailUtils {
     /**
      * 发送邮件给多人
      *
-     * @param mailAccount 邮件户信息
+     * @param mailAccount 邮件户信息
      * @param tos         收件人列表
      * @param subject     标题
      * @param content     正文
@@ -360,7 +360,7 @@ public class MailUtils {
     /**
      * 发送邮件给多人
      *
-     * @param mailAccount 邮件户信息
+     * @param mailAccount 邮件户信息
      * @param tos         收件人列表
      * @param ccs         抄送人列表,可以为null或空
      * @param bccs        密送人列表,可以为null或空
@@ -400,7 +400,7 @@ public class MailUtils {
     /**
      * 发送邮件给多人
      *
-     * @param mailAccount      邮件户信息
+     * @param mailAccount      邮件户信息
      * @param useGlobalSession 是否全局共享Session
      * @param tos              收件人列表
      * @param ccs              抄送人列表,可以为null或空

+ 3 - 3
ruoyi-common/ruoyi-common-oss/pom.xml

@@ -31,7 +31,7 @@
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>s3</artifactId>
             <exclusions>
-                <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
+                <!-- 东西 30M 特别大的 jar 包 性能跟 Netty 差不多 有需要可以自行替换使用 -->
                 <exclusion>
                     <groupId>software.amazon.awssdk</groupId>
                     <artifactId>aws-crt-client</artifactId>
@@ -49,13 +49,13 @@
             </exclusions>
         </dependency>
 
-        <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+        <!-- 适用于 Netty 的客户端 -->
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>netty-nio-client</artifactId>
         </dependency>
 
-        <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+        <!-- 客户端的性能增强传输管理器 -->
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>s3-transfer-manager</artifactId>

+ 39 - 28
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java

@@ -141,7 +141,8 @@ public class OssClient {
         try {
             // 构建上传请求对象
             FileUpload fileUpload = transferManager.uploadFile(
-                x -> x.putObjectRequest(
+                x -> {
+                    x.source(filePath).putObjectRequest(
                         y -> y.bucket(properties.getBucketName())
                             .key(key)
                             .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
@@ -149,10 +150,13 @@ public class OssClient {
                             // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                             // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
                             //.acl(getAccessPolicy().getObjectCannedACL())
-                            .build())
-                    .addTransferListener(LoggingTransferListener.create())
-                    .source(filePath).build());
-
+                            .build()
+                    );
+                    if (log.isDebugEnabled()) {
+                        x.addTransferListener(LoggingTransferListener.create());
+                    }
+                }
+            );
             // 等待上传完成并获取上传结果
             CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
             String eTag = uploadResult.response().eTag();
@@ -192,16 +196,21 @@ public class OssClient {
 
             // 使用 transferManager 进行上传
             Upload upload = transferManager.upload(
-                x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
-                    .putObjectRequest(
+                x -> {
+                    x.requestBody(body).putObjectRequest(
                         y -> y.bucket(properties.getBucketName())
                             .key(key)
                             .contentType(contentType)
                             // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                             // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
                             //.acl(getAccessPolicy().getObjectCannedACL())
-                            .build())
-                    .build());
+                            .build()
+                    );
+                    if (log.isDebugEnabled()) {
+                        x.addTransferListener(LoggingTransferListener.create());
+                    }
+                }
+            );
 
             // 将输入流写入请求体
             body.writeInputStream(inputStream);
@@ -229,13 +238,17 @@ public class OssClient {
         Path tempFilePath = FileUtils.createTempFile().toPath();
         // 使用 S3TransferManager 下载文件
         FileDownload downloadFile = transferManager.downloadFile(
-            x -> x.getObjectRequest(
+            x -> {
+                x.destination(tempFilePath).getObjectRequest(
                     y -> y.bucket(properties.getBucketName())
                         .key(removeBaseUrl(path))
-                        .build())
-                .addTransferListener(LoggingTransferListener.create())
-                .destination(tempFilePath)
-                .build());
+                        .build()
+                );
+                if (log.isDebugEnabled()) {
+                    x.addTransferListener(LoggingTransferListener.create());
+                }
+            }
+        );
         // 等待文件下载操作完成
         downloadFile.completionFuture().join();
         return tempFilePath;
@@ -244,8 +257,8 @@ public class OssClient {
     /**
      * 下载文件从 Amazon S3 到 输出流
      *
-     * @param key 文件在 Amazon S3 中的对象键
-     * @param out 输出流
+     * @param key      文件在 Amazon S3 中的对象键
+     * @param out      输出流
      * @param consumer 自定义处理逻辑
      * @throws OssException 如果下载失败,抛出自定义异常
      */
@@ -260,26 +273,24 @@ public class OssClient {
     /**
      * 下载文件从 Amazon S3 到 输出流
      *
-     * @param key 文件在 Amazon S3 中的对象键
+     * @param key                   文件在 Amazon S3 中的对象键
      * @param contentLengthConsumer 文件大小消费者函数
      * @return 写出订阅器
      * @throws OssException 如果下载失败,抛出自定义异常
      */
     public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
         try {
-            // 构建下载请求
-            DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
-                // 文件对象
-                .getObjectRequest(y -> y.bucket(properties.getBucketName())
-                    .key(key)
-                    .build())
-                .addTransferListener(LoggingTransferListener.create())
+            DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
                 // 使用发布订阅转换器
                 .responseTransformer(AsyncResponseTransformer.toPublisher())
-                .build();
+                // 文件对象
+                .getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
+            if (log.isDebugEnabled()) {
+                typedBuilder.addTransferListener(LoggingTransferListener.create());
+            }
 
             // 使用 S3TransferManager 下载文件
-            Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
+            Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
             // 获取下载发布订阅转换器
             ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
             // 执行文件大小消费者函数
@@ -289,7 +300,7 @@ public class OssClient {
             // 构建写出订阅器对象
             return out -> {
                 // 创建可写入的字节通道
-                try(WritableByteChannel channel = Channels.newChannel(out)){
+                try (WritableByteChannel channel = Channels.newChannel(out)) {
                     // 订阅数据
                     publisher.subscribe(byteBuffer -> {
                         while (byteBuffer.hasRemaining()) {
@@ -347,7 +358,7 @@ public class OssClient {
      *
      * @param objectKey   对象KEY
      * @param expiredTime 链接授权到期时间
-     * @param metadata 元数据
+     * @param metadata    元数据
      */
     public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
         // 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL

+ 0 - 49
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java

@@ -1,16 +1,8 @@
 package org.dromara.common.web.config;
 
-import cn.hutool.captcha.CaptchaUtil;
-import cn.hutool.captcha.CircleCaptcha;
-import cn.hutool.captcha.LineCaptcha;
-import cn.hutool.captcha.ShearCaptcha;
 import org.dromara.common.web.config.properties.CaptchaProperties;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Lazy;
-
-import java.awt.*;
 
 /**
  * 验证码配置
@@ -21,45 +13,4 @@ import java.awt.*;
 @EnableConfigurationProperties(CaptchaProperties.class)
 public class CaptchaConfig {
 
-    private static final int WIDTH = 160;
-    private static final int HEIGHT = 60;
-    private static final Color BACKGROUND = Color.LIGHT_GRAY;
-    private static final Font FONT = new Font("Arial", Font.BOLD, 48);
-
-    /**
-     * 圆圈干扰验证码
-     */
-    @Lazy
-    @Bean
-    public CircleCaptcha circleCaptcha() {
-        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
-    /**
-     * 线段干扰的验证码
-     */
-    @Lazy
-    @Bean
-    public LineCaptcha lineCaptcha() {
-        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
-    /**
-     * 扭曲干扰验证码
-     */
-    @Lazy
-    @Bean
-    public ShearCaptcha shearCaptcha() {
-        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
 }

+ 1 - 8
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java

@@ -1,7 +1,5 @@
 package org.dromara.common.web.config.properties;
 
-import org.dromara.common.web.enums.CaptchaCategory;
-import org.dromara.common.web.enums.CaptchaType;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
@@ -19,12 +17,7 @@ public class CaptchaProperties {
     /**
      * 验证码类型
      */
-    private CaptchaType type;
-
-    /**
-     * 验证码类别
-     */
-    private CaptchaCategory category;
+    private String type;
 
     /**
      * 数字验证码位数

+ 197 - 0
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java

@@ -0,0 +1,197 @@
+package org.dromara.common.web.core;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.img.GraphicsUtil;
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.Serial;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 带干扰线、波浪、圆的验证码
+ *
+ * @author Lion Li
+ */
+public class WaveAndCircleCaptcha extends AbstractCaptcha {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    // 构造方法(略,与之前一致)
+    public WaveAndCircleCaptcha(int width, int height) {
+        this(width, height, 4);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount) {
+        this(width, height, codeCount, 6);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
+        this(width, height, new RandomGenerator(codeCount), interfereCount);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
+        super(width, height, generator, interfereCount);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
+        super(width, height, new RandomGenerator(codeCount), interfereCount, size);
+    }
+
+    @Override
+    public Image createImage(String code) {
+        final BufferedImage image = new BufferedImage(
+            width,
+            height,
+            (null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB
+        );
+        final Graphics2D g = ImgUtil.createGraphics(image, this.background);
+
+        try {
+            drawString(g, code);
+            // 扭曲
+            shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
+            drawInterfere(g);
+        } finally {
+            g.dispose();
+        }
+
+        return image;
+    }
+
+    private void drawString(Graphics2D g, String code) {
+        // 设置抗锯齿(让字体渲染更清晰)
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+        if (this.textAlpha != null) {
+            g.setComposite(this.textAlpha);
+        }
+
+        GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
+    }
+
+    protected void drawInterfere(Graphics2D g) {
+        ThreadLocalRandom random = RandomUtil.getRandom();
+        int circleCount = Math.max(0, this.interfereCount - 1);
+
+        // 圈圈
+        for (int i = 0; i < circleCount; i++) {
+            g.setColor(ImgUtil.randomColor(random));
+            int x = random.nextInt(width);
+            int y = random.nextInt(height);
+            int w = random.nextInt(height >> 1);
+            int h = random.nextInt(height >> 1);
+            g.drawOval(x, y, w, h);
+        }
+
+        // 仅 1 条平滑波浪线
+        if (this.interfereCount >= 1) {
+            g.setColor(getRandomColor(120, 230, random));
+            drawSmoothWave(g, random);
+        }
+    }
+
+    private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
+        int amplitude = random.nextInt(8) + 5;        // 波动幅度
+        int wavelength = random.nextInt(40) + 30;     // 波长
+        double phase = random.nextDouble() * Math.PI * 2;
+
+        // ✅ 关键:限制 baseY 在中间区域
+        int centerY = height / 2;
+        int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
+        int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
+
+        g.setStroke(new BasicStroke(2.5f)); // 线宽
+
+        int[] xPoints = new int[width];
+        int[] yPoints = new int[width];
+        for (int x = 0; x < width; x++) {
+            int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
+            // 限制 y 不要超出图像边界(可选)
+            y = Math.max(amplitude, Math.min(y, height - amplitude));
+            xPoints[x] = x;
+            yPoints[x] = y;
+        }
+        g.drawPolyline(xPoints, yPoints, width);
+    }
+
+    private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
+        int range = max - min;
+        return new Color(
+            min + random.nextInt(range),
+            min + random.nextInt(range),
+            min + random.nextInt(range)
+        );
+    }
+
+    /**
+     * 扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    w1
+     * @param h1    h1
+     * @param color 颜色
+     */
+    private void shear(Graphics g, int w1, int h1, Color color) {
+        shearX(g, w1, h1, color);
+        shearY(g, w1, h1, color);
+    }
+
+    /**
+     * X坐标扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    宽
+     * @param h1    高
+     * @param color 颜色
+     */
+    private void shearX(Graphics g, int w1, int h1, Color color) {
+
+        int period = RandomUtil.randomInt(this.width);
+
+        int frames = 1;
+        int phase = RandomUtil.randomInt(2);
+
+        for (int i = 0; i < h1; i++) {
+            double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(0, i, w1, 1, (int) d, 0);
+            g.setColor(color);
+            g.drawLine((int) d, i, 0, i);
+            g.drawLine((int) d + w1, i, w1, i);
+        }
+
+    }
+
+    /**
+     * Y坐标扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    宽
+     * @param h1    高
+     * @param color 颜色
+     */
+    private void shearY(Graphics g, int w1, int h1, Color color) {
+
+        int period = RandomUtil.randomInt(this.height >> 1);
+
+        int frames = 20;
+        int phase = 7;
+        for (int i = 0; i < w1; i++) {
+            double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(i, 0, 1, h1, 0, (int) d);
+            g.setColor(color);
+            // 擦除原位置的痕迹
+            g.drawLine(i, (int) d, i, 0);
+            g.drawLine(i, (int) d + h1, i, h1);
+        }
+
+    }
+
+}

+ 0 - 35
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaCategory.java

@@ -1,35 +0,0 @@
-package org.dromara.common.web.enums;
-
-import cn.hutool.captcha.AbstractCaptcha;
-import cn.hutool.captcha.CircleCaptcha;
-import cn.hutool.captcha.LineCaptcha;
-import cn.hutool.captcha.ShearCaptcha;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 验证码类别
- *
- * @author Lion Li
- */
-@Getter
-@AllArgsConstructor
-public enum CaptchaCategory {
-
-    /**
-     * 线段干扰
-     */
-    LINE(LineCaptcha.class),
-
-    /**
-     * 圆圈干扰
-     */
-    CIRCLE(CircleCaptcha.class),
-
-    /**
-     * 扭曲干扰
-     */
-    SHEAR(ShearCaptcha.class);
-
-    private final Class<? extends AbstractCaptcha> clazz;
-}

+ 0 - 29
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java

@@ -1,29 +0,0 @@
-package org.dromara.common.web.enums;
-
-import cn.hutool.captcha.generator.CodeGenerator;
-import cn.hutool.captcha.generator.MathGenerator;
-import cn.hutool.captcha.generator.RandomGenerator;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 验证码类型
- *
- * @author Lion Li
- */
-@Getter
-@AllArgsConstructor
-public enum CaptchaType {
-
-    /**
-     * 数字
-     */
-    MATH(MathGenerator.class),
-
-    /**
-     * 字符
-     */
-    CHAR(RandomGenerator.class);
-
-    private final Class<? extends CodeGenerator> clazz;
-}

+ 0 - 146
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/common/register/ServerRegister.java

@@ -1,146 +0,0 @@
-package com.aizuda.snailjob.server.common.register;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.StrUtil;
-import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
-import com.aizuda.snailjob.common.core.util.JsonUtil;
-import com.aizuda.snailjob.common.core.util.NetUtil;
-import com.aizuda.snailjob.common.core.util.SnailJobVersion;
-import com.aizuda.snailjob.common.core.util.StreamUtils;
-import com.aizuda.snailjob.common.log.SnailJobLog;
-import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
-import com.aizuda.snailjob.server.common.config.SystemProperties;
-import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
-import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
-import com.aizuda.snailjob.server.common.handler.InstanceManager;
-import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.google.common.collect.Lists;
-import lombok.RequiredArgsConstructor;
-import org.springframework.boot.autoconfigure.web.ServerProperties;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 服务端注册
- *
- * @author opensnail
- * @date 2023-06-07
- * @since 1.6.0
- */
-@Component(ServerRegister.BEAN_NAME)
-@RequiredArgsConstructor
-public class ServerRegister extends AbstractRegister {
-    public static final String BEAN_NAME = "serverRegister";
-    private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
-    public static final int DELAY_TIME = 30;
-    public static final String CURRENT_CID;
-    public static final String GROUP_NAME = "DEFAULT_SERVER";
-    public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
-    private final InstanceManager instanceManager;
-    private final SystemProperties systemProperties;
-    private final ServerProperties serverProperties;
-
-    static {
-        CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
-    }
-
-    @Override
-    public boolean supports(int type) {
-        return getNodeType().equals(type);
-    }
-
-    @Override
-    protected void beforeProcessor(RegisterContext context) {
-        // 新增扩展参数
-        ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
-        serverNodeExtAttrs.setWebPort(serverProperties.getPort());
-        serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
-
-        context.setGroupName(GROUP_NAME);
-        context.setHostId(CURRENT_CID);
-        String serverHost = systemProperties.getServerHost();
-        if (StrUtil.isEmptyIfStr(serverHost)) {
-            serverHost = NetUtil.getLocalIpStr();
-        }
-        context.setHostIp(serverHost);
-        context.setHostPort(systemProperties.getServerPort());
-        context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
-        context.setNamespaceId(NAMESPACE_ID);
-        context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
-    }
-
-    @Override
-    protected LocalDateTime getExpireAt() {
-        return LocalDateTime.now().plusSeconds(DELAY_TIME);
-    }
-
-    @Override
-    protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
-        refreshExpireAt(Lists.newArrayList(serverNode));
-        return Boolean.TRUE;
-    }
-
-
-    @Override
-    protected void afterProcessor(final ServerNode serverNode) {
-        try {
-            // 同步当前POD消费的组的节点信息
-            // netty的client只会注册到一个服务端,若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
-            ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
-            if (CollUtil.isNotEmpty(allConsumerGroupName)) {
-                Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
-                if (CollUtil.isEmpty(namespaceIdSets)) {
-                    return;
-                }
-
-                List<ServerNode> serverNodes = serverNodeMapper.selectList(
-                        new LambdaQueryWrapper<ServerNode>()
-                                .eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
-                                .in(ServerNode::getNamespaceId, namespaceIdSets)
-                                .in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
-                for (final ServerNode node : serverNodes) {
-                    // 刷新全量本地缓存
-                    instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
-                    // 刷新过期时间
-                    CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
-                }
-            }
-        } catch (Exception e) {
-            SnailJobLog.LOCAL.error("Client refresh failed", e);
-        }
-    }
-
-    @Override
-    protected Integer getNodeType() {
-        return NodeTypeEnum.SERVER.getType();
-    }
-
-    @Override
-    public void start() {
-        SnailJobLog.LOCAL.info("ServerRegister start");
-
-        serverRegisterNode.scheduleAtFixedRate(() -> {
-            try {
-                this.register(new RegisterContext());
-            } catch (Exception e) {
-                SnailJobLog.LOCAL.error("Server-side registration failed", e);
-            }
-        }, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
-
-    }
-
-    @Override
-    public void close() {
-        SnailJobLog.LOCAL.info("ServerRegister close");
-    }
-}

+ 4 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java

@@ -137,6 +137,8 @@ public class SysMenuController extends BaseController {
             return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
         } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
             return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        } else if (!menuService.checkRouteConfigUnique(menu)) {
+            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
         }
         return toAjax(menuService.insertMenu(menu));
     }
@@ -156,6 +158,8 @@ public class SysMenuController extends BaseController {
             return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
         } else if (menu.getMenuId().equals(menu.getParentId())) {
             return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
+        } else if (!menuService.checkRouteConfigUnique(menu)) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
         }
         return toAjax(menuService.updateMenu(menu));
     }

+ 5 - 5
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java

@@ -130,11 +130,11 @@ public class SysMenu extends BaseEntity {
     public String getRouterPath() {
         String routerPath = this.path;
         // 内链打开外网方式
-        if (getParentId() != 0L && isInnerLink()) {
+        if (!Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
             routerPath = innerLinkReplaceEach(routerPath);
         }
         // 非外链并且是一级目录(类型为目录)
-        if (0L == getParentId() && SystemConstants.TYPE_DIR.equals(getMenuType())
+        if (Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(getMenuType())
             && SystemConstants.NO_FRAME.equals(getIsFrame())) {
             routerPath = "/" + this.path;
         }
@@ -152,7 +152,7 @@ public class SysMenu extends BaseEntity {
         String component = SystemConstants.LAYOUT;
         if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) {
             component = this.component;
-        } else if (StringUtils.isEmpty(this.component) && getParentId() != 0L && isInnerLink()) {
+        } else if (StringUtils.isEmpty(this.component) && !Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
             component = SystemConstants.INNER_LINK;
         } else if (StringUtils.isEmpty(this.component) && isParentView()) {
             component = SystemConstants.PARENT_VIEW;
@@ -164,7 +164,7 @@ public class SysMenu extends BaseEntity {
      * 是否为菜单内部跳转
      */
     public boolean isMenuFrame() {
-        return getParentId() == 0L && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
+        return Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
     }
 
     /**
@@ -178,7 +178,7 @@ public class SysMenu extends BaseEntity {
      * 是否为parent_view组件
      */
     public boolean isParentView() {
-        return getParentId() != 0L && SystemConstants.TYPE_DIR.equals(menuType);
+        return !Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(menuType);
     }
 
     /**

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java

@@ -78,7 +78,7 @@ public class SysUser extends TenantEntity {
     private String password;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
     private String status;
 

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java

@@ -78,7 +78,7 @@ public class SysUserBo extends BaseEntity {
     private String password;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
     private String status;
 

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java

@@ -63,9 +63,9 @@ public class SysUserExportVo implements Serializable {
     private String sex;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
-    @ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
+    @ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "sys_normal_disable")
     private String status;
 

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java

@@ -67,9 +67,9 @@ public class SysUserImportVo implements Serializable {
     private String sex;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
-    @ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
+    @ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "sys_normal_disable")
     private String status;
 

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java

@@ -89,7 +89,7 @@ public class SysUserVo implements Serializable {
     private String password;
 
     /**
-     * 号状态(0正常 1停用)
+     * 号状态(0正常 1停用)
      */
     private String status;
 

+ 9 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java

@@ -160,4 +160,13 @@ public interface ISysMenuService {
      * @return 结果
      */
     boolean checkMenuNameUnique(SysMenuBo menu);
+
+    /**
+     * 校验路由组合是否唯一
+     *
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    boolean checkRouteConfigUnique(SysMenuBo menu);
+
 }

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java

@@ -174,7 +174,7 @@ public interface ISysUserService {
      * 修改用户状态
      *
      * @param userId 用户ID
-     * @param status 号状态
+     * @param status 号状态
      * @return 结果
      */
     int updateUserStatus(Long userId, String status);

+ 51 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.utils.MapstructUtils;
@@ -29,13 +30,17 @@ import org.dromara.system.service.ISysMenuService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 菜单 业务层处理
  *
  * @author Lion Li
  */
+@Slf4j
 @RequiredArgsConstructor
 @Service
 public class SysMenuServiceImpl implements ISysMenuService {
@@ -353,6 +358,51 @@ public class SysMenuServiceImpl implements ISysMenuService {
         return !exist;
     }
 
+    /**
+     * 校验路由名称是否唯一
+     *
+     * @param menuBo 菜单信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkRouteConfigUnique(SysMenuBo menuBo) {
+        SysMenu menu = MapstructUtils.convert(menuBo, SysMenu.class);
+        if (SystemConstants.TYPE_BUTTON.equals(menu.getMenuType())) {
+            return true;
+        }
+        long menuId = ObjectUtil.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
+        Long parentId = menu.getParentId();
+        String path = menu.getPath();
+        String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
+        List<SysMenu> sysMenuList = baseMapper.selectList(
+            new LambdaQueryWrapper<SysMenu>()
+                .in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
+                .and(w ->
+                    w.eq(SysMenu::getPath, path).or().eq(SysMenu::getPath, routeName)
+                ));
+        for (SysMenu sysMenu : sysMenuList) {
+            if (!sysMenu.getMenuId().equals(menuId)) {
+                Long dbParentId = sysMenu.getParentId();
+                String dbPath = sysMenu.getPath();
+                String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
+                if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.equals(dbParentId)) {
+                    log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName());
+                    return false;
+                } else if (StringUtils.equalsAnyIgnoreCase(path, dbPath)
+                    && Constants.TOP_PARENT_ID.equals(parentId)
+                    && Constants.TOP_PARENT_ID.equals(dbParentId)) {
+                    log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName());
+                    return false;
+                } else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName)
+                    && sysMenu.getMenuType().equals(menu.getMenuType())) {
+                    log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName());
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     /**
      * 根据父节点的ID获取所有子节点
      *

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java

@@ -375,7 +375,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
      * 修改用户状态
      *
      * @param userId 用户ID
-     * @param status 号状态
+     * @param status 号状态
      * @return 结果
      */
     @Override

+ 4 - 4
script/docker/docker-compose.yml

@@ -99,7 +99,7 @@ services:
     network_mode: "host"
 
   ruoyi-server1:
-    image: ruoyi/ruoyi-server:5.5.2
+    image: ruoyi/ruoyi-server:5.5.3
     container_name: ruoyi-server1
     environment:
       # 时区上海
@@ -115,7 +115,7 @@ services:
     network_mode: "host"
 
   ruoyi-server2:
-    image: ruoyi/ruoyi-server:5.5.2
+    image: ruoyi/ruoyi-server:5.5.3
     container_name: ruoyi-server2
     environment:
       # 时区上海
@@ -131,7 +131,7 @@ services:
     network_mode: "host"
 
   ruoyi-monitor-admin:
-    image: ruoyi/ruoyi-monitor-admin:5.5.2
+    image: ruoyi/ruoyi-monitor-admin:5.5.3
     container_name: ruoyi-monitor-admin
     environment:
       # 时区上海
@@ -143,7 +143,7 @@ services:
     network_mode: "host"
 
   ruoyi-snailjob-server:
-    image: ruoyi/ruoyi-snailjob-server:5.5.2
+    image: ruoyi/ruoyi-snailjob-server:5.5.3
     container_name: ruoyi-snailjob-server
     environment:
       # 时区上海

+ 1 - 1
script/sql/oracle/oracle_ry_vue_5.X.sql

@@ -257,7 +257,7 @@ comment on column sys_user.phonenumber  is '手机号码';
 comment on column sys_user.sex          is '用户性别(0男 1女 2未知)';
 comment on column sys_user.avatar       is '头像路径';
 comment on column sys_user.password     is '密码';
-comment on column sys_user.status       is '号状态(0正常 1停用)';
+comment on column sys_user.status       is '号状态(0正常 1停用)';
 comment on column sys_user.del_flag     is '删除标志(0代表存在 1代表删除)';
 comment on column sys_user.login_ip     is '最后登录IP';
 comment on column sys_user.login_date   is '最后登录时间';

+ 1 - 1
script/sql/postgres/postgres_ry_vue_5.X.sql

@@ -258,7 +258,7 @@ comment on column sys_user.phonenumber  is '手机号码';
 comment on column sys_user.sex          is '用户性别(0男 1女 2未知)';
 comment on column sys_user.avatar       is '头像地址';
 comment on column sys_user.password     is '密码';
-comment on column sys_user.status       is '号状态(0正常 1停用)';
+comment on column sys_user.status       is '号状态(0正常 1停用)';
 comment on column sys_user.del_flag     is '删除标志(0代表存在 1代表删除)';
 comment on column sys_user.login_ip     is '最后登陆IP';
 comment on column sys_user.login_date   is '最后登陆时间';

+ 1 - 1
script/sql/ry_vue_5.X.sql

@@ -148,7 +148,7 @@ create table sys_user (
     sex               char(1)         default '0'                comment '用户性别(0男 1女 2未知)',
     avatar            bigint(20)                                 comment '头像地址',
     password          varchar(100)    default ''                 comment '密码',
-    status            char(1)         default '0'                comment '号状态(0正常 1停用)',
+    status            char(1)         default '0'                comment '号状态(0正常 1停用)',
     del_flag          char(1)         default '0'                comment '删除标志(0代表存在 1代表删除)',
     login_ip          varchar(128)    default ''                 comment '最后登录IP',
     login_date        datetime                                   comment '最后登录时间',

+ 1 - 1
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql

@@ -2802,7 +2802,7 @@ EXEC sys.sp_addextendedproperty
     'COLUMN', N'password'
 GO
 EXEC sys.sp_addextendedproperty
-    'MS_Description', N'号状态(0正常 1停用)' ,
+    'MS_Description', N'号状态(0正常 1停用)' ,
     'SCHEMA', N'dbo',
     'TABLE', N'sys_user',
     'COLUMN', N'status'