疯狂的狮子Li
2024-08-26 098d3347a0df808908aab8c554cd7c4febc5e6d9
!577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
已添加12个文件
已删除6个文件
已修改98个文件
3595 ■■■■■ 文件已修改
.run/ruoyi-monitor-admin.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-server.run.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-snailjob-server.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-bom/pom.xml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java 483 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java 659 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/pom.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/pom.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/pom.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/pom.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/docker-compose.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/nginx/conf/nginx.conf 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/snail_job_oracle.sql 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/snail_job_postgre.sql 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/snail_job.sql 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/snail_job_sqlserver.sql 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.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.2.1" />
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.2" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
      </settings>
.run/ruoyi-server.run.xml
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.1" />
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.2" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
      </settings>
.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.2.1" />
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.2" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
      </settings>
README.md
@@ -9,7 +9,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/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.2.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -61,6 +61,7 @@
| æ•°æ®åº“连接池      | é‡‡ç”¨ HikariCP Spring官方内置连接池 é…ç½®ç®€å• ä»¥æ€§èƒ½ä¸Žç¨³å®šæ€§é—»åå¤©ä¸‹                                                                        | é‡‡ç”¨ druid bug众多 ç¤¾åŒºç»´æŠ¤å·® æ´»è·ƒåº¦ä½Ž é…ç½®ä¼—多繁琐性能一般                                               |
| æ•°æ®åº“主键       | é‡‡ç”¨ é›ªèбID åŸºäºŽæ—¶é—´æˆ³çš„ æœ‰åºå¢žé•¿ å”¯ä¸€ID å†ä¹Ÿä¸ç”¨ä¸ºåˆ†åº“分表 æ•°æ®åˆå¹¶ä¸»é”®å†²çªé‡å¤è€Œå‘愁                                                                  | é‡‡ç”¨ æ•°æ®åº“自增ID æ”¯æŒæ•°æ®é‡æœ‰é™ ä¸æ”¯æŒå¤šæ•°æ®æºä¸»é”®å”¯ä¸€                                                     |
| WebSocket协议 | åŸºäºŽ Spring å°è£…çš„ WebSocket åè®® æ‰©å±•了Token鉴权与分布式会话同步 ä¸å†åªæ˜¯åŸºäºŽå•机的废物                                                         | æ—                                                                                   |
| SSE推送       | é‡‡ç”¨ Spring SSE å®žçް æ‰©å±•了Token鉴权与分布式会话同步                                                                               | æ—                                                                                   |
| åºåˆ—化         | é‡‡ç”¨ Jackson Spring官方内置序列化 é è°±!!!                                                                                    | é‡‡ç”¨ fastjson bugjson è¿œè¿‘闻名                                                           | 
| åˆ†å¸ƒå¼å¹‚ç­‰       | å‚考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | æ‰‹åŠ¨ç¼–å†™æ³¨è§£åŸºäºŽaop实现                                                                      |
| åˆ†å¸ƒå¼é”        | é‡‡ç”¨ Lock4j åº•层基于 Redisson                                                                                           | æ—                                                                                   |
@@ -72,6 +73,7 @@
| æŽ¥å£æ–‡æ¡£        | é‡‡ç”¨ SpringDoc、javadoc æ— æ³¨è§£é›¶å…¥ä¾µåŸºäºŽjava注释<br/>只需把注释写好 æ— éœ€å†å†™ä¸€å¤§å †çš„æ–‡æ¡£æ³¨è§£äº†                                                     | é‡‡ç”¨ Springfox å·²åœæ­¢ç»´æŠ¤ éœ€è¦ç¼–写大量的注解来支持文档生成                                                | 
| æ ¡éªŒæ¡†æž¶        | é‡‡ç”¨ Validation æ”¯æŒæ³¨è§£ä¸Žå·¥å…·ç±»æ ¡éªŒ æ³¨è§£æ”¯æŒå›½é™…化                                                                                  | ä»…支持注解 ä¸”注解不支持国际化                                                                    |
| Excel框架     | é‡‡ç”¨ Alibaba EasyExcel åŸºäºŽæ’件化<br/>框架对其增加了很多功能 ä¾‹å¦‚ è‡ªåŠ¨åˆå¹¶ç›¸åŒå†…å®¹ è‡ªåŠ¨æŽ’åˆ—å¸ƒå±€ å­—典翻译等                                               | åŸºäºŽ POI æ‰‹å†™å®žçް åŠŸèƒ½æœ‰é™ å¤æ‚ æ‰©å±•性差                                                           |
| å·¥ä½œæµæ”¯æŒ       | æ”¯æŒå„种复杂审批 è½¬åŠž å§”æ´¾ åŠ å‡ç­¾ ä¼šç­¾ æˆ–ç­¾ ç¥¨ç­¾ ç­‰åŠŸèƒ½                                                                                   | æ—                                                                                   |
| å·¥å…·ç±»æ¡†æž¶       | é‡‡ç”¨ Hutool、Lombok ä¸Šç™¾ç§å·¥å…·è¦†ç›–90%的使用需求 åŸºäºŽæ³¨è§£è‡ªåŠ¨ç”Ÿæˆ get set ç­‰ç®€åŒ–框架大量代码                                                       | æ‰‹å†™å·¥å…·ç¨³å®šæ€§å·®æ˜“出问题 å·¥å…·æ•°é‡æœ‰é™ ä»£ç è‡ƒè‚¿éœ€è‡ªå·±æ‰‹å†™ get set ç­‰                                            | 
| ç›‘控框架        | é‡‡ç”¨ SpringBoot-Admin åŸºäºŽSpringBoot官方 actuator æŽ¢é’ˆæœºåˆ¶<br/>实时监控服务状态 æ¡†æž¶è¿˜ä¸ºå…¶æ‰©å±•了在线日志查看监控                                    | æ—                                                                                   | 
| é“¾è·¯è¿½è¸ª        | é‡‡ç”¨ Apache SkyWalking è¿˜åœ¨ä¸ºè¯·æ±‚不知道去哪了 åˆ°å“ªå‡ºäº†é—®é¢˜è€Œçƒ¦æ¼å—<br/>用了它即可实时查看请求经过的每一处每一个节点                                            | æ—                                                                                   |
pom.xml
@@ -13,45 +13,46 @@
    <description>RuoYi-Vue-Plus多租户管理系统</description>
    <properties>
        <revision>5.2.1</revision>
        <spring-boot.version>3.2.6</spring-boot.version>
        <revision>5.2.2</revision>
        <spring-boot.version>3.2.9</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.5.0</springdoc.version>
        <springdoc.version>2.6.0</springdoc.version>
        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
        <poi.version>5.2.3</poi.version>
        <easyexcel.version>3.3.4</easyexcel.version>
        <easyexcel.version>4.0.2</easyexcel.version>
        <velocity.version>2.3</velocity.version>
        <satoken.version>1.38.0</satoken.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <p6spy.version>3.9.1</p6spy.version>
        <hutool.version>5.8.27</hutool.version>
        <hutool.version>5.8.31</hutool.version>
        <okhttp.version>4.10.0</okhttp.version>
        <spring-boot-admin.version>3.2.3</spring-boot-admin.version>
        <redisson.version>3.31.0</redisson.version>
        <redisson.version>3.34.1</redisson.version>
        <lock4j.version>2.2.7</lock4j.version>
        <dynamic-ds.version>4.3.1</dynamic-ds.version>
        <alibaba-ttl.version>2.14.4</alibaba-ttl.version>
        <snailjob.version>1.0.1</snailjob.version>
        <mapstruct-plus.version>1.3.6</mapstruct-plus.version>
        <snailjob.version>1.1.2</snailjob.version>
        <mapstruct-plus.version>1.4.4</mapstruct-plus.version>
        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
        <lombok.version>1.18.32</lombok.version>
        <lombok.version>1.18.34</lombok.version>
        <bouncycastle.version>1.76</bouncycastle.version>
        <justauth.version>1.16.6</justauth.version>
        <!-- ç¦»çº¿IP地址定位库 -->
        <ip2region.version>2.7.0</ip2region.version>
        <undertow.version>2.3.15.Final</undertow.version>
        <!-- OSS é…ç½® -->
        <aws.sdk.version>2.25.15</aws.sdk.version>
        <aws.crt.version>0.29.13</aws.crt.version>
        <!-- SMS é…ç½® -->
        <sms4j.version>3.2.1</sms4j.version>
        <sms4j.version>3.3.2</sms4j.version>
        <!-- é™åˆ¶æ¡†æž¶ä¸­çš„fastjson版本 -->
        <fastjson.version>1.2.83</fastjson.version>
        <!-- é¢å‘运行时的D-ORM依赖 -->
        <anyline.version>8.7.2-20240808</anyline.version>
        <!--工作流配置-->
        <flowable.version>7.0.0</flowable.version>
        <flowable.version>7.0.1</flowable.version>
        <!-- æ’件版本 -->
        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@@ -156,25 +157,9 @@
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${easyexcel.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.poi</groupId>
                        <artifactId>poi-ooxml-schemas</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- velocity代码生成使用模板 -->
@@ -307,12 +292,6 @@
                <version>${snailjob.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>transmittable-thread-local</artifactId>
                <version>${alibaba-ttl.version}</version>
            </dependency>
            <!-- åŠ å¯†åŒ…å¼•å…¥ -->
            <dependency>
                <groupId>org.bouncycastle</groupId>
@@ -334,6 +313,28 @@
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-core</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-servlet</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-websockets-jsr</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <artifactId>commons-compress</artifactId>
                <groupId>org.apache.commons</groupId>
                <version>1.26.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
ruoyi-admin/pom.xml
@@ -22,21 +22,28 @@
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!-- Oracle -->
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
        </dependency>
        <!-- PostgreSql -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <!-- SqlServer -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
        </dependency>
<!--        &lt;!&ndash; mp支持的数据库均支持 åªéœ€è¦å¢žåŠ å¯¹åº”çš„jdbc依赖即可 &ndash;&gt;-->
<!--        &lt;!&ndash; Oracle &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.oracle.database.jdbc</groupId>-->
<!--            <artifactId>ojdbc8</artifactId>-->
<!--        </dependency>-->
<!--        &lt;!&ndash; å…¼å®¹oracle低版本 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.oracle.database.nls</groupId>-->
<!--            <artifactId>orai18n</artifactId>-->
<!--        </dependency>-->
<!--        &lt;!&ndash; PostgreSql &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.postgresql</groupId>-->
<!--            <artifactId>postgresql</artifactId>-->
<!--        </dependency>-->
<!--        &lt;!&ndash; SqlServer &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.microsoft.sqlserver</groupId>-->
<!--            <artifactId>mssql-jdbc</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.dromara</groupId>
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -24,9 +24,9 @@
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
@@ -102,11 +102,11 @@
        Long userId = LoginHelper.getUserId();
        scheduledExecutorService.schedule(() -> {
            WebSocketMessageDto dto = new WebSocketMessageDto();
            SseMessageDto dto = new SseMessageDto();
            dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
            dto.setSessionKeys(List.of(userId));
            WebSocketUtils.publishMessage(dto);
        }, 3, TimeUnit.SECONDS);
            dto.setUserIds(List.of(userId));
            SseMessageUtils.publishMessage(dto);
        }, 5, TimeUnit.SECONDS);
        return R.ok(loginVo);
    }
ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java
@@ -3,6 +3,8 @@
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
@@ -81,7 +83,10 @@
     */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
        TenantHelper.dynamic(tenantId, () -> {
        RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
        });
        log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
    }
@@ -90,7 +95,10 @@
     */
    @Override
    public void doKickout(String loginType, Object loginId, String tokenValue) {
        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
        TenantHelper.dynamic(tenantId, () -> {
        RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
        });
        log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
    }
@@ -99,7 +107,10 @@
     */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {
        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
        TenantHelper.dynamic(tenantId, () -> {
        RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
        });
        log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
    }
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -4,13 +4,14 @@
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
@@ -155,16 +156,13 @@
        loginUser.setUserType(user.getUserType());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
        TenantHelper.dynamic(user.getTenantId(), () -> {
            SysDeptVo dept = null;
            if (ObjectUtil.isNotNull(user.getDeptId())) {
                dept = deptService.selectDeptById(user.getDeptId());
            Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
            loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
            loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
            }
            loginUser.setDeptName(ObjectUtil.isNull(dept) ? "" : dept.getDeptName());
            loginUser.setDeptCategory(ObjectUtil.isNull(dept) ? "" : dept.getDeptCategory());
            List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
            loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
        });
        return loginUser;
    }
@@ -186,7 +184,7 @@
     * ç™»å½•校验
     */
    public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
        String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        String loginFail = Constants.LOGIN_FAIL;
        // èŽ·å–ç”¨æˆ·ç™»å½•é”™è¯¯æ¬¡æ•°ï¼Œé»˜è®¤ä¸º0 (可自定义限制策略 ä¾‹å¦‚: key + username + ip)
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
@@ -21,7 +21,6 @@
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -51,13 +50,12 @@
        String tenantId = loginBody.getTenantId();
        String email = loginBody.getEmail();
        String emailCode = loginBody.getEmailCode();
        // é€šè¿‡é‚®ç®±æŸ¥æ‰¾ç”¨æˆ·
        SysUserVo user = loadUserByEmail(tenantId, email);
        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
            SysUserVo user = loadUserByEmail(email);
        loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
        // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser å±žæ€§ä¸å¤Ÿç”¨ç»§æ‰¿æ‰©å±•就行了
        LoginUser loginUser = loginService.buildLoginUser(user);
            return loginService.buildLoginUser(user);
        });
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        SaLoginModel model = new SaLoginModel();
@@ -89,8 +87,7 @@
        return code.equals(emailCode);
    }
    private SysUserVo loadUserByEmail(String tenantId, String email) {
        return TenantHelper.dynamic(tenantId, () -> {
    private SysUserVo loadUserByEmail(String email) {
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", email);
@@ -100,7 +97,6 @@
                throw new UserException("user.blocked", email);
            }
            return user;
        });
    }
}
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -62,11 +62,12 @@
        if (captchaEnabled) {
            validateCaptcha(tenantId, username, code, uuid);
        }
        SysUserVo user = loadUserByUsername(tenantId, username);
        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
            SysUserVo user = loadUserByUsername(username);
        loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
        // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser
        LoginUser loginUser = loginService.buildLoginUser(user);
            return loginService.buildLoginUser(user);
        });
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        SaLoginModel model = new SaLoginModel();
@@ -107,8 +108,7 @@
        }
    }
    private SysUserVo loadUserByUsername(String tenantId, String username) {
        return TenantHelper.dynamic(tenantId, () -> {
    private SysUserVo loadUserByUsername(String username) {
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", username);
@@ -118,7 +118,6 @@
                throw new UserException("user.blocked", username);
            }
            return user;
        });
    }
}
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
@@ -21,7 +21,6 @@
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -51,13 +50,12 @@
        String tenantId = loginBody.getTenantId();
        String phonenumber = loginBody.getPhonenumber();
        String smsCode = loginBody.getSmsCode();
        // é€šè¿‡æ‰‹æœºå·æŸ¥æ‰¾ç”¨æˆ·
        SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
            SysUserVo user = loadUserByPhonenumber(phonenumber);
        loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
        // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser å±žæ€§ä¸å¤Ÿç”¨ç»§æ‰¿æ‰©å±•就行了
        LoginUser loginUser = loginService.buildLoginUser(user);
            return loginService.buildLoginUser(user);
        });
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        SaLoginModel model = new SaLoginModel();
@@ -89,8 +87,7 @@
        return code.equals(smsCode);
    }
    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
        return TenantHelper.dynamic(tenantId, () -> {
    private SysUserVo loadUserByPhonenumber(String phonenumber) {
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", phonenumber);
@@ -100,7 +97,6 @@
                throw new UserException("user.blocked", phonenumber);
            }
            return user;
        });
    }
}
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -92,11 +92,11 @@
        } else {
            social = list.get(0);
        }
        // æŸ¥æ‰¾ç”¨æˆ·
        SysUserVo user = loadUser(social.getTenantId(), social.getUserId());
        LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
            SysUserVo user = loadUser(social.getUserId());
        // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser å±žæ€§ä¸å¤Ÿç”¨ç»§æ‰¿æ‰©å±•就行了
        LoginUser loginUser = loginService.buildLoginUser(user);
            return loginService.buildLoginUser(user);
        });
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        SaLoginModel model = new SaLoginModel();
@@ -116,8 +116,7 @@
        return loginVo;
    }
    private SysUserVo loadUser(String tenantId, Long userId) {
        return TenantHelper.dynamic(tenantId, () -> {
    private SysUserVo loadUser(Long userId) {
            SysUserVo user = userMapper.selectVoById(userId);
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", "");
@@ -127,7 +126,6 @@
                throw new UserException("user.blocked", "");
            }
            return user;
        });
    }
}
ruoyi-admin/src/main/resources/application-dev.yml
@@ -5,6 +5,9 @@
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
ruoyi-admin/src/main/resources/application-prod.yml
@@ -8,6 +8,9 @@
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
ruoyi-admin/src/main/resources/application.yml
@@ -121,9 +121,6 @@
    # swagger æ–‡æ¡£é…ç½®
    - /*/api-docs
    - /*/api-docs/**
    # actuator ç›‘控配置
    - /actuator
    - /actuator/**
# å¤šç§Ÿæˆ·é…ç½®
tenant:
@@ -259,10 +256,15 @@
    logfile:
      external-file: ./logs/sys-console.log
--- # é»˜è®¤/推荐使用sse推送
sse:
  enabled: true
  path: /resource/sse
--- # websocket
websocket:
  # å¦‚果关闭 éœ€è¦å’Œå‰ç«¯å¼€å…³ä¸€èµ·å…³é—­
  enabled: true
  enabled: false
  # è·¯å¾„
  path: /resource/websocket
  # è®¾ç½®è®¿é—®æºåœ°å€
@@ -270,6 +272,10 @@
--- #flowable配置
flowable:
  # å¼€å…³ ç”¨äºŽå¯åЍ/停用工作流
  enabled: true
  process.enabled: ${flowable.enabled}
  eventregistry.enabled: ${flowable.enabled}
  async-executor-activate: false #关闭定时任务JOB
  #  å°†databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
  database-schema-update: true
ruoyi-common/pom.xml
@@ -33,6 +33,7 @@
        <module>ruoyi-common-encrypt</module>
        <module>ruoyi-common-tenant</module>
        <module>ruoyi-common-websocket</module>
        <module>ruoyi-common-sse</module>
    </modules>
    <artifactId>ruoyi-common</artifactId>
ruoyi-common/ruoyi-common-bom/pom.xml
@@ -14,7 +14,7 @@
    </description>
    <properties>
        <revision>5.2.1</revision>
        <revision>5.2.2</revision>
    </properties>
    <dependencyManagement>
@@ -172,6 +172,13 @@
                <version>${revision}</version>
            </dependency>
            <!-- SSE模块 -->
            <dependency>
                <groupId>org.dromara</groupId>
                <artifactId>ruoyi-common-sse</artifactId>
                <version>${revision}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
ruoyi-common/ruoyi-common-core/pom.xml
@@ -94,11 +94,6 @@
            <artifactId>ip2region</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
@@ -22,4 +22,9 @@
     */
    String SYS_DICT_KEY = "sys_dict:";
    /**
     * ç™»å½•账户密码错误次数 redis key
     */
    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
@@ -28,11 +28,6 @@
    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
    /**
     * ç™»å½•账户密码错误次数 redis key
     */
    String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
    /**
     * ä¸‰æ–¹è®¤è¯ redis key
     */
    String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
@@ -68,6 +68,16 @@
    String DICT_NORMAL = "0";
    /**
     * é€šç”¨å­˜åœ¨æ ‡å¿—
     */
    String DEL_FLAG_NORMAL = "0";
    /**
     * é€šç”¨åˆ é™¤æ ‡å¿—
     */
    String DEL_FLAG_REMOVED  = "2";
    /**
     * æ˜¯å¦ä¸ºç³»ç»Ÿé»˜è®¤ï¼ˆæ˜¯ï¼‰
     */
    String YES = "Y";
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
@@ -66,4 +66,20 @@
     * @return ç”¨æˆ·ids
     */
    List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
    /**
     * é€šè¿‡è§’色ID查询用户
     *
     * @param roleIds è§’色ids
     * @return ç”¨æˆ·
     */
    List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
    /**
     * é€šè¿‡éƒ¨é—¨ID查询用户
     *
     * @param deptIds éƒ¨é—¨ids
     * @return ç”¨æˆ·
     */
    List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
}
ruoyi-common/ruoyi-common-excel/pom.xml
@@ -25,6 +25,11 @@
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
        </dependency>
        <dependency>
            <artifactId>commons-compress</artifactId>
            <groupId>org.apache.commons</groupId>
            <version>1.26.2</version>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
@@ -107,7 +107,7 @@
                    }
                    if (!cellValue.equals(val)) {
                        if ((i - repeatCell.getCurrent() > 1) && 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));
@@ -115,6 +115,11 @@
                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
                            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));
                    }
                }
            }
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java
@@ -1,7 +1,7 @@
package org.dromara.common.mail.config;
import cn.hutool.extra.mail.MailAccount;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailAccount;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
@@ -5,6 +5,9 @@
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.JakartaMail;
import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
import cn.hutool.extra.mail.MailAccount;
import jakarta.mail.Authenticator;
import jakarta.mail.Session;
import lombok.AccessLevel;
@@ -17,7 +20,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
 * é‚®ä»¶å·¥å…·ç±»
@@ -385,7 +388,7 @@
    public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
        Authenticator authenticator = null;
        if (mailAccount.isAuth()) {
            authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
            authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
        }
        return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
@@ -412,7 +415,7 @@
     */
    private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
                               Map<String, InputStream> imageMap, boolean isHtml, File... files) {
        final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
        final JakartaMail mail = JakartaMail.create(mailAccount).setUseGlobalSession(useGlobalSession);
        // å¯é€‰æŠ„送人
        if (CollUtil.isNotEmpty(ccs)) {
@@ -431,7 +434,7 @@
        // å›¾ç‰‡
        if (MapUtil.isNotEmpty(imageMap)) {
            for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
            for (Entry<String, InputStream> entry : imageMap.entrySet()) {
                mail.addImage(entry.getKey(), entry.getValue());
                // å…³é—­æµ
                IoUtil.close(entry.getValue());
@@ -463,5 +466,4 @@
        return result;
    }
    // ------------------------------------------------------------------------------------------------------------------------ Private method end
}
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
@@ -30,4 +30,11 @@
     */
    String[] value() default "dept_id";
    /**
     * æƒé™æ ‡è¯†ç¬¦ ç”¨äºŽé€šè¿‡èœå•权限标识符来获取数据权限
     * æ‹¥æœ‰æ­¤æ ‡è¯†ç¬¦çš„角色 å°†ä¸ä¼šæ‹¼æŽ¥æ­¤è§’色的数据过滤sql
     *
     * @return æƒé™æ ‡è¯†ç¬¦
     */
    String permission() default "";
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
@@ -20,4 +20,11 @@
     */
    DataColumn[] value();
    /**
     * æƒé™æ‹¼æŽ¥æ ‡è¯†ç¬¦(用于指定连接语句的sql符号)
     * å¦‚不填 é»˜è®¤ select ç”¨ OR å…¶ä»–语句用 AND
     * å†…容 OR æˆ–者 AND
     */
    String joinStr() default "";
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -13,9 +13,9 @@
 * å†…置数据:
 * - {@code user}: å½“前登录用户信息,参考 {@link LoginUser}
 * å†…置服务:
 * - {@code sdss}: ç³»ç»Ÿæ•°æ®æƒé™æœåŠ¡ï¼Œå‚è€ƒ {@link ISysDataScopeService}
 * - {@code sdss}: ç³»ç»Ÿæ•°æ®æƒé™æœåŠ¡ï¼Œå‚è€ƒ ISysDataScopeService
 * å¦‚需扩展数据,可以通过 {@link DataPermissionHelper} è¿›è¡Œæ“ä½œ
 * å¦‚需扩展服务,可以通过 {@link ISysDataScopeService} è‡ªè¡Œç¼–写
 * å¦‚需扩展服务,可以通过 ISysDataScopeService è‡ªè¡Œç¼–写
 * </p>
 *
 * @author Lion Li
@@ -32,29 +32,21 @@
    /**
     * è‡ªå®šæ•°æ®æƒé™
     * ä½¿ç”¨ SpEL è¡¨è¾¾å¼ï¼š`#{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )`
     * å¦‚果不满足条件,则使用默认 SQL è¡¨è¾¾å¼ï¼š`1 = 0`
     */
    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
    /**
     * éƒ¨é—¨æ•°æ®æƒé™
     * ä½¿ç”¨ SpEL è¡¨è¾¾å¼ï¼š`#{#deptName} = #{#user.deptId}`
     * å¦‚果不满足条件,则使用默认 SQL è¡¨è¾¾å¼ï¼š`1 = 0`
     */
    DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
    /**
     * éƒ¨é—¨åŠä»¥ä¸‹æ•°æ®æƒé™
     * ä½¿ç”¨ SpEL è¡¨è¾¾å¼ï¼š`#{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )}`
     * å¦‚果不满足条件,则使用默认 SQL è¡¨è¾¾å¼ï¼š`1 = 0`
     */
    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
    /**
     * ä»…本人数据权限
     * ä½¿ç”¨ SpEL è¡¨è¾¾å¼ï¼š`#{#userName} = #{#user.userId}`
     * å¦‚果不满足条件,则使用默认 SQL è¡¨è¾¾å¼ï¼š`1 = 0`
     */
    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -48,6 +48,10 @@
                            ? baseEntity.getCreateDept() : loginUser.getDeptId());
                    }
                }
            } else {
                Date date = new Date();
                this.strictInsertFill(metaObject, "createTime", Date.class, date);
                this.strictInsertFill(metaObject, "updateTime", Date.class, date);
            }
        } catch (Exception e) {
            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
@@ -72,6 +76,8 @@
                if (ObjectUtil.isNotNull(userId)) {
                    baseEntity.setUpdateBy(userId);
                }
            } else {
                this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
            }
        } catch (Exception e) {
            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -99,7 +99,7 @@
            return where;
        }
        // æž„造数据过滤条件的 SQL ç‰‡æ®µ
        String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect);
        String dataFilterSql = buildDataFilter(dataPermission, isSelect);
        if (StringUtils.isBlank(dataFilterSql)) {
            return where;
        }
@@ -120,14 +120,17 @@
    /**
     * æž„建数据过滤条件的 SQL è¯­å¥
     *
     * @param dataColumns æ•°æ®æƒé™æ³¨è§£ä¸­çš„列信息
     * @param dataPermission æ•°æ®æƒé™æ³¨è§£
     * @param isSelect    æ ‡å¿—当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
     * @return æž„建的数据过滤条件的 SQL è¯­å¥
     * @throws ServiceException å¦‚果角色的数据范围异常或者 key ä¸Ž value çš„长度不匹配,则抛出 ServiceException å¼‚常
     */
    private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
    private String buildDataFilter(DataPermission dataPermission, boolean isSelect) {
        // æ›´æ–°æˆ–删除需满足所有条件
        String joinStr = isSelect ? " OR " : " AND ";
        if (StringUtils.isNotBlank(dataPermission.joinStr())) {
            joinStr = " " + dataPermission.joinStr() + " ";
        }
        LoginUser user = DataPermissionHelper.getVariable("user");
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(beanResolver);
@@ -145,7 +148,7 @@
                return "";
            }
            boolean isSuccess = false;
            for (DataColumn dataColumn : dataColumns) {
            for (DataColumn dataColumn : dataPermission.value()) {
                if (dataColumn.key().length != dataColumn.value().length) {
                    throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
                }
@@ -155,6 +158,13 @@
                )) {
                    continue;
                }
                // åŒ…含权限标识符 è¿™ç›´æŽ¥è·³è¿‡
                if (StringUtils.isNotBlank(dataColumn.permission()) &&
                    CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
                ) {
                    isSuccess = true;
                    continue;
                }
                // è®¾ç½®æ³¨è§£å˜é‡ key ä¸ºè¡¨è¾¾å¼å˜é‡ value ä¸ºå˜é‡å€¼
                for (int i = 0; i < dataColumn.key().length; i++) {
                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -2,14 +2,17 @@
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.function.Supplier;
/**
@@ -23,6 +26,8 @@
public class DataPermissionHelper {
    private static final String DATA_PERMISSION_KEY = "data:permission";
    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
    /**
     * ä»Žä¸Šä¸‹æ–‡ä¸­èŽ·å–æŒ‡å®šé”®çš„å˜é‡å€¼ï¼Œå¹¶å°†å…¶è½¬æ¢ä¸ºæŒ‡å®šçš„ç±»åž‹
@@ -66,23 +71,54 @@
        throw new NullPointerException("data permission context type exception");
    }
    private static IgnoreStrategy getIgnoreStrategy() {
        Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
        if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
            if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
                return ignoreStrategy;
            }
        }
        return null;
    }
    /**
     * å¼€å¯å¿½ç•¥æ•°æ®æƒé™(开启后需手动调用 {@link #disableIgnore()} å…³é—­)
     */
    public static void enableIgnore() {
        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
        if (ObjectUtil.isNull(ignoreStrategy)) {
        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
        } else {
            ignoreStrategy.setDataPermission(true);
        }
        Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
        reentrantStack.push(reentrantStack.size() + 1);
    }
    /**
     * å…³é—­å¿½ç•¥æ•°æ®æƒé™
     */
    public static void disableIgnore() {
        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
        if (ObjectUtil.isNotNull(ignoreStrategy)) {
            boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
                && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
                && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
                && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine())
                && CollectionUtil.isEmpty(ignoreStrategy.getOthers());
            Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
            boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
            if (noOtherIgnoreStrategy && empty) {
        InterceptorIgnoreHelper.clearIgnoreStrategy();
            } else if (empty) {
                ignoreStrategy.setDataPermission(false);
            }
        }
    }
    /**
     * åœ¨å¿½ç•¥æ•°æ®æƒé™ä¸­æ‰§è¡Œ
     * <p>禁止在忽略数据权限中执行忽略数据权限</p>
     *
     * @param handle å¤„理执行方法
     */
@@ -97,7 +133,6 @@
    /**
     * åœ¨å¿½ç•¥æ•°æ®æƒé™ä¸­æ‰§è¡Œ
     * <p>禁止在忽略数据权限中执行忽略数据权限</p>
     *
     * @param handle å¤„理执行方法
     */
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
@@ -80,11 +80,11 @@
    private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        String key = rateLimiter.key();
        if (StringUtils.isNotBlank(key)) {
        // åˆ¤æ–­ key ä¸ä¸ºç©º å’Œ ä¸æ˜¯è¡¨è¾¾å¼
        if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method targetMethod = signature.getMethod();
            Object[] args = point.getArgs();
            //noinspection DataFlowIssue
            MethodBasedEvaluationContext context =
                new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
            context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
@@ -15,15 +15,17 @@
    private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
        CAFFEINE = SpringUtils.getBean("caffeine");
    private final String name;
    private final Cache cache;
    public CaffeineCacheDecorator(Cache cache) {
    public CaffeineCacheDecorator(String name, Cache cache) {
        this.name = name;
        this.cache = cache;
    }
    @Override
    public String getName() {
        return cache.getName();
        return name;
    }
    @Override
@@ -32,7 +34,7 @@
    }
    public String getUniqueKey(Object key) {
        return cache.getName() + ":" + key;
        return name + ":" + key;
    }
    @Override
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
@@ -156,7 +156,7 @@
    private Cache createMap(String name, CacheConfig config) {
        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
        Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
@@ -170,7 +170,7 @@
    private Cache createMapCache(String name, CacheConfig config) {
        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
        Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
@@ -1,11 +1,15 @@
package org.dromara.common.security.config;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -14,6 +18,7 @@
import org.dromara.common.security.handler.AllUrlHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -71,4 +76,19 @@
            .excludePathPatterns(securityProperties.getExcludes());
    }
    /**
     * å¯¹ actuator å¥åº·æ£€æŸ¥æŽ¥å£ åšè´¦å·å¯†ç é‰´æƒ
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        String username = SpringUtils.getProperty("spring.boot.admin.client.username");
        String password = SpringUtils.getProperty("spring.boot.admin.client.password");
        return new SaServletFilter()
            .addInclude("/actuator", "/actuator/**")
            .setAuth(obj -> {
                SaHttpBasicUtil.check(username + ":" + password);
            })
            .setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
    }
}
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
@@ -37,7 +37,57 @@
    /**
     * é“¶è¡Œå¡
     */
    BANK_CARD(DesensitizedUtil::bankCard);
    BANK_CARD(DesensitizedUtil::bankCard),
    /**
     * ä¸­æ–‡å
     */
    CHINESE_NAME(DesensitizedUtil::chineseName),
    /**
     * å›ºå®šç”µè¯
     */
    FIXED_PHONE(DesensitizedUtil::fixedPhone),
    /**
     * ç”¨æˆ·ID
     */
    USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
    /**
     * å¯†ç 
     */
    PASSWORD(DesensitizedUtil::password),
    /**
     * ipv4
     */
    IPV4(DesensitizedUtil::ipv4),
    /**
     * ipv6
     */
    IPV6(DesensitizedUtil::ipv6),
    /**
     * ä¸­å›½å¤§é™†è½¦ç‰Œï¼ŒåŒ…含普通车辆、新能源车辆
     */
    CAR_LICENSE(DesensitizedUtil::carLicense),
    /**
     * åªæ˜¾ç¤ºç¬¬ä¸€ä¸ªå­—符
     */
    FIRST_MASK(DesensitizedUtil::firstMask),
    /**
     * æ¸…空为null
     */
    CLEAR(s -> DesensitizedUtil.clear()),
    /**
     * æ¸…空为""
     */
    CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());
    //可自行添加其他脱敏策略
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -58,9 +58,9 @@
            case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
            case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
            case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
            case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey("").build(), STATE_CACHE);
            case "stack_overflow" -> new AuthStackOverflowRequest(builder.build(), STATE_CACHE);
            case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE);
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId("").build(), STATE_CACHE);
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.build(), STATE_CACHE);
            case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
            case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
            case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
ruoyi-common/ruoyi-common-sse/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.dromara</groupId>
        <artifactId>ruoyi-common</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ruoyi-common-sse</artifactId>
    <description>
        ruoyi-common-sse æ¨¡å—
    </description>
    <dependencies>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-satoken</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-json</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package org.dromara.common.sse.config;
import org.dromara.common.sse.controller.SseController;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.listener.SseTopicListener;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
 * SSE è‡ªåŠ¨è£…é…
 *
 * @author Lion Li
 */
@AutoConfiguration
@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
@EnableConfigurationProperties(SseProperties.class)
public class SseAutoConfiguration {
    @Bean
    public SseEmitterManager sseEmitterManager() {
        return new SseEmitterManager();
    }
    @Bean
    public SseTopicListener sseTopicListener() {
        return new SseTopicListener();
    }
    @Bean
    public SseController sseController(SseEmitterManager sseEmitterManager) {
        return new SseController(sseEmitterManager);
    }
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package org.dromara.common.sse.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * SSE é…ç½®é¡¹
 *
 * @author Lion Li
 */
@Data
@ConfigurationProperties("sse")
public class SseProperties {
    private Boolean enabled;
    /**
     * è·¯å¾„
     */
    private String path;
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
package org.dromara.common.sse.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
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;
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 æŽ§åˆ¶å™¨
 *
 * @author Lion Li
 */
@RestController
@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
@RequiredArgsConstructor
public class SseController implements DisposableBean {
    private final SseEmitterManager sseEmitterManager;
    /**
     * å»ºç«‹ SSE è¿žæŽ¥
     */
    @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect() {
        String tokenValue = StpUtil.getTokenValue();
        Long userId = LoginHelper.getUserId();
        return sseEmitterManager.connect(userId, tokenValue);
    }
    /**
     * å…³é—­ SSE è¿žæŽ¥
     */
    @SaIgnore
    @GetMapping(value = "${sse.path}/close")
    public R<Void> close() {
        String tokenValue = StpUtil.getTokenValue();
        Long userId = LoginHelper.getUserId();
        sseEmitterManager.disconnect(userId, tokenValue);
        return R.ok();
    }
    /**
     * å‘特定用户发送消息
     *
     * @param userId ç›®æ ‡ç”¨æˆ·çš„ ID
     * @param msg    è¦å‘送的消息内容
     */
    @GetMapping(value = "${sse.path}/send")
    public R<Void> 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<Void> send(String msg) {
        sseEmitterManager.publishAll(msg);
        return R.ok();
    }
    /**
     * æ¸…理资源。此方法目前不执行任何操作,但避免因未实现而导致错误
     */
    @Override
    public void destroy() throws Exception {
        // é”€æ¯æ—¶ä¸éœ€è¦åšä»€ä¹ˆ æ­¤æ–¹æ³•避免无用操作报错
    }
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
package org.dromara.common.sse.core;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
 * ç®¡ç† Server-Sent Events (SSE) è¿žæŽ¥
 *
 * @author Lion Li
 */
@Slf4j
public class SseEmitterManager {
    /**
     * è®¢é˜…的频道
     */
    private final static String SSE_TOPIC = "global:sse";
    private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
    /**
     * å»ºç«‹ä¸ŽæŒ‡å®šç”¨æˆ·çš„ SSE è¿žæŽ¥
     *
     * @param userId ç”¨æˆ·çš„唯一标识符,用于区分不同用户的连接
     * @param token  ç”¨æˆ·çš„唯一令牌,用于识别具体的连接
     * @return è¿”回一个 SseEmitter å®žä¾‹ï¼Œå®¢æˆ·ç«¯å¯ä»¥é€šè¿‡è¯¥å®žä¾‹æŽ¥æ”¶ SSE äº‹ä»¶
     */
    public SseEmitter connect(Long userId, String token) {
        // ä»Ž USER_TOKEN_EMITTERS ä¸­èŽ·å–æˆ–åˆ›å»ºå½“å‰ç”¨æˆ·çš„ SseEmitter æ˜ å°„表(ConcurrentHashMap)
        // æ¯ä¸ªç”¨æˆ·å¯ä»¥æœ‰å¤šä¸ª SSE è¿žæŽ¥ï¼Œé€šè¿‡ token è¿›è¡ŒåŒºåˆ†
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
        // åˆ›å»ºä¸€ä¸ªæ–°çš„ SseEmitter å®žä¾‹ï¼Œè¶…时时间设置为 0 è¡¨ç¤ºæ— é™åˆ¶
        SseEmitter emitter = new SseEmitter(0L);
        emitters.put(token, emitter);
        // å½“ emitter å®Œæˆã€è¶…时或发生错误时,从映射表中移除对应的 token
        emitter.onCompletion(() -> emitters.remove(token));
        emitter.onTimeout(() -> emitters.remove(token));
        emitter.onError((e) -> emitters.remove(token));
        try {
            // å‘客户端发送一条连接成功的事件
            emitter.send(SseEmitter.event().comment("connected"));
        } catch (IOException e) {
            // å¦‚果发送消息失败,则从映射表中移除 emitter
            emitters.remove(token);
        }
        return emitter;
    }
    /**
     * æ–­å¼€æŒ‡å®šç”¨æˆ·çš„ SSE è¿žæŽ¥
     *
     * @param userId ç”¨æˆ·çš„唯一标识符,用于区分不同用户的连接
     * @param token  ç”¨æˆ·çš„唯一令牌,用于识别具体的连接
     */
    public void disconnect(Long userId, String token) {
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
        if (emitters != null) {
            try {
                emitters.get(token).send(SseEmitter.event().comment("disconnected"));
            } catch (Exception ignore) {
            }
            emitters.remove(token);
        }
    }
    /**
     * è®¢é˜…SSE消息主题,并提供一个消费者函数来处理接收到的消息
     *
     * @param consumer å¤„理SSE消息的消费者函数
     */
    public void subscribeMessage(Consumer<SseMessageDto> consumer) {
        RedisUtils.subscribe(SSE_TOPIC, SseMessageDto.class, consumer);
    }
    /**
     * å‘指定的用户会话发送消息
     *
     * @param userId  è¦å‘送消息的用户id
     * @param message è¦å‘送的消息内容
     */
    public void sendMessage(Long userId, String message) {
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
        if (emitters != null) {
            for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
                try {
                    entry.getValue().send(SseEmitter.event()
                        .name("message")
                        .data(message));
                } catch (Exception e) {
                    emitters.remove(entry.getKey());
                }
            }
        }
    }
    /**
     * æœ¬æœºå…¨ç”¨æˆ·ä¼šè¯å‘送消息
     *
     * @param message è¦å‘送的消息内容
     */
    public void sendMessage(String message) {
        for (Long userId : USER_TOKEN_EMITTERS.keySet()) {
            sendMessage(userId, message);
        }
    }
    /**
     * å‘布SSE订阅消息
     *
     * @param sseMessageDto è¦å‘布的SSE消息对象
     */
    public void publishMessage(SseMessageDto sseMessageDto) {
        List<Long> unsentUserIds = new ArrayList<>();
        // å½“前服务内用户,直接发送消息
        for (Long userId : sseMessageDto.getUserIds()) {
            if (USER_TOKEN_EMITTERS.containsKey(userId)) {
                sendMessage(userId, sseMessageDto.getMessage());
                continue;
            }
            unsentUserIds.add(userId);
        }
        // ä¸åœ¨å½“前服务内用户,发布订阅消息
        if (CollUtil.isNotEmpty(unsentUserIds)) {
            SseMessageDto broadcastMessage = new SseMessageDto();
            broadcastMessage.setMessage(sseMessageDto.getMessage());
            broadcastMessage.setUserIds(unsentUserIds);
            RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
                log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}",
                    SSE_TOPIC, unsentUserIds, sseMessageDto.getMessage());
            });
        }
    }
    /**
     * å‘所有的用户发布订阅的消息(群发)
     *
     * @param message è¦å‘布的消息内容
     */
    public void publishAll(String message) {
        SseMessageDto broadcastMessage = new SseMessageDto();
        broadcastMessage.setMessage(message);
        RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
            log.info("SSE发送主题订阅消息topic:{} message:{}", SSE_TOPIC, message);
        });
    }
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package org.dromara.common.sse.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * æ¶ˆæ¯çš„dto
 *
 * @author zendwang
 */
@Data
public class SseMessageDto implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * éœ€è¦æŽ¨é€åˆ°çš„session key åˆ—表
     */
    private List<Long> userIds;
    /**
     * éœ€è¦å‘送的消息
     */
    private String message;
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.dromara.common.sse.listener;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.sse.core.SseEmitterManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
/**
 * SSE ä¸»é¢˜è®¢é˜…监听器
 *
 * @author Lion Li
 */
@Slf4j
public class SseTopicListener implements ApplicationRunner, Ordered {
    @Autowired
    private SseEmitterManager sseEmitterManager;
    /**
     * åœ¨Spring Boot应用程序启动时初始化SSE主题订阅监听器
     *
     * @param args åº”用程序参数
     * @throws Exception åˆå§‹åŒ–过程中可能抛出的异常
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sseEmitterManager.subscribeMessage((message) -> {
            log.info("SSE主题订阅收到消息session keys={} message={}", message.getUserIds(), message.getMessage());
            // å¦‚æžœkey不为空就按照key发消息 å¦‚果为空就群发
            if (CollUtil.isNotEmpty(message.getUserIds())) {
                message.getUserIds().forEach(key -> {
                    sseEmitterManager.sendMessage(key, message.getMessage());
                });
            } else {
                sseEmitterManager.sendMessage(message.getMessage());
            }
        });
        log.info("初始化SSE主题订阅监听器成功");
    }
    @Override
    public int getOrder() {
        return -1;
    }
}
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.dromara.common.sse.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.dto.SseMessageDto;
/**
 * SSE工具类
 *
 * @author Lion Li
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SseMessageUtils {
    private final static SseEmitterManager MANAGER = SpringUtils.getBean(SseEmitterManager.class);
    /**
     * å‘指定的WebSocket会话发送消息
     *
     * @param userId  è¦å‘送消息的用户id
     * @param message è¦å‘送的消息内容
     */
    public static void sendMessage(Long userId, String message) {
        MANAGER.sendMessage(userId, message);
    }
    /**
     * æœ¬æœºå…¨ç”¨æˆ·ä¼šè¯å‘送消息
     *
     * @param message è¦å‘送的消息内容
     */
    public static void sendMessage(String message) {
        MANAGER.sendMessage(message);
    }
    /**
     * å‘布SSE订阅消息
     *
     * @param sseMessageDto è¦å‘布的SSE消息对象
     */
    public static void publishMessage(SseMessageDto sseMessageDto) {
        MANAGER.publishMessage(sseMessageDto);
    }
    /**
     * å‘所有的用户发布订阅的消息(群发)
     *
     * @param message è¦å‘布的消息内容
     */
    public static void publishAll(String message) {
        MANAGER.publishAll(message);
    }
}
ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
org.dromara.common.sse.config.SseAutoConfiguration
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
@@ -1,8 +1,9 @@
package org.dromara.common.tenant.helper;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
@@ -11,9 +12,11 @@
import org.dromara.common.core.constant.GlobalConstants;
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.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import java.util.Stack;
import java.util.function.Supplier;
/**
@@ -27,7 +30,9 @@
    private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
    private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
    private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new ThreadLocal<>();
    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
    /**
     * ç§Ÿæˆ·åŠŸèƒ½æ˜¯å¦å¯ç”¨
@@ -36,18 +41,49 @@
        return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
    }
    private static IgnoreStrategy getIgnoreStrategy() {
        Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
        if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
            if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
                return ignoreStrategy;
            }
        }
        return null;
    }
    /**
     * å¼€å¯å¿½ç•¥ç§Ÿæˆ·(开启后需手动调用 {@link #disableIgnore()} å…³é—­)
     */
    public static void enableIgnore() {
        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
        if (ObjectUtil.isNull(ignoreStrategy)) {
        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
        } else {
            ignoreStrategy.setTenantLine(true);
        }
        Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
        reentrantStack.push(reentrantStack.size() + 1);
    }
    /**
     * å…³é—­å¿½ç•¥ç§Ÿæˆ·
     */
    public static void disableIgnore() {
        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
        if (ObjectUtil.isNotNull(ignoreStrategy)) {
            boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
                && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
                && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
                && !Boolean.TRUE.equals(ignoreStrategy.getDataPermission())
                && CollectionUtil.isEmpty(ignoreStrategy.getOthers());
            Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
            boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
            if (noOtherIgnoreStrategy && empty) {
        InterceptorIgnoreHelper.clearIgnoreStrategy();
            } else if (empty) {
                ignoreStrategy.setTenantLine(false);
            }
        }
    }
    /**
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java
@@ -1,5 +1,7 @@
package org.dromara.common.tenant.manager;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
@@ -11,6 +13,7 @@
 *
 * @author Lion Li
 */
@Slf4j
public class TenantSpringCacheManager extends PlusSpringCacheManager {
    public TenantSpringCacheManager() {
@@ -18,10 +21,16 @@
    @Override
    public Cache getCache(String name) {
        if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
            return super.getCache(name);
        }
        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
            return super.getCache(name);
        }
        String tenantId = TenantHelper.getTenantId();
        if (StringUtils.isBlank(tenantId)) {
            log.error("无法获取有效的租户id -> Null");
        }
        if (StringUtils.startsWith(name, tenantId)) {
            // å¦‚果存在则直接返回
            return super.getCache(name);
ruoyi-common/ruoyi-common-web/pom.xml
@@ -44,6 +44,19 @@
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-servlet</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-websockets-jsr</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
@@ -16,9 +16,12 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.io.IOException;
/**
 * å…¨å±€å¼‚常处理器
@@ -92,6 +95,20 @@
    /**
     * æ‹¦æˆªæœªçŸ¥çš„运行时异常
     */
    @ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(IOException.class)
    public void handleRuntimeException(IOException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        if (requestURI.contains("sse")) {
            // sse ç»å¸¸æ€§è¿žæŽ¥ä¸­æ–­ ä¾‹å¦‚关闭浏览器 ç›´æŽ¥å±è”½
            return;
        }
        log.error("请求地址'{}',连接中断", requestURI, e);
    }
    /**
     * æ‹¦æˆªæœªçŸ¥çš„运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
@@ -6,7 +6,6 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
@@ -19,7 +18,6 @@
/**
 * web的调用时间统计拦截器
 * dev环境有效
 *
 * @author Lion Li
 * @since 3.3.0
@@ -27,13 +25,10 @@
@Slf4j
public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
    private final String prodProfile = "prod";
    private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
            String url = request.getMethod() + " " + request.getRequestURI();
            // æ‰“印请求参数
@@ -57,7 +52,7 @@
            StopWatch stopWatch = new StopWatch();
            KEY_CACHE.set(stopWatch);
            stopWatch.start();
        }
        return true;
    }
@@ -68,12 +63,10 @@
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
            StopWatch stopWatch = KEY_CACHE.get();
            stopWatch.stop();
            log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
            KEY_CACHE.remove();
        }
    }
    /**
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java
@@ -113,7 +113,7 @@
     * @param session WebSocket会话
     * @param message è¦å‘送的WebSocket消息对象
     */
    private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
    private synchronized static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
        if (session == null || !session.isOpen()) {
            log.warn("[send] session会话已经关闭");
        } else {
ruoyi-extend/ruoyi-monitor-admin/pom.xml
@@ -12,10 +12,21 @@
    <artifactId>ruoyi-monitor-admin</artifactId>
    <dependencies>
        <!-- SpringWeb模块 -->
        <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- web å®¹å™¨ä½¿ç”¨ undertow æ€§èƒ½æ›´å¼º -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
        <!-- spring security å®‰å…¨è®¤è¯ -->
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java
@@ -39,9 +39,7 @@
            .authorizeHttpRequests((authorize) ->
                authorize.requestMatchers(
                        new AntPathRequestMatcher(adminContextPath + "/assets/**"),
                        new AntPathRequestMatcher(adminContextPath + "/login"),
                        new AntPathRequestMatcher("/actuator"),
                        new AntPathRequestMatcher("/actuator/**")
                        new AntPathRequestMatcher(adminContextPath + "/login")
                    ).permitAll()
                    .anyRequest().authenticated())
            .formLogin((formLogin) ->
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java
@@ -9,6 +9,8 @@
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import static de.codecentric.boot.admin.server.domain.values.StatusInfo.*;
/**
 * è‡ªå®šä¹‰äº‹ä»¶é€šçŸ¥å¤„理
 *
@@ -28,13 +30,26 @@
        return Mono.fromRunnable(() -> {
            // å®žä¾‹çŠ¶æ€æ”¹å˜äº‹ä»¶
            if (event instanceof InstanceStatusChangedEvent) {
                // èŽ·å–å®žä¾‹æ³¨å†Œåç§°
                String registName = instance.getRegistration().getName();
                // èŽ·å–å®žä¾‹ID
                String instanceId = event.getInstance().getValue();
                // èŽ·å–å®žä¾‹çŠ¶æ€
                String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
                log.info("Instance Status Change: [{}],[{}],[{}]", registName, instanceId, status);
                // èŽ·å–æœåŠ¡URL
                String serviceUrl = instance.getRegistration().getServiceUrl();
                String statusName = switch (status) {
                    case STATUS_UP -> "服务上线"; // å®žä¾‹æˆåŠŸå¯åŠ¨å¹¶å¯ä»¥æ­£å¸¸å¤„ç†è¯·æ±‚
                    case STATUS_OFFLINE -> "服务离线"; //实例被手动或自动地从服务中移除
                    case STATUS_RESTRICTED -> "服务受限"; //表示实例在某些方面受限,可能无法完全提供所有服务
                    case STATUS_OUT_OF_SERVICE -> "停止服务状态"; //表示实例已被标记为停止提供服务,可能是计划内维护或测试
                    case STATUS_DOWN -> "服务下线"; //实例因崩溃、错误或其他原因停止运行
                    case STATUS_UNKNOWN -> "服务未知异常"; //监控系统无法确定实例的当前状态
                    default -> "未知状态"; //没有匹配的状态
                };
                log.info("Instance Status Change: çŠ¶æ€åç§°ã€{}】, æ³¨å†Œåç§°ã€{}】, å®žä¾‹ID【{}】, çŠ¶æ€ã€{}】, æœåŠ¡URL【{}】",
                    statusName, registName, instanceId, status, serviceUrl);
            }
        });
    }
}
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
@@ -41,5 +41,8 @@
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package com.aizuda.snailjob.server.starter.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ActuatorAuthFilter implements Filter {
    private final String username;
    private final String password;
    public ActuatorAuthFilter(String username, String password) {
        this.username = username;
        this.password = password;
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // èŽ·å– Authorization å¤´
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Basic ")) {
            // å¦‚果没有提供 Authorization æˆ–者格式不对,则返回 401
            response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
            return;
        }
        // è§£ç  Base64 ç¼–码的用户名和密码
        String base64Credentials = authHeader.substring("Basic ".length());
        byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
        String credentials = new String(credDecoded, StandardCharsets.UTF_8);
        String[] split = credentials.split(":");
        if (split.length != 2) {
            response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
            return;
        }
        // éªŒè¯ç”¨æˆ·åå’Œå¯†ç 
        if (!username.equals(split[0]) && password.equals(split[1])) {
            response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
            return;
        }
        // å¦‚果认证成功,继续处理请求
        filterChain.doFilter(request, response);
    }
    @Override
    public void init(FilterConfig filterConfig) {
    }
    @Override
    public void destroy() {
    }
}
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.aizuda.snailjob.server.starter.filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * æƒé™å®‰å…¨é…ç½®
 *
 * @author Lion Li
 */
@Configuration
public class SecurityConfig {
    @Value("${spring.boot.admin.client.username}")
    private String username;
    @Value("${spring.boot.admin.client.password}")
    private String password;
    @Bean
    public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
        FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new ActuatorAuthFilter(username, password));
        registrationBean.addUrlPatterns("/actuator", "/actuator/**");
        return registrationBean;
    }
}
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
@@ -43,5 +43,8 @@
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
@@ -43,5 +43,8 @@
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
@@ -4,14 +4,14 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.poi.ss.formula.functions.T;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.demo.domain.TestDemo;
import org.dromara.demo.domain.vo.TestDemoVo;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@@ -44,16 +44,17 @@
    List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
    @Override
    @DataPermission({
    @DataPermission(value = {
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    int updateById(@Param(Constants.ENTITY) TestDemo entity);
    }, joinStr = "AND")
    List<TestDemo> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
    @Override
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    int deleteByIds(@Param(Constants.COLL) Collection<?> idList);
    int updateById(@Param(Constants.ENTITY) TestDemo entity);
}
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
@@ -3,6 +3,8 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -12,7 +14,6 @@
import org.dromara.demo.domain.vo.TestDemoVo;
import org.dromara.demo.mapper.TestDemoMapper;
import org.dromara.demo.service.ITestDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
@@ -99,7 +100,11 @@
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
            // åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
            List<TestDemo> list = baseMapper.selectBatchIds(ids);
            if (list.size() != ids.size()) {
                throw new ServiceException("您没有删除权限!");
            }
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
ruoyi-modules/ruoyi-generator/pom.xml
@@ -47,6 +47,38 @@
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.anyline</groupId>
            <artifactId>anyline-environment-spring-data-jdbc</artifactId>
            <version>${anyline.version}</version>
        </dependency>
        <dependency>
            <groupId>org.anyline</groupId>
            <artifactId>anyline-data-jdbc-mysql</artifactId>
            <version>${anyline.version}</version>
        </dependency>
        <!-- anyline支持100+种类型数据库 æ·»åŠ å¯¹åº”çš„jdbc依赖与anyline对应数据库依赖包即可 -->
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-mssql</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--        </dependency>-->
    </dependencies>
</project>
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
package org.dromara.generator.config;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.anyline.data.datasource.DataSourceMonitor;
import org.anyline.data.runtime.DataRuntime;
import org.anyline.util.ConfigTable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.HashMap;
import java.util.Map;
/**
 * anyline é€‚配 åŠ¨æ€æ•°æ®æºæ”¹é€ 
 *
 * @author Lion Li
 */
@Slf4j
@Component
public class MyBatisDataSourceMonitor implements DataSourceMonitor {
    public MyBatisDataSourceMonitor() {
        // è°ƒæ•´æ‰§è¡Œæ¨¡å¼ä¸ºè‡ªå®šä¹‰
        ConfigTable.KEEP_ADAPTER = 2;
        // ç¦ç”¨ç¼“å­˜
        ConfigTable.METADATA_CACHE_SCOPE = 0;
    }
    private final Map<String, String> features = new HashMap<>();
    /**
     * æ•°æ®æºç‰¹å¾ ç”¨æ¥å®šå‡† adapter åŒ…含数据库或JDBC协议关键字<br/>
     * ä¸€èˆ¬ä¼šé€šè¿‡ äº§å“å_url åˆæˆ å¦‚果返回null ä¸Šå±‚方法会通过driver_产品名_url合成
     *
     * @param datasource æ•°æ®æº
     * @return String è¿”回null由上层自动提取
     */
    @Override
    public String feature(DataRuntime runtime, Object datasource) {
        String feature = null;
        if (datasource instanceof JdbcTemplate jdbc) {
            DataSource ds = jdbc.getDataSource();
            if (ds instanceof DynamicRoutingDataSource) {
                String key = DynamicDataSourceContextHolder.peek();
                feature = features.get(key);
                if (null == feature) {
                    Connection con = null;
                    try {
                        con = DataSourceUtils.getConnection(ds);
                        DatabaseMetaData meta = con.getMetaData();
                        String url = meta.getURL();
                        feature = meta.getDatabaseProductName().toLowerCase().replace(" ", "") + "_" + url;
                        features.put(key, feature);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    } finally {
                        if (null != con && !DataSourceUtils.isConnectionTransactional(con, ds)) {
                            DataSourceUtils.releaseConnection(con, ds);
                        }
                    }
                }
            }
        }
        return feature;
    }
    /**
     * æ•°æ®æºå”¯ä¸€æ ‡è¯† å¦‚果不实现则默认feature
     * @param datasource æ•°æ®æº
     * @return String è¿”回null由上层自动提取
     */
    @Override
    public String key(DataRuntime runtime, Object datasource) {
        if(datasource instanceof JdbcTemplate jdbc){
            DataSource ds = jdbc.getDataSource();
            if(ds instanceof DynamicRoutingDataSource){
                return DynamicDataSourceContextHolder.peek();
            }
        }
        return runtime.getKey();
    }
    /**
     * ConfigTable.KEEP_ADAPTER=2 : æ ¹æ®å½“前接口判断是否保持同一个数据源绑定同一个adapter<br/>
     * DynamicRoutingDataSource类型的返回false,因为同一个DynamicRoutingDataSource可能对应多类数据库, å¦‚果项目中只有一种数据库 åº”该直接返回true
     *
     * @param datasource æ•°æ®æº
     * @return boolean
     */
    @Override
    public boolean keepAdapter(DataRuntime runtime, Object datasource) {
        if (datasource instanceof JdbcTemplate jdbc) {
            DataSource ds = jdbc.getDataSource();
            return !(ds instanceof DynamicRoutingDataSource);
        }
        return true;
    }
}
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
@@ -17,7 +17,6 @@
 *
 * @author Lion Li
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gen_table_column")
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java
@@ -1,12 +1,8 @@
package org.dromara.generator.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTableColumn;
import java.util.List;
/**
 * ä¸šåŠ¡å­—æ®µ æ•°æ®å±‚
@@ -15,14 +11,5 @@
 */
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, GenTableColumn> {
    /**
     * æ ¹æ®è¡¨åç§°æŸ¥è¯¢åˆ—信息
     *
     * @param tableName è¡¨åç§°
     * @param dataName  æ•°æ®æºåç§°
     * @return åˆ—信息
     */
    @DS("#dataName")
    List<GenTableColumn> selectDbTableColumnsByName(@Param("tableName") String tableName, String dataName);
}
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java
@@ -18,22 +18,6 @@
public interface GenTableMapper extends BaseMapperPlus<GenTable, GenTable> {
    /**
     * æŸ¥è¯¢æ®åº“列表
     *
     * @param genTable æŸ¥è¯¢æ¡ä»¶
     * @return æ•°æ®åº“表集合
     */
    Page<GenTable> selectPageDbTableList(@Param("page") Page<GenTable> page, @Param("genTable") GenTable genTable);
    /**
     * æŸ¥è¯¢æ®åº“列表
     *
     * @param tableNames è¡¨åç§°ç»„
     * @return æ•°æ®åº“表集合
     */
    List<GenTable> selectDbTableListByNames(String[] tableNames);
    /**
     * æŸ¥è¯¢æ‰€æœ‰è¡¨ä¿¡æ¯
     *
     * @return è¡¨ä¿¡æ¯é›†åˆ
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
@@ -9,15 +9,20 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.anyline.metadata.Column;
import org.anyline.metadata.Table;
import org.anyline.proxy.ServiceProxy;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
@@ -41,11 +46,7 @@
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -62,6 +63,8 @@
    private final GenTableMapper baseMapper;
    private final GenTableColumnMapper genTableColumnMapper;
    private final IdentifierGenerator identifierGenerator;
    private static final String[] TABLE_IGNORE = new String[]{"sj_", "act_", "flw_", "gen_"};
    /**
     * æŸ¥è¯¢ä¸šåŠ¡å­—æ®µåˆ—è¡¨
@@ -107,11 +110,67 @@
        return wrapper;
    }
    /**
     * æŸ¥è¯¢æ•°æ®åº“列表
     *
     * @param genTable  åŒ…含查询条件的GenTable对象
     * @param pageQuery åŒ…含分页信息的PageQuery对象
     * @return åŒ…含分页结果的TableDataInfo对象
     */
    @DS("#genTable.dataName")
    @Override
    public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
        genTable.getParams().put("genTableNames",baseMapper.selectTableNameList(genTable.getDataName()));
        Page<GenTable> page = baseMapper.selectPageDbTableList(pageQuery.build(), genTable);
        // èŽ·å–æŸ¥è¯¢æ¡ä»¶
        String tableName = genTable.getTableName();
        String tableComment = genTable.getTableComment();
        LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
        if (CollUtil.isEmpty(tablesMap)) {
            return TableDataInfo.build();
        }
        List<String> tableNames = baseMapper.selectTableNameList(genTable.getDataName());
        String[] tableArrays;
        if (CollUtil.isNotEmpty(tableNames)) {
            tableArrays = tableNames.toArray(new String[0]);
        } else {
            tableArrays = new String[0];
        }
        // è¿‡æ»¤å¹¶è½¬æ¢è¡¨æ ¼æ•°æ®
        List<GenTable> tables = tablesMap.values().stream()
            .filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> {
                if (CollUtil.isEmpty(tableNames)) {
                    return true;
                }
                return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays);
            })
            .filter(x -> {
                boolean nameMatches = true;
                boolean commentMatches = true;
                // è¿›è¡Œè¡¨åç§°çš„æ¨¡ç³ŠæŸ¥è¯¢
                if (StringUtils.isNotBlank(tableName)) {
                    nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName);
                }
                // è¿›è¡Œè¡¨æè¿°çš„æ¨¡ç³ŠæŸ¥è¯¢
                if (StringUtils.isNotBlank(tableComment)) {
                    commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment);
                }
                // åŒæ—¶åŒ¹é…åç§°å’Œæè¿°
                return nameMatches && commentMatches;
            })
            .map(x -> {
                GenTable gen = new GenTable();
                gen.setTableName(x.getName());
                gen.setTableComment(x.getComment());
                gen.setCreateTime(x.getCreateTime());
                gen.setUpdateTime(x.getUpdateTime());
                return gen;
            }).toList();
        IPage<GenTable> page = pageQuery.build();
        page.setTotal(tables.size());
        // æ‰‹åŠ¨åˆ†é¡µ set数据
        page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables));
        return TableDataInfo.build(page);
    }
@@ -125,7 +184,29 @@
    @DS("#dataName")
    @Override
    public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
        return baseMapper.selectDbTableListByNames(tableNames);
        Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
        LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
        if (CollUtil.isEmpty(tablesMap)) {
            return new ArrayList<>();
        }
        List<Table<?>> tableList = tablesMap.values().stream()
            .filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> tableNameSet.contains(x.getName())).toList();
        if (CollUtil.isEmpty(tableList)) {
            return new ArrayList<>();
        }
        return tableList.stream().map(x -> {
            GenTable gen = new GenTable();
            gen.setDataName(dataName);
            gen.setTableName(x.getName());
            gen.setTableComment(x.getComment());
            gen.setCreateTime(x.getCreateTime());
            gen.setUpdateTime(x.getUpdateTime());
            return gen;
        }).toList();
    }
    /**
@@ -187,7 +268,7 @@
                int row = baseMapper.insert(table);
                if (row > 0) {
                    // ä¿å­˜åˆ—信息
                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName, dataName);
                    List<GenTableColumn> genTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(tableName, dataName);
                    List<GenTableColumn> saveColumns = new ArrayList<>();
                    for (GenTableColumn column : genTableColumns) {
                        GenUtils.initColumnField(column, table);
@@ -201,6 +282,32 @@
        } catch (Exception e) {
            throw new ServiceException("导入失败:" + e.getMessage());
        }
    }
    /**
     * æ ¹æ®è¡¨åç§°æŸ¥è¯¢åˆ—信息
     *
     * @param tableName è¡¨åç§°
     * @param dataName  æ•°æ®æºåç§°
     * @return åˆ—信息
     */
    @DS("#dataName")
    @Override
    public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
        LinkedHashMap<String, Column> columns = ServiceProxy.metadata().columns(tableName);
        List<GenTableColumn> tableColumns = new ArrayList<>();
        columns.forEach((columnName, column) -> {
            GenTableColumn tableColumn = new GenTableColumn();
            tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
            tableColumn.setColumnName(column.getName());
            tableColumn.setColumnComment(column.getComment());
            tableColumn.setColumnType(column.getTypeName().toLowerCase());
            tableColumn.setSort(column.getPosition());
            tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
            tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
            tableColumns.add(tableColumn);
        });
        return tableColumns;
    }
    /**
@@ -298,7 +405,7 @@
        List<GenTableColumn> tableColumns = table.getColumns();
        Map<String, GenTableColumn> tableColumnMap = StreamUtils.toIdentityMap(tableColumns, GenTableColumn::getColumnName);
        List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(table.getTableName(), table.getDataName());
        List<GenTableColumn> dbTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(table.getTableName(), table.getDataName());
        if (CollUtil.isEmpty(dbTableColumns)) {
            throw new ServiceException("同步数据失败,原表结构不存在");
        }
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java
@@ -86,6 +86,15 @@
    void importGenTable(List<GenTable> tableList, String dataName);
    /**
     * æ ¹æ®è¡¨åç§°æŸ¥è¯¢åˆ—信息
     *
     * @param tableName è¡¨åç§°
     * @param dataName  æ•°æ®æºåç§°
     * @return åˆ—信息
     */
    List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName);
    /**
     * é¢„览代码
     *
     * @param tableId è¡¨ç¼–号
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java
@@ -215,6 +215,9 @@
                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
            } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
                importList.add("java.math.BigDecimal");
            } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
                importList.add("org.dromara.common.translation.annotation.Translation");
                importList.add("org.dromara.common.translation.constant.TransConstant");
            }
        }
        return importList;
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
@@ -7,87 +7,4 @@
    <resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
    </resultMap>
    <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
            select column_name,
                   (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else '0' end) as is_required,
                   (case when column_key = 'PRI' then '1' else '0' end) as is_pk,
                   ordinal_position as sort,
                   column_comment,
                   (case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
                   column_type
            from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
            order by ordinal_position
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
            select lower(temp.column_name) as column_name,
                    (case when (temp.nullable = 'N'  and  temp.constraint_type != 'P') then '1' else '0' end) as is_required,
                    (case when temp.constraint_type = 'P' then '1' else '0' end) as is_pk,
                    temp.column_id as sort,
                    temp.comments as column_comment,
                    (case when temp.constraint_type = 'P' then '1' else '0' end) as is_increment,
                    lower(temp.data_type) as column_type
            from (
                select col.column_id, col.column_name,col.nullable, col.data_type, colc.comments, uc.constraint_type, row_number()
                    over (partition by col.column_name order by uc.constraint_type desc) as row_flg
                from user_tab_columns col
                left join user_col_comments colc on colc.table_name = col.table_name and colc.column_name = col.column_name
                left join user_cons_columns ucc on ucc.table_name = col.table_name and ucc.column_name = col.column_name
                left join user_constraints uc on uc.constraint_name = ucc.constraint_name
                where col.table_name = upper(#{tableName})
            ) temp
            WHERE temp.row_flg = 1
            ORDER BY temp.column_id
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
            SELECT column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
            FROM (
                SELECT c.relname AS table_name,
                       a.attname AS column_name,
                       d.description AS column_comment,
                       CASE WHEN a.attnotnull AND con.conname IS NULL THEN 1 ELSE 0
                       END AS is_required,
                       CASE WHEN con.conname IS NOT NULL THEN 1 ELSE 0
                       END AS is_pk,
                       a.attnum AS sort,
                       CASE WHEN "position"(pg_get_expr(ad.adbin, ad.adrelid),
                           ((c.relname::text || '_'::text) || a.attname::text) || '_seq'::text) > 0 THEN 1 ELSE 0
                       END AS is_increment,
                       btrim(
                           CASE WHEN t.typelem <![CDATA[ <> ]]> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text ELSE
                                CASE WHEN t.typtype = 'd'::"char" THEN format_type(t.typbasetype, NULL::integer)
                                ELSE format_type(a.atttypid, NULL::integer) END
                           END, '"'::text
                       ) AS column_type
                FROM pg_attribute a
                    JOIN (pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid) ON a.attrelid = c.oid
                    LEFT JOIN pg_description d ON d.objoid = c.oid AND a.attnum = d.objsubid
                    LEFT JOIN pg_constraint con ON con.conrelid = c.oid AND (a.attnum = ANY (con.conkey))
                    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
                    LEFT JOIN pg_type t ON a.atttypid = t.oid
                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
                    AND a.attnum > 0
                    AND n.nspname = 'public'::name
                ORDER BY c.relname, a.attnum
            ) temp
            WHERE table_name = (#{tableName})
                AND column_type <![CDATA[ <> ]]> '-'
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
            SELECT
                cast(A.NAME as nvarchar) as column_name,
                cast(B.NAME as nvarchar) + (case when B.NAME = 'numeric' then '(' + cast(A.prec as nvarchar) + ',' + cast(A.scale as nvarchar) + ')' else '' end) as column_type,
                cast(G.[VALUE] as nvarchar) as column_comment,
                (SELECT 1 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE Z WHERE TABLE_NAME = D.NAME and A.NAME = Z.column_name  ) as is_pk,
                colorder as sort
            FROM SYSCOLUMNS A
                LEFT JOIN SYSTYPES B ON A.XTYPE = B.XUSERTYPE
                INNER JOIN SYSOBJECTS D ON A.ID = D.ID AND D.XTYPE='U' AND D.NAME != 'DTPROPERTIES'
                LEFT JOIN SYS.EXTENDED_PROPERTIES G ON A.ID = G.MAJOR_ID AND A.COLID = G.MINOR_ID
                LEFT JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID AND F.MINOR_ID = 0
            WHERE D.NAME = #{tableName}
            ORDER BY A.COLORDER
        </if>
    </select>
</mapper>
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
@@ -14,239 +14,25 @@
        <id property="columnId" column="column_id"/>
    </resultMap>
    <select id="selectPageDbTableList" resultMap="GenTableResult">
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
            select table_name, table_comment, create_time, update_time
            from information_schema.tables
            where table_schema = (select database())
            AND table_name NOT LIKE 'sj_%' AND table_name NOT LIKE 'gen_%'
            AND table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
            <if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
                AND table_name NOT IN
                <foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
                    #{item}
                </foreach>
            </if>
            <if test="genTable.tableName != null and genTable.tableName != ''">
                AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
            </if>
            <if test="genTable.tableComment != null and genTable.tableComment != ''">
                AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
            </if>
            order by create_time desc
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
            from user_tables dt, user_tab_comments dtc, user_objects uo
            where dt.table_name = dtc.table_name
            and dt.table_name = uo.object_name
            and uo.object_type = 'TABLE'
            AND dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
            AND dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
            <if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
                AND lower(dt.table_name) NOT IN
                <foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
                    #{item}
                </foreach>
            </if>
            <if test="genTable.tableName != null and genTable.tableName != ''">
                AND lower(dt.table_name) like lower(concat(concat('%', #{genTable.tableName}), '%'))
            </if>
            <if test="genTable.tableComment != null and genTable.tableComment != ''">
                AND lower(dtc.comments) like lower(concat(concat('%', #{genTable.tableComment}), '%'))
            </if>
            order by create_time desc
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
            select table_name, table_comment, create_time, update_time
            from (
                SELECT c.relname AS table_name,
                        obj_description(c.oid) AS table_comment,
                        CURRENT_TIMESTAMP AS create_time,
                        CURRENT_TIMESTAMP AS update_time
                FROM pg_class c
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
                    AND c.relname != 'spatial_%'::text
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'sj_%' AND table_name NOT LIKE 'gen_%'
            AND table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
            <if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
                AND table_name NOT IN
                <foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
                    #{item}
                </foreach>
            </if>
            <if test="genTable.tableName != null and genTable.tableName != ''">
                AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
            </if>
            <if test="genTable.tableComment != null and genTable.tableComment != ''">
                AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
            </if>
            order by create_time desc
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
            SELECT cast(D.NAME as nvarchar) as table_name,
                   cast(F.VALUE as nvarchar) as table_comment,
                   crdate as create_time,
                   refdate as update_time
            FROM SYSOBJECTS D
                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
                    AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
                    AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
            <if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
                AND D.NAME NOT IN
                <foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
                    #{item}
                </foreach>
            </if>
            <if test="genTable.tableName != null and genTable.tableName != ''">
                AND lower(D.NAME) like lower(concat(N'%', N'${genTable.tableName}', N'%'))
            </if>
            <if test="genTable.tableComment != null and genTable.tableComment != ''">
                AND lower(CAST(F.VALUE AS nvarchar)) like lower(concat(N'%', N'${genTable.tableComment}', N'%'))
            </if>
            order by crdate desc
        </if>
    </select>
    <select id="selectDbTableListByNames" resultMap="GenTableResult">
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
            select table_name, table_comment, create_time, update_time from information_schema.tables
            where table_schema = (select database())
            and table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
            and table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
            and table_name in
            <foreach collection="array" item="name" open="(" separator="," close=")">
                 #{name}
            </foreach>
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
            from user_tables dt, user_tab_comments dtc, user_objects uo
            where dt.table_name = dtc.table_name
            and dt.table_name = uo.object_name
            and uo.object_type = 'TABLE'
            and dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
            and dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
            and lower(dt.table_name) in
            <foreach collection="array" item="name" open="(" separator="," close=")">
                #{name}
            </foreach>
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
            select table_name, table_comment, create_time, update_time
            from (
                SELECT c.relname AS table_name,
                        obj_description(c.oid) AS table_comment,
                        CURRENT_TIMESTAMP AS create_time,
                        CURRENT_TIMESTAMP AS update_time
                FROM pg_class c
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
                    AND c.relname != 'spatial_%'::text
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
            and table_name NOT LIKE 'act_%' and table_name NOT LIKE 'flw_%'
            and table_name in
            <foreach collection="array" item="name" open="(" separator="," close=")">
                #{name}
            </foreach>
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
            SELECT cast(D.NAME as nvarchar) as table_name,
                   cast(F.VALUE as nvarchar) as table_comment,
                   crdate as create_time,
                   refdate as update_time
            FROM SYSOBJECTS D
                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
                    AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
                    AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
                    AND D.NAME in
                    <foreach collection="array" item="name" open="(" separator="," close=")">
                        #{name}
                    </foreach>
        </if>
    </select>
    <select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
            select table_name, table_comment, create_time, update_time from information_schema.tables
            where table_schema = (select database())
            and table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
            and table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
            and table_name = #{tableName}
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
            select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
            from user_tables dt, user_tab_comments dtc, user_objects uo
            where dt.table_name = dtc.table_name
            and dt.table_name = uo.object_name
            and uo.object_type = 'TABLE'
            AND dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
            AND dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
            AND dt.table_name NOT IN (select table_name from gen_table)
            and lower(dt.table_name) = #{tableName}
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
            select table_name, table_comment, create_time, update_time
            from (
                SELECT c.relname AS table_name,
                        obj_description(c.oid) AS table_comment,
                        CURRENT_TIMESTAMP AS create_time,
                        CURRENT_TIMESTAMP AS update_time
                FROM pg_class c
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
                WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
                    AND c.relname != 'spatial_%'::text
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
            and table_name NOT LIKE 'act_%' and table_name NOT LIKE 'flw_%'
            and table_name = #{tableName}
        </if>
        <if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
            SELECT cast(D.NAME as nvarchar) as table_name,
                   cast(F.VALUE as nvarchar) as table_comment,
                   crdate as create_time,
                   refdate as update_time
            FROM SYSOBJECTS D
                INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
                    AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
                    AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
                    AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
                    AND D.NAME = #{tableName}
        </if>
    </select>
    <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
    <sql id="genSelect">
        SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
               c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
        FROM gen_table t
             LEFT JOIN gen_table_column c ON t.table_id = c.table_id
    </sql>
    <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
        <include refid="genSelect"/>
        where t.table_id = #{tableId} order by c.sort
    </select>
    <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
        SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
               c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
        FROM gen_table t
             LEFT JOIN gen_table_column c ON t.table_id = c.table_id
        <include refid="genSelect"/>
        where t.table_name = #{tableName} order by c.sort
    </select>
    <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
        SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
               c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
        FROM gen_table t
             LEFT JOIN gen_table_column c ON t.table_id = c.table_id
        <include refid="genSelect"/>
        order by c.sort
    </select>
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm
@@ -53,6 +53,13 @@
#end
    private $column.javaType $column.javaField;
#if($column.htmlType == "imageUpload")
    /**
     * ${column.columnComment}Url
     */
    @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
    private String ${column.javaField}Url";
#end
#end
#end
ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm
@@ -9,6 +9,12 @@
                        #elseif($column.javaType == 'Boolean') boolean;
                        #else string;
                    #end
#if($column.htmlType == "imageUpload")
  /**
   * ${column.columnComment}Url
   */
  ${column.javaField}Url: string;
#end
#end
#end
#if ($table.tree)
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -99,9 +99,9 @@
          </template>
        </el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
        <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
          <template #default="scope">
            <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
            <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
          </template>
        </el-table-column>
#elseif($column.list && $column.dictType && "" != $column.dictType)
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -101,9 +101,9 @@
          </template>
        </el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
        <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
          <template #default="scope">
            <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
            <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
          </template>
        </el-table-column>
#elseif($column.list && $column.dictType && "" != $column.dictType)
ruoyi-modules/ruoyi-system/pom.xml
@@ -95,6 +95,11 @@
            <artifactId>ruoyi-common-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-sse</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java
@@ -1,7 +1,9 @@
package org.dromara.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.common.core.constant.GlobalConstants;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
@@ -13,8 +15,6 @@
import org.dromara.system.domain.bo.SysLogininforBo;
import org.dromara.system.domain.vo.SysLogininforVo;
import org.dromara.system.service.ISysLogininforService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -79,7 +79,7 @@
    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
    @GetMapping("/unlock/{userName}")
    public R<Void> unlock(@PathVariable("userName") String userName) {
        String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName;
        String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
        if (RedisUtils.hasKey(loginName)) {
            RedisUtils.deleteObject(loginName);
        }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
@@ -12,6 +12,7 @@
import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysPostService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -29,6 +30,7 @@
public class SysDeptController extends BaseController {
    private final ISysDeptService deptService;
    private final ISysPostService postService;
    /**
     * èŽ·å–éƒ¨é—¨åˆ—è¡¨
@@ -117,6 +119,9 @@
        if (deptService.checkDeptExistUser(deptId)) {
            return R.warn("部门存在用户,不允许删除");
        }
        if (postService.countPostByDeptId(deptId) > 0) {
            return R.warn("部门存在岗位,不允许删除");
        }
        deptService.checkDeptDataScope(deptId);
        return toAjax(deptService.deleteDeptById(deptId));
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java
@@ -8,8 +8,8 @@
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.bo.SysNoticeBo;
import org.dromara.system.domain.vo.SysNoticeVo;
import org.dromara.system.service.ISysNoticeService;
@@ -62,7 +62,7 @@
            return R.fail();
        }
        String type = dictService.getDictLabel("sys_notice_type", notice.getNoticeType());
        WebSocketUtils.publishAll("[" + type + "] " + notice.getNoticeTitle());
        SseMessageUtils.publishAll("[" + type + "] " + notice.getNoticeTitle());
        return R.ok();
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
@@ -11,6 +11,7 @@
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.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysUserBo;
@@ -72,7 +73,8 @@
        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
            return R.fail("修改用户'" + username + "'失败,邮箱账号已存在");
        }
        if (userService.updateUserProfile(user) > 0) {
        int rows = DataPermissionHelper.ignore(() -> userService.updateUserProfile(user));
        if (rows > 0) {
            return R.ok();
        }
        return R.fail("修改个人信息异常,请联系管理员");
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java
@@ -24,6 +24,7 @@
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysTenantService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -38,6 +39,7 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant")
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class SysTenantController extends BaseController {
    private final ISysTenantService tenantService;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java
@@ -20,6 +20,7 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -34,6 +35,7 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant/package")
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class SysTenantPackageController extends BaseController {
    private final ISysTenantPackageService tenantPackageService;
@@ -92,6 +94,9 @@
    @RepeatSubmit()
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
        if (!tenantPackageService.checkPackageNameUnique(bo)) {
            return R.fail("新增套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
        }
        return toAjax(tenantPackageService.insertByBo(bo));
    }
@@ -104,6 +109,9 @@
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
        if (!tenantPackageService.checkPackageNameUnique(bo)) {
            return R.fail("修改套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
        }
        return toAjax(tenantPackageService.updateByBo(bo));
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
@@ -61,13 +61,13 @@
    /**
     * ç”¨æˆ·é‚®ç®±
     */
    @Sensitive(strategy = SensitiveStrategy.EMAIL)
    @Sensitive(strategy = SensitiveStrategy.EMAIL, perms = "system:user:edit")
    private String email;
    /**
     * æ‰‹æœºå·ç 
     */
    @Sensitive(strategy = SensitiveStrategy.PHONE)
    @Sensitive(strategy = SensitiveStrategy.PHONE, perms = "system:user:edit")
    private String phonenumber;
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java
@@ -81,6 +81,14 @@
    long countUserPostById(Long postId);
    /**
     * é€šè¿‡éƒ¨é—¨ID查询岗位使用数量
     *
     * @param deptId éƒ¨é—¨id
     * @return ç»“æžœ
     */
    long countPostByDeptId(Long deptId);
    /**
     * åˆ é™¤å²—位信息
     *
     * @param postId å²—位ID
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java
@@ -1,9 +1,9 @@
package org.dromara.system.service;
import org.dromara.system.domain.vo.SysTenantPackageVo;
import org.dromara.system.domain.bo.SysTenantPackageBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.bo.SysTenantPackageBo;
import org.dromara.system.domain.vo.SysTenantPackageVo;
import java.util.Collection;
import java.util.List;
@@ -46,6 +46,11 @@
    Boolean updateByBo(SysTenantPackageBo bo);
    /**
     * æ ¡éªŒå¥—餐名称是否唯一
     */
    boolean checkPackageNameUnique(SysTenantPackageBo bo);
    /**
     * ä¿®æ”¹å¥—餐状态
     */
    int updatePackageStatus(SysTenantPackageBo bo);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
@@ -38,6 +39,9 @@
     */
    @Override
    public String getRoleCustom(Long roleId) {
        if (ObjectUtil.isNull(roleId)) {
            return "-1";
        }
        List<SysRoleDept> list = roleDeptMapper.selectList(
            new LambdaQueryWrapper<SysRoleDept>()
                .select(SysRoleDept::getDeptId)
@@ -45,7 +49,7 @@
        if (CollUtil.isNotEmpty(list)) {
            return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId()));
        }
        return null;
        return "-1";
    }
    /**
@@ -56,6 +60,9 @@
     */
    @Override
    public String getDeptAndChild(Long deptId) {
        if (ObjectUtil.isNull(deptId)) {
            return "-1";
        }
        List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptId)
            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
@@ -64,7 +71,7 @@
        if (CollUtil.isNotEmpty(ids)) {
            return StreamUtils.join(ids, Convert::toStr);
        }
        return null;
        return "-1";
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
@@ -78,7 +78,7 @@
    private LambdaQueryWrapper<SysDept> buildQueryWrapper(SysDeptBo bo) {
        LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery();
        lqw.eq(SysDept::getDelFlag, "0");
        lqw.eq(SysDept::getDelFlag, UserConstants.DEL_FLAG_NORMAL);
        lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId());
        lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId());
        lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName());
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -77,8 +77,9 @@
    @Override
    public List<SysOssVo> listByIds(Collection<Long> ossIds) {
        List<SysOssVo> list = new ArrayList<>();
        SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
        for (Long id : ossIds) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            SysOssVo vo = ossService.getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo));
@@ -100,8 +101,9 @@
    @Override
    public String selectUrlByIds(String ossIds) {
        List<String> list = new ArrayList<>();
        SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            SysOssVo vo = ossService.getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo).getUrl());
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
@@ -178,6 +178,17 @@
    }
    /**
     * é€šè¿‡éƒ¨é—¨ID查询岗位使用数量
     *
     * @param deptId éƒ¨é—¨id
     * @return ç»“æžœ
     */
    @Override
    public long countPostByDeptId(Long deptId) {
        return baseMapper.selectCount(new LambdaQueryWrapper<SysPost>().eq(SysPost::getDeptId, deptId));
    }
    /**
     * åˆ é™¤å²—位信息
     *
     * @param postId å²—位ID
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -293,6 +293,10 @@
    @Transactional(rollbackFor = Exception.class)
    public int updateRole(SysRoleBo bo) {
        SysRole role = MapstructUtils.convert(bo, SysRole.class);
        if (UserConstants.ROLE_DISABLE.equals(role.getStatus()) && this.countUserRoleByRoleId(role.getRoleId()) > 0) {
            throw new ServiceException("角色已分配,不能禁用!");
        }
        // ä¿®æ”¹è§’色信息
        baseMapper.updateById(role);
        // åˆ é™¤è§’色与菜单关联
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java
@@ -1,6 +1,7 @@
package org.dromara.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -117,6 +118,17 @@
    }
    /**
     * æ ¡éªŒå¥—餐名称是否唯一
     */
    @Override
    public boolean checkPackageNameUnique(SysTenantPackageBo bo) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysTenantPackage>()
            .eq(SysTenantPackage::getPackageName, bo.getPackageName())
            .ne(ObjectUtil.isNotNull(bo.getPackageId()), SysTenantPackage::getPackageId, bo.getPackageId()));
        return !exist;
    }
    /**
     * ä¿®æ”¹å¥—餐状态
     *
     * @param bo å¥—餐信息
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -26,10 +26,7 @@
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.domain.vo.SysRoleVo;
@@ -473,17 +470,14 @@
     */
    private void insertUserRole(Long userId, Long[] roleIds, boolean clear) {
        if (ArrayUtil.isNotEmpty(roleIds)) {
            // åˆ¤æ–­æ˜¯å¦å…·æœ‰æ­¤è§’色的操作权限
            List<SysRoleVo> roles = roleMapper.selectRoleList(new LambdaQueryWrapper<>());
            if (CollUtil.isEmpty(roles)) {
                throw new ServiceException("没有权限访问角色的数据");
            }
            List<Long> roleList = StreamUtils.toList(roles, SysRoleVo::getRoleId);
            List<Long> roleList = new ArrayList<>(List.of(roleIds));
            if (!LoginHelper.isSuperAdmin(userId)) {
                roleList.remove(UserConstants.SUPER_ADMIN_ID);
            }
            List<Long> canDoRoleList = StreamUtils.filter(List.of(roleIds), roleList::contains);
            if (CollUtil.isEmpty(canDoRoleList)) {
            // åˆ¤æ–­æ˜¯å¦å…·æœ‰æ­¤è§’色的操作权限
            List<SysRoleVo> roles = roleMapper.selectRoleList(
                new QueryWrapper<SysRole>().in("r.role_id", roleList));
            if (CollUtil.isEmpty(roles)) {
                throw new ServiceException("没有权限访问角色的数据");
            }
            if (clear) {
@@ -491,7 +485,7 @@
                userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userId));
            }
            // æ–°å¢žç”¨æˆ·ä¸Žè§’色管理
            List<SysUserRole> list = StreamUtils.toList(canDoRoleList, roleId -> {
            List<SysUserRole> list = StreamUtils.toList(roleList, roleId -> {
                SysUserRole ur = new SysUserRole();
                ur.setUserId(userId);
                ur.setRoleId(roleId);
@@ -640,7 +634,7 @@
            return List.of();
        }
        List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName)
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
            .eq(SysUser::getStatus, UserConstants.USER_NORMAL)
            .in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
        return BeanUtil.copyToList(list, UserDTO.class);
@@ -653,4 +647,26 @@
        return StreamUtils.toList(userRoles, SysUserRole::getUserId);
    }
    @Override
    public List<UserDTO> selectUsersByRoleIds(List<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return List.of();
        }
        List<SysUserRole> userRoles = userRoleMapper.selectList(
            new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
        List<Long> userIds = StreamUtils.toList(userRoles, SysUserRole::getUserId);
        return selectListByIds(userIds);
    }
    @Override
    public List<UserDTO> selectUsersByDeptIds(List<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
            return List.of();
        }
        List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
            .eq(SysUser::getStatus, UserConstants.USER_NORMAL)
            .in(CollUtil.isNotEmpty(deptIds), SysUser::getDeptId, deptIds));
        return BeanUtil.copyToList(list, UserDTO.class);
    }
}
ruoyi-modules/ruoyi-workflow/pom.xml
@@ -57,7 +57,7 @@
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-all</artifactId>
            <version>1.10</version>
            <version>1.17</version>
            <exclusions>
                <exclusion>
                    <groupId>xalan</groupId>
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java
@@ -18,6 +18,7 @@
import org.dromara.workflow.service.IActModelService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -36,8 +37,8 @@
@RequestMapping("/workflow/model")
public class ActModelController extends BaseController {
    private final RepositoryService repositoryService;
    @Autowired(required = false)
    private RepositoryService repositoryService;
    private final IActModelService actModelService;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java
@@ -21,6 +21,7 @@
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.engine.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -38,10 +39,9 @@
@RequestMapping("/workflow/task")
public class ActTaskController extends BaseController {
    @Autowired(required = false)
    private TaskService taskService;
    private final IActTaskService actTaskService;
    private final TaskService taskService;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java
@@ -39,6 +39,7 @@
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.validation.ValidationError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -65,7 +66,8 @@
@Service
public class ActModelServiceImpl implements IActModelService {
    private final RepositoryService repositoryService;
    @Autowired(required = false)
    private RepositoryService repositoryService;
    private final IWfNodeConfigService wfNodeConfigService;
    private final IWfDefinitionConfigService wfDefinitionConfigService;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java
@@ -37,6 +37,7 @@
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.deployer.ResourceNameUtil;
import org.flowable.engine.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@@ -61,8 +62,10 @@
@Service
public class ActProcessDefinitionServiceImpl implements IActProcessDefinitionService {
    private final RepositoryService repositoryService;
    private final ProcessMigrationService processMigrationService;
    @Autowired(required = false)
    private RepositoryService repositoryService;
    @Autowired(required = false)
    private ProcessMigrationService processMigrationService;
    private final IWfCategoryService wfCategoryService;
    private final IWfDefinitionConfigService wfDefinitionConfigService;
    private final WfDefinitionConfigMapper wfDefinitionConfigMapper;
@@ -288,6 +291,7 @@
                Model modelData = repositoryService.newModel();
                modelData.setKey(pd.getKey());
                modelData.setName(pd.getName());
                modelData.setCategory(pd.getCategory());
                modelData.setTenantId(pd.getTenantId());
                repositoryService.saveModel(modelData);
                repositoryService.addModelEditorSource(modelData.getId(), IoUtil.readBytes(inputStream));
@@ -350,8 +354,7 @@
            initWfDefConfig();
        } else {
            String originalFilename = file.getOriginalFilename();
            String bpmnResourceSuffix = ResourceNameUtil.BPMN_RESOURCE_SUFFIXES[0];
            if (originalFilename.contains(bpmnResourceSuffix)) {
            if (StringUtils.containsAny(originalFilename, ResourceNameUtil.BPMN_RESOURCE_SUFFIXES)) {
                // æ–‡ä»¶å = æµç¨‹åç§°-流程key
                String[] splitFilename = originalFilename.substring(0, originalFilename.lastIndexOf(".")).split("-");
                if (splitFilename.length < 2) {
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
@@ -48,6 +48,7 @@
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -68,12 +69,17 @@
@Service
public class ActProcessInstanceServiceImpl implements IActProcessInstanceService {
    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final TaskService taskService;
    @Autowired(required = false)
    private RepositoryService repositoryService;
    @Autowired(required = false)
    private RuntimeService runtimeService;
    @Autowired(required = false)
    private HistoryService historyService;
    @Autowired(required = false)
    private TaskService taskService;
    @Autowired(required = false)
    private ManagementService managementService;
    private final IActHiProcinstService actHiProcinstService;
    private final ManagementService managementService;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
    private final IWfNodeConfigService wfNodeConfigService;
    private final FlowProcessEventHandler flowProcessEventHandler;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
@@ -52,6 +52,7 @@
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.variable.api.persistence.entity.VariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -71,11 +72,16 @@
@Service
public class ActTaskServiceImpl implements IActTaskService {
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final HistoryService historyService;
    private final IdentityService identityService;
    private final ManagementService managementService;
    @Autowired(required = false)
    private RuntimeService runtimeService;
    @Autowired(required = false)
    private TaskService taskService;
    @Autowired(required = false)
    private HistoryService historyService;
    @Autowired(required = false)
    private IdentityService identityService;
    @Autowired(required = false)
    private ManagementService managementService;
    private final ActTaskMapper actTaskMapper;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
    private final ActHiTaskinstMapper actHiTaskinstMapper;
@@ -261,7 +267,7 @@
        queryWrapper.eq("t.business_status_", BusinessStatusEnum.WAITING.getStatus());
        queryWrapper.eq(TenantHelper.isEnable(), "t.tenant_id_", TenantHelper.getTenantId());
        String ids = StreamUtils.join(roleIds, x -> "'" + x + "'");
        queryWrapper.and(w1 -> w1.eq("t.assignee_", userId).or(w2 -> w2.isNull("t.assignee_").apply("exists ( select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TASK_ID_ = t.ID_ and LINK.TYPE_ = 'candidate' and (LINK.USER_ID_ = {0} or ( LINK.GROUP_ID_ IN ({1}) ) ))", userId, ids)));
        queryWrapper.and(w1 -> w1.eq("t.assignee_", userId).or(w2 -> w2.isNull("t.assignee_").apply("exists ( select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TASK_ID_ = t.ID_ and LINK.TYPE_ = 'candidate' and (LINK.USER_ID_ = {0} or ( LINK.GROUP_ID_ IN (" + ids + ") ) ))", userId)));
        if (StringUtils.isNotBlank(taskBo.getName())) {
            queryWrapper.like("t.name_", taskBo.getName());
        }
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
@@ -15,6 +15,7 @@
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -32,8 +33,8 @@
public class WfCategoryServiceImpl implements IWfCategoryService {
    private final WfCategoryMapper baseMapper;
    private final RepositoryService repositoryService;
    @Autowired(required = false)
    private RepositoryService repositoryService;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
@@ -8,6 +8,7 @@
import org.dromara.workflow.service.IActProcessInstanceService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -22,8 +23,9 @@
@Service
public class WorkflowServiceImpl implements WorkflowService {
    @Autowired(required = false)
    private RuntimeService runtimeService;
    private final IActProcessInstanceService iActProcessInstanceService;
    private final RuntimeService runtimeService;
    private final IActHiProcinstService iActHiProcinstService;
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
script/docker/docker-compose.yml
@@ -100,7 +100,7 @@
    network_mode: "host"
  ruoyi-server1:
    image: ruoyi/ruoyi-server:5.2.1
    image: ruoyi/ruoyi-server:5.2.2
    container_name: ruoyi-server1
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -115,7 +115,7 @@
    network_mode: "host"
  ruoyi-server2:
    image: ruoyi/ruoyi-server:5.2.1
    image: ruoyi/ruoyi-server:5.2.2
    container_name: ruoyi-server2
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -130,7 +130,7 @@
    network_mode: "host"
  ruoyi-monitor-admin:
    image: ruoyi/ruoyi-monitor-admin:5.2.1
    image: ruoyi/ruoyi-monitor-admin:5.2.2
    container_name: ruoyi-monitor-admin
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -142,7 +142,7 @@
    network_mode: "host"
  ruoyi-snailjob-server:
    image: ruoyi/ruoyi-snailjob-server:5.2.1
    image: ruoyi/ruoyi-snailjob-server:5.2.2
    container_name: ruoyi-snailjob-server
    environment:
      # æ—¶åŒºä¸Šæµ·
script/docker/nginx/conf/nginx.conf
@@ -78,10 +78,13 @@
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # websocket参数
            proxy_read_timeout 86400s;
            # sse ä¸Ž websocket参数
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_buffering off;
            proxy_cache off;
            proxy_pass http://server/;
        }
script/sql/oracle/snail_job_oracle.sql
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : Oracle
 Date: 2024-05-14 23:36:38
 Date: 2024-07-06 12:49:36
*/
@@ -136,7 +136,7 @@
COMMENT ON COLUMN sj_notify_recipient.id IS '主键';
COMMENT ON COLUMN sj_notify_recipient.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_notify_recipient.recipient_name IS '接收人名称';
COMMENT ON COLUMN sj_notify_recipient.notify_type IS '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦';
COMMENT ON COLUMN sj_notify_recipient.notify_type IS '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦ 5 webhook';
COMMENT ON COLUMN sj_notify_recipient.notify_attribute IS '配置属性';
COMMENT ON COLUMN sj_notify_recipient.description IS '描述';
COMMENT ON COLUMN sj_notify_recipient.create_dt IS '创建时间';
@@ -296,8 +296,8 @@
ALTER TABLE sj_retry_task_log_message
    ADD CONSTRAINT pk_sj_retry_task_log_message PRIMARY KEY (id);
CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, unique_id);
CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt);
CREATE INDEX idx_sj_rt_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, unique_id);
CREATE INDEX idx_sj_rt_log_message_02 ON sj_retry_task_log_message (create_dt);
COMMENT ON COLUMN sj_retry_task_log_message.id IS '主键';
COMMENT ON COLUMN sj_retry_task_log_message.namespace_id IS '命名空间id';
@@ -389,8 +389,7 @@
-- sj_distributed_lock
CREATE TABLE sj_distributed_lock
(
    id         number GENERATED ALWAYS AS IDENTITY,
    name       varchar2(64)                              NULL,
    name       varchar2(64)                              NOT NULL,
    lock_until timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
    locked_at  timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
    locked_by  varchar2(255)                             NULL,
@@ -399,9 +398,8 @@
);
ALTER TABLE sj_distributed_lock
    ADD CONSTRAINT pk_sj_distributed_lock PRIMARY KEY (id);
    ADD CONSTRAINT pk_sj_distributed_lock PRIMARY KEY (name);
COMMENT ON COLUMN sj_distributed_lock.id IS '主键';
COMMENT ON COLUMN sj_distributed_lock.name IS '锁名称';
COMMENT ON COLUMN sj_distributed_lock.lock_until IS '锁定时长';
COMMENT ON COLUMN sj_distributed_lock.locked_at IS '锁定时间';
@@ -449,7 +447,7 @@
ALTER TABLE sj_system_user_permission
    ADD CONSTRAINT pk_sj_system_user_permission PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id);
CREATE UNIQUE INDEX uk_sj_su_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id);
COMMENT ON COLUMN sj_system_user_permission.id IS '主键';
COMMENT ON COLUMN sj_system_user_permission.group_name IS '组名称';
@@ -598,7 +596,11 @@
    parent_id      number        DEFAULT 0                                  NOT NULL,
    task_status    smallint      DEFAULT 0                                  NOT NULL,
    retry_count    number        DEFAULT 0                                  NOT NULL,
    mr_stage       smallint      DEFAULT NULL                               NULL,
    leaf           smallint      DEFAULT '1'                                NOT NULL,
    task_name      varchar2(255) DEFAULT ''                                 NULL,
    client_info    varchar2(128) DEFAULT NULL                               NULL,
    wf_context     clob          DEFAULT NULL                               NULL,
    result_message clob                                                     NULL,
    args_str       clob          DEFAULT NULL                               NULL,
    args_type      smallint      DEFAULT 1                                  NOT NULL,
@@ -622,7 +624,11 @@
COMMENT ON COLUMN sj_job_task.parent_id IS '父执行器id';
COMMENT ON COLUMN sj_job_task.task_status IS '执行的状态 0、失败 1、成功';
COMMENT ON COLUMN sj_job_task.retry_count IS '重试次数';
COMMENT ON COLUMN sj_job_task.mr_stage IS '动态分片所处阶段 1:map 2:reduce 3:mergeReduce';
COMMENT ON COLUMN sj_job_task.leaf IS '叶子节点';
COMMENT ON COLUMN sj_job_task.task_name IS '任务名称';
COMMENT ON COLUMN sj_job_task.client_info IS '客户端地址 clientId#ip:port';
COMMENT ON COLUMN sj_job_task.wf_context IS '工作流全局上下文';
COMMENT ON COLUMN sj_job_task.result_message IS '执行结果';
COMMENT ON COLUMN sj_job_task.args_str IS '执行方法参数';
COMMENT ON COLUMN sj_job_task.args_type IS '参数类型 ';
@@ -773,6 +779,7 @@
    executor_timeout number        DEFAULT 0                                  NOT NULL,
    description      varchar2(256) DEFAULT ''                                 NULL,
    flow_info        clob          DEFAULT NULL                               NULL,
    wf_context       clob          DEFAULT NULL                               NULL,
    bucket_index     number        DEFAULT 0                                  NOT NULL,
    version          number                                                   NOT NULL,
    ext_attrs        varchar2(256) DEFAULT ''                                 NULL,
@@ -799,6 +806,7 @@
COMMENT ON COLUMN sj_workflow.executor_timeout IS '任务执行超时时间,单位秒';
COMMENT ON COLUMN sj_workflow.description IS '描述';
COMMENT ON COLUMN sj_workflow.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow.wf_context IS '上下文';
COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_workflow.version IS '版本号';
COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段';
@@ -864,8 +872,10 @@
    task_batch_status smallint      DEFAULT 0                                  NOT NULL,
    operation_reason  smallint      DEFAULT 0                                  NOT NULL,
    flow_info         clob          DEFAULT NULL                               NULL,
    wf_context        clob          DEFAULT NULL                               NULL,
    execution_at      number        DEFAULT 0                                  NOT NULL,
    ext_attrs         varchar2(256) DEFAULT ''                                 NULL,
    version           number        DEFAULT 1                                  NOT NULL,
    deleted           smallint      DEFAULT 0                                  NOT NULL,
    create_dt         date          DEFAULT CURRENT_TIMESTAMP                  NOT NULL,
    update_dt         date          DEFAULT CURRENT_TIMESTAMP                  NOT NULL
@@ -885,10 +895,11 @@
COMMENT ON COLUMN sj_workflow_task_batch.task_batch_status IS '任务批次状态 0、失败 1、成功';
COMMENT ON COLUMN sj_workflow_task_batch.operation_reason IS '操作原因';
COMMENT ON COLUMN sj_workflow_task_batch.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow_task_batch.wf_context IS '全局上下文';
COMMENT ON COLUMN sj_workflow_task_batch.execution_at IS '任务执行时间';
COMMENT ON COLUMN sj_workflow_task_batch.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_workflow_task_batch.version IS '版本号';
COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除';
COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间';
COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间';
COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次';
script/sql/postgres/snail_job_postgre.sql
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : PostgreSQL
 Date: 2024-05-13 22:49:34
 Date: 2024-07-06 11:45:40
*/
@@ -124,7 +124,7 @@
COMMENT ON COLUMN sj_notify_recipient.id IS '主键';
COMMENT ON COLUMN sj_notify_recipient.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_notify_recipient.recipient_name IS '接收人名称';
COMMENT ON COLUMN sj_notify_recipient.notify_type IS '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦';
COMMENT ON COLUMN sj_notify_recipient.notify_type IS '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦ 5 webhook';
COMMENT ON COLUMN sj_notify_recipient.notify_attribute IS '配置属性';
COMMENT ON COLUMN sj_notify_recipient.description IS '描述';
COMMENT ON COLUMN sj_notify_recipient.create_dt IS '创建时间';
@@ -359,8 +359,7 @@
-- sj_distributed_lock
CREATE TABLE sj_distributed_lock
(
    id         bigserial PRIMARY KEY,
    name       varchar(64)  NOT NULL,
    name       varchar(64)  PRIMARY KEY,
    lock_until timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_at  timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_by  varchar(255) NOT NULL,
@@ -368,7 +367,6 @@
    update_dt  timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON COLUMN sj_distributed_lock.id IS '主键';
COMMENT ON COLUMN sj_distributed_lock.name IS '锁名称';
COMMENT ON COLUMN sj_distributed_lock.lock_until IS '锁定时长';
COMMENT ON COLUMN sj_distributed_lock.locked_at IS '锁定时间';
@@ -550,7 +548,11 @@
    parent_id      bigint       NOT NULL DEFAULT 0,
    task_status    smallint     NOT NULL DEFAULT 0,
    retry_count    int          NOT NULL DEFAULT 0,
    mr_stage       smallint     NULL     DEFAULT NULL,
    leaf           smallint     NOT NULL DEFAULT '1',
    task_name      varchar(255) NOT NULL DEFAULT '',
    client_info    varchar(128) NULL     DEFAULT NULL,
    wf_context     text         NULL     DEFAULT NULL,
    result_message text         NOT NULL,
    args_str       text         NULL     DEFAULT NULL,
    args_type      smallint     NOT NULL DEFAULT 1,
@@ -571,7 +573,11 @@
COMMENT ON COLUMN sj_job_task.parent_id IS '父执行器id';
COMMENT ON COLUMN sj_job_task.task_status IS '执行的状态 0、失败 1、成功';
COMMENT ON COLUMN sj_job_task.retry_count IS '重试次数';
COMMENT ON COLUMN sj_job_task.mr_stage IS '动态分片所处阶段 1:map 2:reduce 3:mergeReduce';
COMMENT ON COLUMN sj_job_task.leaf IS '叶子节点';
COMMENT ON COLUMN sj_job_task.task_name IS '任务名称';
COMMENT ON COLUMN sj_job_task.client_info IS '客户端地址 clientId#ip:port';
COMMENT ON COLUMN sj_job_task.wf_context IS '工作流全局上下文';
COMMENT ON COLUMN sj_job_task.result_message IS '执行结果';
COMMENT ON COLUMN sj_job_task.args_str IS '执行方法参数';
COMMENT ON COLUMN sj_job_task.args_type IS '参数类型 ';
@@ -713,6 +719,7 @@
    executor_timeout int          NOT NULL DEFAULT 0,
    description      varchar(256) NOT NULL DEFAULT '',
    flow_info        text         NULL     DEFAULT NULL,
    wf_context       text         NULL     DEFAULT NULL,
    bucket_index     int          NOT NULL DEFAULT 0,
    version          int          NOT NULL,
    ext_attrs        varchar(256) NULL     DEFAULT '',
@@ -736,6 +743,7 @@
COMMENT ON COLUMN sj_workflow.executor_timeout IS '任务执行超时时间,单位秒';
COMMENT ON COLUMN sj_workflow.description IS '描述';
COMMENT ON COLUMN sj_workflow.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow.wf_context IS '上下文';
COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_workflow.version IS '版本号';
COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段';
@@ -798,8 +806,10 @@
    task_batch_status smallint     NOT NULL DEFAULT 0,
    operation_reason  smallint     NOT NULL DEFAULT 0,
    flow_info         text         NULL     DEFAULT NULL,
    wf_context        text         NULL     DEFAULT NULL,
    execution_at      bigint       NOT NULL DEFAULT 0,
    ext_attrs         varchar(256) NULL     DEFAULT '',
    version           int          NOT NULL DEFAULT 1,
    deleted           smallint     NOT NULL DEFAULT 0,
    create_dt         timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_dt         timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP
@@ -816,10 +826,11 @@
COMMENT ON COLUMN sj_workflow_task_batch.task_batch_status IS '任务批次状态 0、失败 1、成功';
COMMENT ON COLUMN sj_workflow_task_batch.operation_reason IS '操作原因';
COMMENT ON COLUMN sj_workflow_task_batch.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow_task_batch.wf_context IS '全局上下文';
COMMENT ON COLUMN sj_workflow_task_batch.execution_at IS '任务执行时间';
COMMENT ON COLUMN sj_workflow_task_batch.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_workflow_task_batch.version IS '版本号';
COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除';
COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间';
COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间';
COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次';
script/sql/snail_job.sql
@@ -68,7 +68,7 @@
    `id`               bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    `namespace_id`     varchar(64)         NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
    `recipient_name`   varchar(64)         NOT NULL COMMENT '接收人名称',
    `notify_type`      tinyint(4)          NOT NULL DEFAULT 0 COMMENT '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦',
    `notify_type`      tinyint(4)          NOT NULL DEFAULT 0 COMMENT '通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦ 5 webhook',
    `notify_attribute` varchar(512)        NOT NULL COMMENT '配置属性',
    `description`      varchar(256)        NOT NULL DEFAULT '' COMMENT '描述',
    `create_dt`        datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -222,15 +222,13 @@
CREATE TABLE `sj_distributed_lock`
(
    `id`         bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    `name`       varchar(64)         NOT NULL COMMENT '锁名称',
    `lock_until` timestamp(3)        NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁定时长',
    `locked_at`  timestamp(3)        NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '锁定时间',
    `locked_by`  varchar(255)        NOT NULL COMMENT '锁定者',
    `create_dt`  datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_dt`  datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_name` (`name`)
    PRIMARY KEY (`name`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 0
  DEFAULT CHARSET = utf8mb4 COMMENT ='锁定表';
@@ -345,12 +343,16 @@
    `job_id`         bigint(20)          NOT NULL COMMENT '任务信息id',
    `task_batch_id`  bigint(20)          NOT NULL COMMENT '调度任务id',
    `parent_id`      bigint(20)          NOT NULL DEFAULT 0 COMMENT '父执行器id',
    `task_status`    tinyint(4)          NOT NULL DEFAULT 0 COMMENT '执行的状态 0、失败 1、成功',
    `task_status`    tinyint             NOT NULL DEFAULT 0 COMMENT '执行的状态 0、失败 1、成功',
    `retry_count`    int(11)             NOT NULL DEFAULT 0 COMMENT '重试次数',
    `mr_stage`       tinyint                      DEFAULT NULL COMMENT '动态分片所处阶段 1:map 2:reduce 3:mergeReduce',
    `leaf`           tinyint             NOT NULL DEFAULT '1' COMMENT '叶子节点',
    `task_name`      varchar(255)        NOT NULL DEFAULT '' COMMENT '任务名称',
    `client_info`    varchar(128)                 DEFAULT NULL COMMENT '客户端地址 clientId#ip:port',
    `wf_context`     text                         DEFAULT NULL COMMENT '工作流全局上下文',
    `result_message` text                NOT NULL COMMENT '执行结果',
    `args_str`       text                         DEFAULT NULL COMMENT '执行方法参数',
    `args_type`      tinyint(4)          NOT NULL DEFAULT 1 COMMENT '参数类型 ',
    `args_type`      tinyint             NOT NULL DEFAULT 1 COMMENT '参数类型 ',
    `ext_attrs`      varchar(256)        NULL     DEFAULT '' COMMENT '扩展字段',
    `create_dt`      datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_dt`      datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
@@ -447,6 +449,7 @@
    `executor_timeout` int(11)             NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒',
    `description`      varchar(256)        NOT NULL DEFAULT '' COMMENT '描述',
    `flow_info`        text                         DEFAULT NULL COMMENT '流程信息',
    `wf_context`       text                         DEFAULT NULL COMMENT '上下文',
    `bucket_index`     int(11)             NOT NULL DEFAULT 0 COMMENT 'bucket',
    `version`          int(11)             NOT NULL COMMENT '版本号',
    `ext_attrs`        varchar(256)        NULL     DEFAULT '' COMMENT '扩展字段',
@@ -495,8 +498,10 @@
    `task_batch_status` tinyint(4)          NOT NULL DEFAULT 0 COMMENT '任务批次状态 0、失败 1、成功',
    `operation_reason`  tinyint(4)          NOT NULL DEFAULT 0 COMMENT '操作原因',
    `flow_info`         text                         DEFAULT NULL COMMENT '流程信息',
    `wf_context`        text                         DEFAULT NULL COMMENT '全局上下文',
    `execution_at`      bigint(13)          NOT NULL DEFAULT 0 COMMENT '任务执行时间',
    `ext_attrs`         varchar(256)        NULL     DEFAULT '' COMMENT '扩展字段',
    `version`           int(11)              NOT NULL DEFAULT 1 COMMENT '版本号',
    `deleted`           tinyint(4)          NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除',
    `create_dt`         datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_dt`         datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
script/sql/sqlserver/snail_job_sqlserver.sql
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : Microsoft SQL Server
 Date: 2024-05-13 23:03:34
 Date: 2024-07-06 12:55:47
*/
@@ -370,7 +370,7 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦',
     'MS_Description', N'通知类型 1、钉钉 2、邮件 3、企业微信 4 é£žä¹¦ 5 webhook',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_notify_recipient',
     'COLUMN', N'notify_type'
@@ -1142,21 +1142,13 @@
-- sj_distributed_lock
CREATE TABLE sj_distributed_lock
(
    id         bigint        NOT NULL PRIMARY KEY IDENTITY,
    name       nvarchar(64)  NOT NULL,
    name       nvarchar(64)  NOT NULL PRIMARY KEY,
    lock_until datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    locked_at  datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    locked_by  nvarchar(255) NOT NULL,
    create_dt  datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_dt  datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP
)
GO
EXEC sp_addextendedproperty
     'MS_Description', N'主键',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_distributed_lock',
     'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
@@ -1745,7 +1737,11 @@
    parent_id      bigint        NOT NULL DEFAULT 0,
    task_status    tinyint       NOT NULL DEFAULT 0,
    retry_count    int           NOT NULL DEFAULT 0,
    mr_stage       tinyint       NULL     DEFAULT NULL,
    leaf           tinyint       NOT NULL DEFAULT '1',
    task_name      nvarchar(255) NOT NULL DEFAULT '',
    client_info    nvarchar(128) NULL     DEFAULT NULL,
    wf_context     nvarchar(max) NULL     DEFAULT NULL,
    result_message nvarchar(max) NOT NULL,
    args_str       nvarchar(max) NULL     DEFAULT NULL,
    args_type      tinyint       NOT NULL DEFAULT 1,
@@ -1819,10 +1815,38 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'动态分片所处阶段 1:map 2:reduce 3:mergeReduce',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job_task',
     'COLUMN', N'mr_stage'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'叶子节点',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job_task',
     'COLUMN', N'leaf'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'任务名称',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job_task',
     'COLUMN', N'task_name'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'客户端地址 clientId#ip:port',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job_task',
     'COLUMN', N'client_info'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'工作流全局上下文',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job_task',
     'COLUMN', N'wf_context'
GO
EXEC sp_addextendedproperty
@@ -2281,6 +2305,7 @@
    executor_timeout int           NOT NULL DEFAULT 0,
    description      nvarchar(256) NOT NULL DEFAULT '',
    flow_info        nvarchar(max) NULL     DEFAULT NULL,
    wf_context       nvarchar(max) NULL     DEFAULT NULL,
    bucket_index     int           NOT NULL DEFAULT 0,
    version          int           NOT NULL,
    ext_attrs        nvarchar(256) NULL     DEFAULT '',
@@ -2377,6 +2402,13 @@
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow',
     'COLUMN', N'flow_info'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'上下文',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow',
     'COLUMN', N'wf_context'
GO
EXEC sp_addextendedproperty
@@ -2590,8 +2622,10 @@
    task_batch_status tinyint       NOT NULL DEFAULT 0,
    operation_reason  tinyint       NOT NULL DEFAULT 0,
    flow_info         nvarchar(max) NULL     DEFAULT NULL,
    wf_context        nvarchar(max) NULL     DEFAULT NULL,
    execution_at      bigint        NOT NULL DEFAULT 0,
    ext_attrs         nvarchar(256) NULL     DEFAULT '',
    version           int           NOT NULL DEFAULT 1,
    deleted           tinyint       NOT NULL DEFAULT 0,
    create_dt         datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_dt         datetime2     NOT NULL DEFAULT CURRENT_TIMESTAMP
@@ -2655,6 +2689,13 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'全局上下文',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow_task_batch',
     'COLUMN', N'wf_context'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'任务执行时间',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow_task_batch',
@@ -2669,6 +2710,13 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'版本号',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow_task_batch',
     'COLUMN', N'version'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'逻辑删除 1、删除',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow_task_batch',