疯狂的狮子Li
2024-05-20 69e3afc7707d467b758858b52d3784947f7a502b
!538 ♥️发布 5.2.0-BETA 公测版本
Merge pull request !538 from 疯狂的狮子Li/dev
已添加162个文件
已重命名3个文件
已删除25个文件
已修改170个文件
28406 ■■■■ 文件已修改
.run/ruoyi-monitor-admin.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-server.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-snailjob-server.run.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/Dockerfile 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/ip2region.xdb 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-bom/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/pom.xml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-job/pom.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-log/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/pom.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/pom.xml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java 649 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/pom.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sms/pom.xml 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopiamSource.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/pom.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/Dockerfile 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/AdminServerConfig.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/Dockerfile 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/pom.xml 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/java/org/dromara/powerjob/PowerJobServerApplication.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application-dev.properties 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application-prod.properties 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application.properties 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/banner.txt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/logback-plus.xml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/Dockerfile 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/pom.xml 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/BroadcastProcessorDemo.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/LogTestProcessor.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/MapProcessorDemo.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/MapReduceProcessorDemo.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/SimpleProcessor.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/StandaloneProcessorDemo.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/TimeoutProcessor.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/workflow/WorkflowStandaloneProcessor.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | 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/ISysRoleService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java 227 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/pom.xml 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/FormTypeEnum.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfDefinitionConfigController.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfFormManageController.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfDefinitionConfig.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfFormManage.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfNodeConfig.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfTaskBackNode.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfDefinitionConfigBo.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfFormManageBo.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfNodeConfigBo.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/VariableVo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfDefinitionConfigVo.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfFormManageVo.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfNodeConfigVo.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java 1120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/handler/TaskTimeoutJobHandler.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfDefinitionConfigMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfFormManageMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfNodeConfigMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfTaskBackNodeMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfDefinitionConfigService.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfFormManageService.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfNodeConfigService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfTaskBackNodeService.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java 424 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java 683 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java 872 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfDefinitionConfigServiceImpl.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfFormManageServiceImpl.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfNodeConfigServiceImpl.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfTaskBackNodeServiceImpl.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/QueryUtils.java 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfDefinitionConfigMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfFormManageMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfNodeConfigMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfTaskBackNodeMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/bin/ry.bat 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/bin/ry.sh 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/bpmn/模型.zip 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/docker-compose.yml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/nginx/conf/nginx.conf 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/flowable.sql 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/flowable.sql 259 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/oracle_ry_vue_5.X.sql 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/oracle_test.sql 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/powerjob.sql 694 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/snail_job_oracle.sql 894 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/flowable.sql 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/postgres_ry_vue_5.X.sql 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/postgres_test.sql 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/powerjob.sql 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/snail_job_postgre.sql 825 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/powerjob.sql 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/ry_vue_5.X.sql 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/snail_job.sql 509 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/flowable.sql 447 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/powerjob.sql 517 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/snail_job_sqlserver.sql 2697 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql 709 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/sqlserver_test.sql 510 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/test.sql 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/oracle/update_5.0-5.1.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/oracle/update_5.1.0-5.1.1.sql 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/oracle/update_5.1.1-5.1.2.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/oracle/update_5.1.2-5.2.0.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/postgres/update_5.0-5.1.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/postgres/update_5.1.1-5.1.2.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/postgres/update_5.1.2-5.2.0.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/sqlserver/update_5.0-5.1.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/sqlserver/update_5.1.2-5.2.0.sql 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/update_5.1.1-5.1.2.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/update/update_5.1.2-5.2.0.sql 5 ●●●●● 补丁 | 查看 | 原始文档 | 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.1.2" />
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
      </settings>
.run/ruoyi-server.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-server:5.1.2" />
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
      </settings>
.run/ruoyi-snailjob-server.run.xml
ÎļþÃû´Ó .run/ruoyi-powerjob-server.run.xml ÐÞ¸Ä
@@ -1,10 +1,10 @@
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="ruoyi-powerjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
  <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-powerjob-server:5.1.2" />
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-powerjob-server/Dockerfile" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
      </settings>
    </deployment>
    <method v="2" />
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.1.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.1-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -27,8 +27,10 @@
## èµžåЩ商
MaxKey - https://gitee.com/dromara/MaxKey <br>
CCFlow - https://gitee.com/opencc/RuoYi-JFlow <br>
MaxKey ä¸šç•Œé¢†å…ˆå•点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow é©°è˜ä½Žä»£ç -流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 è½¯ä»¶å®šåˆ¶å¼€å‘APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 è½¯ä»¶å¼€å‘平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
[如何成为赞助商 åŠ ç¾¤è”ç³»ä½œè€…è¯¦è°ˆ](https://plus-doc.dromara.org/#/common/add_group)
# æœ¬æ¡†æž¶ä¸ŽRuoYi的功能差异
@@ -62,7 +64,7 @@
| åºåˆ—化         | é‡‡ç”¨ Jackson Spring官方内置序列化 é è°±!!!                                                                                    | é‡‡ç”¨ fastjson bugjson è¿œè¿‘闻名                                                           | 
| åˆ†å¸ƒå¼å¹‚ç­‰       | å‚考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | æ‰‹åŠ¨ç¼–å†™æ³¨è§£åŸºäºŽaop实现                                                                      |
| åˆ†å¸ƒå¼é”        | é‡‡ç”¨ Lock4j åº•层基于 Redisson                                                                                           | æ—                                                                                   |
| åˆ†å¸ƒå¼ä»»åŠ¡è°ƒåº¦     | é‡‡ç”¨ PowerJob å¤©ç”Ÿæ”¯æŒåˆ†å¸ƒå¼ ç»Ÿä¸€çš„管理中心                                                                                       | é‡‡ç”¨ Quartz åŸºäºŽæ•°æ®åº“锁性能差 é›†ç¾¤éœ€è¦åšå¾ˆå¤šé…ç½®ä¸Žæ”¹é€                                                    |
| åˆ†å¸ƒå¼ä»»åŠ¡è°ƒåº¦     | é‡‡ç”¨ SnailJob å¤©ç”Ÿæ”¯æŒåˆ†å¸ƒå¼ ç»Ÿä¸€çš„管理中心 æ”¯æŒå¤šç§æ•°æ®åº“ æ”¯æŒåˆ†ç‰‡é‡è¯•DAG任务流等                                                                 | é‡‡ç”¨ Quartz åŸºäºŽæ•°æ®åº“锁性能差 é›†ç¾¤éœ€è¦åšå¾ˆå¤šé…ç½®ä¸Žæ”¹é€                                                    |
| æ–‡ä»¶å­˜å‚¨        | é‡‡ç”¨ Minio åˆ†å¸ƒå¼æ–‡ä»¶å­˜å‚¨ å¤©ç”Ÿæ”¯æŒå¤šæœºã€å¤šç¡¬ç›˜ã€å¤šåˆ†ç‰‡ã€å¤šå‰¯æœ¬å­˜å‚¨<br/>支持权限管理 å®‰å…¨å¯é  æ–‡ä»¶å¯åŠ å¯†å­˜å‚¨                                                     | é‡‡ç”¨ æœ¬æœºæ–‡ä»¶å­˜å‚¨ æ–‡ä»¶è£¸æ¼ æ˜“丢失泄漏 ä¸æ”¯æŒé›†ç¾¤æœ‰å•点效应                                                    |
| äº‘存储         | é‡‡ç”¨ AWS S3 åè®®å®¢æˆ·ç«¯ æ”¯æŒ ä¸ƒç‰›ã€é˜¿é‡Œã€è…¾è®¯ ç­‰ä¸€åˆ‡æ”¯æŒS3协议的厂家                                                                          | ä¸æ”¯æŒ                                                                                |
| çŸ­ä¿¡          | é‡‡ç”¨ sms4j çŸ­ä¿¡èžåˆåŒ… æ”¯æŒæ•°åç§çŸ­ä¿¡åނ家 åªéœ€åœ¨yml配置好厂家密钥即可使用 å¯å¤šåŽ‚å®¶å…±ç”¨                                                                 | ä¸æ”¯æŒ                                                                                |
pom.xml
@@ -13,40 +13,41 @@
    <description>RuoYi-Vue-Plus多租户管理系统</description>
    <properties>
        <revision>5.1.2</revision>
        <spring-boot.version>3.1.7</spring-boot.version>
        <revision>5.2.0-BETA</revision>
        <spring-boot.version>3.2.5</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <spring-boot.mybatis>3.0.3</spring-boot.mybatis>
        <springdoc.version>2.2.0</springdoc.version>
        <mybatis.version>3.5.16</mybatis.version>
        <springdoc.version>2.5.0</springdoc.version>
        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
        <poi.version>5.2.3</poi.version>
        <easyexcel.version>3.3.3</easyexcel.version>
        <easyexcel.version>3.3.4</easyexcel.version>
        <velocity.version>2.3</velocity.version>
        <satoken.version>1.37.0</satoken.version>
        <mybatis-plus.version>3.5.4</mybatis-plus.version>
        <satoken.version>1.38.0</satoken.version>
        <mybatis-plus.version>3.5.6</mybatis-plus.version>
        <p6spy.version>3.9.1</p6spy.version>
        <hutool.version>5.8.22</hutool.version>
        <hutool.version>5.8.27</hutool.version>
        <okhttp.version>4.10.0</okhttp.version>
        <spring-boot-admin.version>3.1.8</spring-boot-admin.version>
        <redisson.version>3.24.3</redisson.version>
        <lock4j.version>2.2.5</lock4j.version>
        <dynamic-ds.version>4.2.0</dynamic-ds.version>
        <spring-boot-admin.version>3.2.3</spring-boot-admin.version>
        <redisson.version>3.29.0</redisson.version>
        <lock4j.version>2.2.7</lock4j.version>
        <dynamic-ds.version>4.3.0</dynamic-ds.version>
        <alibaba-ttl.version>2.14.4</alibaba-ttl.version>
        <powerjob.version>4.3.6</powerjob.version>
        <mapstruct-plus.version>1.3.5</mapstruct-plus.version>
        <snailjob.version>1.0.0-beta1</snailjob.version>
        <mapstruct-plus.version>1.3.6</mapstruct-plus.version>
        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
        <lombok.version>1.18.30</lombok.version>
        <lombok.version>1.18.32</lombok.version>
        <bouncycastle.version>1.76</bouncycastle.version>
        <justauth.version>1.16.6</justauth.version>
        <!-- ç¦»çº¿IP地址定位库 -->
        <ip2region.version>2.7.0</ip2region.version>
        <!-- OSS é…ç½® -->
        <aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version>
        <aws.sdk.version>2.25.15</aws.sdk.version>
        <aws.crt.version>0.29.13</aws.crt.version>
        <!-- SMS é…ç½® -->
        <sms4j.version>2.2.0</sms4j.version>
        <sms4j.version>3.2.1</sms4j.version>
        <!-- é™åˆ¶æ¡†æž¶ä¸­çš„fastjson版本 -->
        <fastjson.version>1.2.83</fastjson.version>
@@ -56,6 +57,9 @@
        <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
        <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
        <!--工作流配置-->
        <flowable.version>7.0.0</flowable.version>
    </properties>
    <profiles>
@@ -106,6 +110,14 @@
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-bom</artifactId>
                <version>${hutool.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-bom</artifactId>
                <version>${flowable.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
@@ -205,14 +217,14 @@
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${spring-boot.mybatis}</version>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
@@ -235,10 +247,23 @@
                <version>${okhttp.version}</version>
            </dependency>
            <!--  AWS SDK for Java 2.x  -->
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-s3</artifactId>
                <version>${aws-java-sdk-s3.version}</version>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>s3</artifactId>
                <version>${aws.sdk.version}</version>
            </dependency>
            <!-- ä½¿ç”¨AWS基于 CRT çš„ S3 å®¢æˆ·ç«¯ -->
            <dependency>
                <groupId>software.amazon.awssdk.crt</groupId>
                <artifactId>aws-crt</artifactId>
                <version>${aws.crt.version}</version>
            </dependency>
            <!-- åŸºäºŽ AWS CRT çš„ S3 å®¢æˆ·ç«¯çš„æ€§èƒ½å¢žå¼ºçš„ S3 ä¼ è¾“管理器 -->
            <dependency>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>s3-transfer-manager</artifactId>
                <version>${aws.sdk.version}</version>
            </dependency>
            <!--短信sms4j-->
            <dependency>
@@ -271,16 +296,16 @@
                <version>${lock4j.version}</version>
            </dependency>
            <!-- PowerJob -->
            <!-- SnailJob Client -->
            <dependency>
                <groupId>tech.powerjob</groupId>
                <artifactId>powerjob-worker-spring-boot-starter</artifactId>
                <version>${powerjob.version}</version>
                <groupId>com.aizuda</groupId>
                <artifactId>snail-job-client-starter</artifactId>
                <version>${snailjob.version}</version>
            </dependency>
            <dependency>
                <groupId>tech.powerjob</groupId>
                <artifactId>powerjob-official-processors</artifactId>
                <version>${powerjob.version}</version>
                <groupId>com.aizuda</groupId>
                <artifactId>snail-job-client-job-core</artifactId>
                <version>${snailjob.version}</version>
            </dependency>
            <dependency>
@@ -336,6 +361,13 @@
            <dependency>
                <groupId>org.dromara</groupId>
                <artifactId>ruoyi-demo</artifactId>
                <version>${revision}</version>
            </dependency>
            <!--  å·¥ä½œæµæ¨¡å—  -->
            <dependency>
                <groupId>org.dromara</groupId>
                <artifactId>ruoyi-workflow</artifactId>
                <version>${revision}</version>
            </dependency>
@@ -398,6 +430,7 @@
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <argLine>-Dfile.encoding=UTF-8</argLine>
                    <!-- æ ¹æ®æ‰“包环境执行对应的@Tag测试方法 -->
                    <groups>${profiles.active}</groups>
                    <!-- æŽ’除标签 -->
ruoyi-admin/Dockerfile
@@ -19,6 +19,6 @@
           # åº”用名称 å¦‚果想区分集群节点监控 æ”¹æˆä¸åŒçš„名称即可
           #-Dskywalking.agent.service_name=ruoyi-server \
           #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
           -jar app.jar \
           -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS}
           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
           -jar app.jar
ruoyi-admin/pom.xml
@@ -55,6 +55,11 @@
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-system</artifactId>
        </dependency>
@@ -75,6 +80,12 @@
            <artifactId>ruoyi-demo</artifactId>
        </dependency>
        <!--  å·¥ä½œæµæ¨¡å—  -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-workflow</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
@@ -91,6 +102,16 @@
            <artifactId>JustAuth</artifactId>
        </dependency>
        <!-- SnailJob client -->
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-job-core</artifactId>
        </dependency>
        <!-- skywalking æ•´åˆ logback -->
<!--        <dependency>-->
<!--            <groupId>org.apache.skywalking</groupId>-->
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -23,9 +23,10 @@
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
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.SysClient;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysClientService;
import org.dromara.system.service.ISysConfigService;
@@ -81,7 +82,7 @@
        // æŽˆæƒç±»åž‹å’Œå®¢æˆ·ç«¯id
        String clientId = loginBody.getClientId();
        String grantType = loginBody.getGrantType();
        SysClient client = clientService.queryByClientId(clientId);
        SysClientVo client = clientService.queryByClientId(clientId);
        // æŸ¥è¯¢ä¸åˆ° client æˆ– client å†…不包含 grantType
        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
            log.info("客户端id: {} è®¤è¯ç±»åž‹ï¼š{} å¼‚常!.", clientId, grantType);
@@ -96,7 +97,10 @@
        Long userId = LoginHelper.getUserId();
        scheduledExecutorService.schedule(() -> {
            WebSocketUtils.sendMessage(userId, "欢迎登录RuoYi-Vue-Plus后台管理系统");
            WebSocketMessageDto dto = new WebSocketMessageDto();
            dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
            dto.setSessionKeys(List.of(userId));
            WebSocketUtils.publishMessage(dto);
        }, 3, TimeUnit.SECONDS);
        return R.ok(loginVo);
    }
ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
@@ -5,6 +5,9 @@
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
@@ -21,11 +24,7 @@
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.web.domain.vo.CaptchaVo;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -66,11 +65,11 @@
        String templateId = "";
        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
        map.put("code", code);
        SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
        SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
        SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
        if (!"OK".equals(smsResponse.getCode())) {
        if (!smsResponse.isSuccess()) {
            log.error("验证码短信发送异常 => {}", smsResponse);
            return R.fail(smsResponse.getMessage());
            return R.fail(smsResponse.getData().toString());
        }
        return R.ok();
    }
@@ -121,6 +120,7 @@
        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
        captcha.setGenerator(codeGenerator);
        captcha.createCode();
        // å¦‚果是数学验证码,使用SpEL表达式处理验证码结果
        String code = captcha.getCode();
        if (isMath) {
            ExpressionParser parser = new SpelExpressionParser();
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java
@@ -13,10 +13,19 @@
@AutoMapper(target = SysTenantVo.class)
public class TenantListVo {
    /**
     * ç§Ÿæˆ·ç¼–号
     */
    private String tenantId;
    /**
     * ä¼ä¸šåç§°
     */
    private String companyName;
    /**
     * åŸŸå
     */
    private String domain;
}
ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java
@@ -10,7 +10,6 @@
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.domain.dto.UserOnlineDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
@@ -18,6 +17,7 @@
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Component;
@@ -43,7 +43,6 @@
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = ServletUtils.getClientIP();
        LoginUser user = LoginHelper.getLoginUser();
        UserOnlineDTO dto = new UserOnlineDTO();
        dto.setIpaddr(ip);
        dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
@@ -51,25 +50,29 @@
        dto.setOs(userAgent.getOs().getName());
        dto.setLoginTime(System.currentTimeMillis());
        dto.setTokenId(tokenValue);
        dto.setUserName(user.getUsername());
        dto.setClientKey(user.getClientKey());
        dto.setDeviceType(user.getDeviceType());
        dto.setDeptName(user.getDeptName());
        if(tokenConfig.getTimeout() == -1) {
            RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
        } else {
            RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
        }
        String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
        String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
        dto.setUserName(username);
        dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
        dto.setDeviceType(loginModel.getDevice());
        dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
        TenantHelper.dynamic(tenantId, () -> {
            if(tokenConfig.getTimeout() == -1) {
                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
            } else {
                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
            }
        });
        // è®°å½•登录日志
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setTenantId(user.getTenantId());
        logininforEvent.setUsername(user.getUsername());
        logininforEvent.setTenantId(tenantId);
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
        logininforEvent.setMessage(MessageUtils.message("user.login.success"));
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
        // æ›´æ–°ç™»å½•信息
        loginService.recordLoginInfo(user.getUserId(), ip);
        loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
        log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
    }
ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java
@@ -4,6 +4,7 @@
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.web.domain.vo.LoginVo;
/**
@@ -17,8 +18,13 @@
    /**
     * ç™»å½•
     *
     * @param body      ç™»å½•对象
     * @param client    æŽˆæƒç®¡ç†è§†å›¾å¯¹è±¡
     * @param grantType æŽˆæƒç±»åž‹
     * @return ç™»å½•验证信息
     */
    static LoginVo login(String body, SysClient client, String grantType) {
    static LoginVo login(String body, SysClientVo client, String grantType) {
        // æŽˆæƒç±»åž‹å’Œå®¢æˆ·ç«¯id
        String beanName = grantType + BASE_NAME;
        if (!SpringUtils.containsBean(beanName)) {
@@ -30,7 +36,11 @@
    /**
     * ç™»å½•
     *
     * @param body   ç™»å½•对象
     * @param client æŽˆæƒç®¡ç†è§†å›¾å¯¹è±¡
     * @return ç™»å½•验证信息
     */
    LoginVo login(String body, SysClient client);
    LoginVo login(String body, SysClientVo client);
}
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -5,6 +5,7 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
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;
@@ -15,6 +16,7 @@
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.TenantStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.*;
import org.dromara.common.log.event.LogininforEvent;
@@ -25,13 +27,9 @@
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysSocialBo;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.domain.vo.*;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysPermissionService;
import org.dromara.system.service.ISysSocialService;
import org.dromara.system.service.ISysTenantService;
import org.dromara.system.service.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -59,6 +57,8 @@
    private final ISysTenantService tenantService;
    private final ISysPermissionService permissionService;
    private final ISysSocialService sysSocialService;
    private final ISysRoleService roleService;
    private final ISysDeptService deptService;
    private final SysUserMapper userMapper;
@@ -66,20 +66,28 @@
     * ç»‘定第三方用户
     *
     * @param authUserData æŽˆæƒå“åº”实体
     * @return ç»Ÿä¸€å“åº”实体
     */
    @Lock4j
    public void socialRegister(AuthUser authUserData) {
        String authId = authUserData.getSource() + authUserData.getUuid();
        // ç¬¬ä¸‰æ–¹ç”¨æˆ·ä¿¡æ¯
        SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class);
        BeanUtil.copyProperties(authUserData.getToken(), bo);
        bo.setUserId(LoginHelper.getUserId());
        Long userId = LoginHelper.getUserId();
        bo.setUserId(userId);
        bo.setAuthId(authId);
        bo.setOpenId(authUserData.getUuid());
        bo.setUserName(authUserData.getUsername());
        bo.setNickName(authUserData.getNickname());
        List<SysSocialVo> checkList = sysSocialService.selectByAuthId(authId);
        if (CollUtil.isNotEmpty(checkList)) {
            throw new ServiceException("此三方账号已经被绑定!");
        }
        // æŸ¥è¯¢æ˜¯å¦å·²ç»ç»‘定用户
        List<SysSocialVo> list = sysSocialService.selectByAuthId(authId);
        SysSocialBo params = new SysSocialBo();
        params.setUserId(userId);
        params.setSource(bo.getSource());
        List<SysSocialVo> list = sysSocialService.queryList(params);
        if (CollUtil.isEmpty(list)) {
            // æ²¡æœ‰ç»‘定用户, æ–°å¢žç”¨æˆ·ä¿¡æ¯
            sysSocialService.insertByBo(bo);
@@ -87,6 +95,8 @@
            // æ›´æ–°ç”¨æˆ·ä¿¡æ¯
            bo.setId(list.get(0).getId());
            sysSocialService.updateByBo(bo);
            // å¦‚果要绑定的平台账号已经被绑定过了 æ˜¯å¦æŠ›å¼‚常自行决断
            // throw new ServiceException("此平台账号已经被绑定!");
        }
    }
@@ -132,7 +142,6 @@
        SpringUtils.context().publishEvent(logininforEvent);
    }
    /**
     * æž„建登录用户
     */
@@ -146,9 +155,16 @@
        loginUser.setUserType(user.getUserType());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
        loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
        List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
        loginUser.setRoles(roles);
        TenantHelper.dynamic(user.getTenantId(), () -> {
            SysDeptVo dept = null;
            if (ObjectUtil.isNotNull(user.getDeptId())) {
                dept = deptService.selectDeptById(user.getDeptId());
            }
            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;
    }
ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java
@@ -1,7 +1,6 @@
package org.dromara.web.service;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants;
@@ -61,8 +60,7 @@
        boolean exist = TenantHelper.dynamic(tenantId, () -> {
            return userMapper.exists(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserName, sysUser.getUserName())
                .ne(ObjectUtil.isNotNull(sysUser.getUserId()), SysUser::getUserId, sysUser.getUserId()));
                .eq(SysUser::getUserName, sysUser.getUserName()));
        });
        if (exist) {
            throw new UserException("user.register.save.error", username);
@@ -82,7 +80,7 @@
     * @param uuid     å”¯ä¸€æ ‡è¯†
     */
    public void validateCaptcha(String tenantId, String username, String code, String uuid) {
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
@@ -23,6 +23,7 @@
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;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
@@ -44,7 +45,7 @@
    private final SysUserMapper userMapper;
    @Override
    public LoginVo login(String body, SysClient client) {
    public LoginVo login(String body, SysClientVo client) {
        EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
        ValidatorUtils.validate(loginBody);
        String tenantId = loginBody.getTenantId();
@@ -90,9 +91,7 @@
    private SysUserVo loadUserByEmail(String tenantId, String email) {
        return TenantHelper.dynamic(tenantId, () -> {
            SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getEmail, SysUser::getStatus)
                .eq(SysUser::getEmail, email));
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", email);
                throw new UserException("user.not.exists", email);
@@ -100,7 +99,7 @@
                log.info("登录用户:{} å·²è¢«åœç”¨.", email);
                throw new UserException("user.blocked", email);
            }
            return userMapper.selectUserByEmail(email);
            return user;
        });
    }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -26,6 +26,7 @@
import org.dromara.common.web.config.properties.CaptchaProperties;
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;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
@@ -48,7 +49,7 @@
    private final SysUserMapper userMapper;
    @Override
    public LoginVo login(String body, SysClient client) {
    public LoginVo login(String body, SysClientVo client) {
        PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
        ValidatorUtils.validate(loginBody);
        String tenantId = loginBody.getTenantId();
@@ -109,9 +110,7 @@
    private SysUserVo loadUserByUsername(String tenantId, String username) {
        return TenantHelper.dynamic(tenantId, () -> {
            SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getUserName, SysUser::getStatus)
                .eq(SysUser::getUserName, username));
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", username);
                throw new UserException("user.not.exists", username);
@@ -119,7 +118,7 @@
                log.info("登录用户:{} å·²è¢«åœç”¨.", username);
                throw new UserException("user.blocked", username);
            }
            return userMapper.selectUserByUserName(username);
            return user;
        });
    }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
@@ -23,6 +23,7 @@
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;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
@@ -44,7 +45,7 @@
    private final SysUserMapper userMapper;
    @Override
    public LoginVo login(String body, SysClient client) {
    public LoginVo login(String body, SysClientVo client) {
        SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
        ValidatorUtils.validate(loginBody);
        String tenantId = loginBody.getTenantId();
@@ -90,9 +91,7 @@
    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
        return TenantHelper.dynamic(tenantId, () -> {
            SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getPhonenumber, SysUser::getStatus)
                .eq(SysUser::getPhonenumber, phonenumber));
            SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", phonenumber);
                throw new UserException("user.not.exists", phonenumber);
@@ -100,7 +99,7 @@
                log.info("登录用户:{} å·²è¢«åœç”¨.", phonenumber);
                throw new UserException("user.blocked", phonenumber);
            }
            return userMapper.selectUserByPhonenumber(phonenumber);
            return user;
        });
    }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -7,7 +7,6 @@
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
@@ -23,8 +22,7 @@
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
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.SysSocialVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
@@ -59,7 +57,7 @@
     * @param client   å®¢æˆ·ç«¯ä¿¡æ¯
     */
    @Override
    public LoginVo login(String body, SysClient client) {
    public LoginVo login(String body, SysClientVo client) {
        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
        ValidatorUtils.validate(loginBody);
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
@@ -83,11 +81,16 @@
        if (CollUtil.isEmpty(list)) {
            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
        }
        Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
        if (opt.isEmpty()) {
            throw new ServiceException("对不起,你没有权限登录当前租户!");
        SysSocialVo social;
        if (TenantHelper.isEnable()) {
            Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
            if (opt.isEmpty()) {
                throw new ServiceException("对不起,你没有权限登录当前租户!");
            }
            social = opt.get();
        } else {
            social = list.get(0);
        }
        SysSocialVo social = opt.get();
        // æŸ¥æ‰¾ç”¨æˆ·
        SysUserVo user = loadUser(social.getTenantId(), social.getUserId());
@@ -114,9 +117,7 @@
    private SysUserVo loadUser(String tenantId, Long userId) {
        return TenantHelper.dynamic(tenantId, () -> {
            SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getUserName, SysUser::getStatus)
                .eq(SysUser::getUserId, userId));
            SysUserVo user = userMapper.selectVoById(userId);
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户:{} ä¸å­˜åœ¨.", "");
                throw new UserException("user.not.exists", "");
@@ -124,7 +125,7 @@
                log.info("登录用户:{} å·²è¢«åœç”¨.", "");
                throw new UserException("user.blocked", "");
            }
            return userMapper.selectUserByUserName(user.getUserName());
            return user;
        });
    }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
@@ -12,6 +12,7 @@
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
@@ -19,7 +20,7 @@
import org.springframework.stereotype.Service;
/**
 * é‚®ä»¶è®¤è¯ç­–ç•¥
 * å°ç¨‹åºè®¤è¯ç­–ç•¥
 *
 * @author Michelle.Chung
 */
@@ -31,7 +32,7 @@
    private final SysLoginService loginService;
    @Override
    public LoginVo login(String body, SysClient client) {
    public LoginVo login(String body, SysClientVo client) {
        XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
        ValidatorUtils.validate(loginBody);
        // xcxCode ä¸º å°ç¨‹åºè°ƒç”¨ wx.login æŽˆæƒåŽèŽ·å–
ruoyi-admin/src/main/resources/application-dev.yml
@@ -8,21 +8,19 @@
  username: ruoyi
  password: 123456
--- # powerjob é…ç½®
powerjob:
  worker:
    # å¦‚何开启调度中心请查看文档教程
    enabled: false
    # éœ€è¦å…ˆåœ¨ powerjob ç™»å½•页执行应用注册后才能使用
    app-name: ruoyi-worker
    allow-lazy-connect-server: false
    max-appended-wf-context-length: 4096
    max-result-length: 4096
    # 28080 ç«¯å£ éšç€ä¸»åº”用端口飘逸 é¿å…é›†ç¾¤å†²çª
    port: 2${server.port}
    protocol: http
    server-address: 127.0.0.1:7700
    store-strategy: disk
--- # snail-job é…ç½®
snail-job:
  enabled: true
  # éœ€è¦åœ¨ SnailJob åŽå°ç»„管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
  group-name: "ruoyi_group"
  # SnailJob æŽ¥å…¥éªŒè¯ä»¤ç‰Œ è¯¦è§ script/sql/snail_job.sql `sj_group_config` è¡¨
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
  server:
    host: 127.0.0.1
    port: 1788
  # è¯¦è§ script/sql/snail_job.sql `sj_namespace` è¡¨
  namespace: ${spring.profiles.active}
--- # æ•°æ®æºé…ç½®
spring:
@@ -43,7 +41,7 @@
          driverClassName: com.mysql.cj.jdbc.Driver
          # jdbc æ‰€æœ‰å‚数配置参考 https://lionli.blog.csdn.net/article/details/122018562
          # rewriteBatchedStatements=true æ‰¹å¤„理优化 å¤§å¹…提升批量插入更新删除性能(对数据库有性能损耗 ä½¿ç”¨æ‰¹é‡æ“ä½œåº”考虑性能问题)
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          username: root
          password: root
        # ä»Žåº“数据源
@@ -51,7 +49,7 @@
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          username:
          password:
#        oracle:
@@ -149,36 +147,40 @@
  connectionTimeout: 0
--- # sms çŸ­ä¿¡ æ”¯æŒ é˜¿é‡Œäº‘ è…¾è®¯äº‘ äº‘片 ç­‰ç­‰å„式各样的短信服务商
# https://wind.kim/doc/start æ–‡æ¡£åœ°å€ å„个厂商可同时使用
# https://sms4j.com/doc3/ å·®å¼‚配置文档地址 æ”¯æŒå•厂商多配置,可以配置多个同时使用
sms:
  # é˜¿é‡Œäº‘ dysmsapi.aliyuncs.com
  alibaba:
    #请求地址 é»˜è®¤ä¸º dysmsapi.aliyuncs.com å¦‚无特殊改变可以不用设置
    requestUrl: dysmsapi.aliyuncs.com
    #阿里云的accessKey
    accessKeyId: xxxxxxx
    #阿里云的accessKeySecret
    accessKeySecret: xxxxxxx
    #短信签名
    signature: æµ‹è¯•
  tencent:
    #请求地址默认为 sms.tencentcloudapi.com å¦‚无特殊改变可不用设置
    requestUrl: sms.tencentcloudapi.com
    #腾讯云的accessKey
    accessKeyId: xxxxxxx
    #腾讯云的accessKeySecret
    accessKeySecret: xxxxxxx
    #短信签名
    signature: æµ‹è¯•
    #短信sdkAppId
    sdkAppId: appid
    #地域信息默认为 ap-guangzhou å¦‚无特殊改变可不用设置
    territory: ap-guangzhou
  # é…ç½®æºç±»åž‹ç”¨äºŽæ ‡å®šé…ç½®æ¥æº(interface,yaml)
  config-type: yaml
  # ç”¨äºŽæ ‡å®šyml中的配置是否开启短信拦截,接口配置不受此限制
  restricted: true
  # çŸ­ä¿¡æ‹¦æˆªé™åˆ¶å•手机号每分钟最大发送,只对开启了拦截的配置有效
  minute-max: 1
  # çŸ­ä¿¡æ‹¦æˆªé™åˆ¶å•手机号每日最大发送量,只对开启了拦截的配置有效
  account-max: 30
  # ä»¥ä¸‹é…ç½®æ¥è‡ªäºŽ org.dromara.sms4j.provider.config.BaseConfig类中
  blends:
    # å”¯ä¸€ID ç”¨äºŽå‘送短信寻找具体配置 éšä¾¿å®šä¹‰åˆ«ç”¨ä¸­æ–‡å³å¯
    # å¯ä»¥åŒæ—¶å­˜åœ¨ä¸¤ä¸ªç›¸åŒåނ商 ä¾‹å¦‚: ali1 ali2 ä¸¤ä¸ªä¸åŒçš„阿里短信账号 ä¹Ÿå¯ç”¨äºŽåŒºåˆ†ç§Ÿæˆ·
    config1:
      # æ¡†æž¶å®šä¹‰çš„厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
      supplier: alibaba
      # æœ‰äº›ç§°ä¸ºaccessKey有些称之为apiKey,也有称为sdkKey或者appId。
      access-key-id: æ‚¨çš„accessKey
      # ç§°ä¸ºaccessSecret有些称之为apiSecret
      access-key-secret: æ‚¨çš„accessKeySecret
      signature: æ‚¨çš„短信签名
      sdk-app-id: æ‚¨çš„sdkAppId
    config2:
      # åŽ‚å•†æ ‡è¯†ï¼Œæ ‡å®šæ­¤é…ç½®æ˜¯å“ªä¸ªåŽ‚å•†ï¼Œè¯¦ç»†è¯·çœ‹åŽ‚å•†æ ‡è¯†ä»‹ç»éƒ¨åˆ†
      supplier: tencent
      access-key-id: æ‚¨çš„accessKey
      access-key-secret: æ‚¨çš„accessKeySecret
      signature: æ‚¨çš„短信签名
      sdk-app-id: æ‚¨çš„sdkAppId
--- # ä¸‰æ–¹æŽˆæƒ
justauth:
  enabled: true
  # å‰ç«¯å¤–网访问地址
  address: http://localhost:80
  type:
@@ -189,6 +191,13 @@
      client-id: 876892492581044224
      client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
      redirect-uri: ${justauth.address}/social-callback?source=maxkey
    topiam:
      # topiam æœåŠ¡å™¨åœ°å€
      server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
      client-id: 449c4*********937************759
      client-secret: ac7***********1e0************28d
      redirect-uri: ${justauth.address}/social-callback?source=topiam
      scopes: [openid, email, phone, profile]
    qq:
      client-id: 10**********6
      client-secret: 1f7d08**********5b7**********29e
ruoyi-admin/src/main/resources/application-prod.yml
@@ -11,21 +11,18 @@
  username: ruoyi
  password: 123456
--- # powerjob é…ç½®
powerjob:
  worker:
    # å¦‚何开启调度中心请查看文档教程
    enabled: false
    # éœ€è¦å…ˆåœ¨ powerjob ç™»å½•页执行应用注册后才能使用
    app-name: ruoyi-worker
    allow-lazy-connect-server: false
    max-appended-wf-context-length: 4096
    max-result-length: 4096
    # 28080 ç«¯å£ éšç€ä¸»åº”用端口飘逸 é¿å…é›†ç¾¤å†²çª
    port: 2${server.port}
    protocol: http
    server-address: 127.0.0.1:7700
    store-strategy: disk
--- # snail-job é…ç½®
snail-job:
  enabled: false
  # éœ€è¦åœ¨ SnailJob åŽå°ç»„管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
  group-name: "ruoyi_group"
  # SnailJob æŽ¥å…¥éªŒè¯ä»¤ç‰Œ è¯¦è§ script/sql/snail_job.sql `sj_group_config` è¡¨
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
  server:
    host: 127.0.0.1
    port: 1788
  # è¯¦è§ script/sql/snail_job.sql `sj_namespace` è¡¨
  namespace: ${spring.profiles.active}
--- # æ•°æ®æºé…ç½®
spring:
@@ -46,7 +43,7 @@
          driverClassName: com.mysql.cj.jdbc.Driver
          # jdbc æ‰€æœ‰å‚数配置参考 https://lionli.blog.csdn.net/article/details/122018562
          # rewriteBatchedStatements=true æ‰¹å¤„理优化 å¤§å¹…提升批量插入更新删除性能(对数据库有性能损耗 ä½¿ç”¨æ‰¹é‡æ“ä½œåº”考虑性能问题)
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          username: root
          password: root
        # ä»Žåº“数据源
@@ -54,7 +51,7 @@
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
          username:
          password:
#        oracle:
@@ -152,35 +149,39 @@
  connectionTimeout: 0
--- # sms çŸ­ä¿¡ æ”¯æŒ é˜¿é‡Œäº‘ è…¾è®¯äº‘ äº‘片 ç­‰ç­‰å„式各样的短信服务商
# https://wind.kim/doc/start æ–‡æ¡£åœ°å€ å„个厂商可同时使用
# https://sms4j.com/doc3/ å·®å¼‚配置文档地址 æ”¯æŒå•厂商多配置,可以配置多个同时使用
sms:
  # é˜¿é‡Œäº‘ dysmsapi.aliyuncs.com
  alibaba:
    #请求地址 é»˜è®¤ä¸º dysmsapi.aliyuncs.com å¦‚无特殊改变可以不用设置
    requestUrl: dysmsapi.aliyuncs.com
    #阿里云的accessKey
    accessKeyId: xxxxxxx
    #阿里云的accessKeySecret
    accessKeySecret: xxxxxxx
    #短信签名
    signature: æµ‹è¯•
  tencent:
    #请求地址默认为 sms.tencentcloudapi.com å¦‚无特殊改变可不用设置
    requestUrl: sms.tencentcloudapi.com
    #腾讯云的accessKey
    accessKeyId: xxxxxxx
    #腾讯云的accessKeySecret
    accessKeySecret: xxxxxxx
    #短信签名
    signature: æµ‹è¯•
    #短信sdkAppId
    sdkAppId: appid
    #地域信息默认为 ap-guangzhou å¦‚无特殊改变可不用设置
    territory: ap-guangzhou
  # é…ç½®æºç±»åž‹ç”¨äºŽæ ‡å®šé…ç½®æ¥æº(interface,yaml)
  config-type: yaml
  # ç”¨äºŽæ ‡å®šyml中的配置是否开启短信拦截,接口配置不受此限制
  restricted: true
  # çŸ­ä¿¡æ‹¦æˆªé™åˆ¶å•手机号每分钟最大发送,只对开启了拦截的配置有效
  minute-max: 1
  # çŸ­ä¿¡æ‹¦æˆªé™åˆ¶å•手机号每日最大发送量,只对开启了拦截的配置有效
  account-max: 30
  # ä»¥ä¸‹é…ç½®æ¥è‡ªäºŽ org.dromara.sms4j.provider.config.BaseConfig类中
  blends:
    # å”¯ä¸€ID ç”¨äºŽå‘送短信寻找具体配置 éšä¾¿å®šä¹‰åˆ«ç”¨ä¸­æ–‡å³å¯
    # å¯ä»¥åŒæ—¶å­˜åœ¨ä¸¤ä¸ªç›¸åŒåނ商 ä¾‹å¦‚: ali1 ali2 ä¸¤ä¸ªä¸åŒçš„阿里短信账号 ä¹Ÿå¯ç”¨äºŽåŒºåˆ†ç§Ÿæˆ·
    config1:
      # æ¡†æž¶å®šä¹‰çš„厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
      supplier: alibaba
      # æœ‰äº›ç§°ä¸ºaccessKey有些称之为apiKey,也有称为sdkKey或者appId。
      access-key-id: æ‚¨çš„accessKey
      # ç§°ä¸ºaccessSecret有些称之为apiSecret
      access-key-secret: æ‚¨çš„accessKeySecret
      signature: æ‚¨çš„短信签名
      sdk-app-id: æ‚¨çš„sdkAppId
    config2:
      # åŽ‚å•†æ ‡è¯†ï¼Œæ ‡å®šæ­¤é…ç½®æ˜¯å“ªä¸ªåŽ‚å•†ï¼Œè¯¦ç»†è¯·çœ‹åŽ‚å•†æ ‡è¯†ä»‹ç»éƒ¨åˆ†
      supplier: tencent
      access-key-id: æ‚¨çš„accessKey
      access-key-secret: æ‚¨çš„accessKeySecret
      signature: æ‚¨çš„短信签名
      sdk-app-id: æ‚¨çš„sdkAppId
--- # ä¸‰æ–¹æŽˆæƒ
justauth:
  enabled: true
  # å‰ç«¯å¤–网访问地址
  address: http://localhost:80
  type:
@@ -191,6 +192,13 @@
      client-id: 876892492581044224
      client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
      redirect-uri: ${justauth.address}/social-callback?source=maxkey
    topiam:
      # topiam æœåŠ¡å™¨åœ°å€
      server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
      client-id: 449c4*********937************759
      client-secret: ac7***********1e0************28d
      redirect-uri: ${justauth.address}/social-callback?source=topiam
      scopes: [ openid, email, phone, profile ]
    qq:
      client-id: 10**********6
      client-secret: 1f7d08**********5b7**********29e
ruoyi-admin/src/main/resources/application.yml
@@ -5,7 +5,7 @@
  # ç‰ˆæœ¬
  version: ${revision}
  # ç‰ˆæƒå¹´ä»½
  copyrightYear: 2023
  copyrightYear: 2024
captcha:
  enable: true
@@ -46,7 +46,7 @@
  level:
    org.dromara: @logging.level@
    org.springframework: warn
    tech.powerjob.worker.background: warn
    org.mybatis.spring.mapper: error
  config: classpath:logback-plus.xml
# ç”¨æˆ·é…ç½®
@@ -61,6 +61,10 @@
spring:
  application:
    name: ${ruoyi.name}
  threads:
    # å¼€å¯è™šæ‹Ÿçº¿ç¨‹ ä»…jdk21可用
    virtual:
      enabled: false
  # èµ„源信息
  messages:
    # å›½é™…化资源文件路径
@@ -75,6 +79,8 @@
      # è®¾ç½®æ€»ä¸Šä¼ çš„æ–‡ä»¶å¤§å°
      max-request-size: 20MB
  mvc:
    # è®¾ç½®é™æ€èµ„源路径 é˜²æ­¢æ‰€æœ‰è¯·æ±‚都去查静态资源
    static-path-pattern: /static/**
    format:
      date-time: yyyy-MM-dd HH:mm:ss
  jackson:
@@ -138,8 +144,7 @@
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
  # ä¸æ”¯æŒå¤šåŒ…, å¦‚有需要可在注解配置 æˆ– æå‡æ‰«åŒ…等级
  # ä¾‹å¦‚ com.**.**.mapper
  # å¤šåŒ…名使用 ä¾‹å¦‚ org.dromara.**.mapper,org.xxx.**.mapper
  mapperPackage: org.dromara.**.mapper
  # å¯¹åº”çš„ XML æ–‡ä»¶ä½ç½®
  mapperLocations: classpath*:mapper/**/*Mapper.xml
@@ -226,6 +231,7 @@
  urlPatterns: /system/*,/monitor/*,/tool/*
# å…¨å±€çº¿ç¨‹æ± ç›¸å…³é…ç½®
# å¦‚使用JDK21请直接使用虚拟线程 ä¸è¦å¼€å¯æ­¤é…ç½®
thread-pool:
  # æ˜¯å¦å¼€å¯çº¿ç¨‹æ± 
  enabled: false
@@ -261,3 +267,21 @@
  path: /resource/websocket
  # è®¾ç½®è®¿é—®æºåœ°å€
  allowedOrigins: '*'
--- #flowable配置
flowable:
  async-executor-activate: false #关闭定时任务JOB
  #  å°†databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
  database-schema-update: true
  activity-font-name: å®‹ä½“
  label-font-name: å®‹ä½“
  annotation-font-name: å®‹ä½“
  # å…³é—­å„个模块生成表,目前只使用工作流基础表
  idm:
    enabled: false
  cmmn:
    enabled: false
  dmn:
    enabled: false
  app:
    enabled: false
ruoyi-admin/src/main/resources/ip2region.xdb
Binary files differ
ruoyi-common/ruoyi-common-bom/pom.xml
@@ -14,7 +14,7 @@
    </description>
    <properties>
        <revision>5.1.2</revision>
        <revision>5.2.0-BETA</revision>
    </properties>
    <dependencyManagement>
ruoyi-common/ruoyi-common-core/pom.xml
@@ -94,6 +94,11 @@
            <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/config/ApplicationConfig.java
@@ -2,6 +2,7 @@
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
/**
 * ç¨‹åºæ³¨è§£é…ç½®
@@ -11,6 +12,7 @@
@AutoConfiguration
// è¡¨ç¤ºé€šè¿‡aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig {
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
@@ -5,18 +5,19 @@
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
 * å¼‚步配置
 * <p>
 * å¦‚果未使用虚拟线程则生效
 *
 * @author Lion Li
 */
@EnableAsync(proxyTargetClass = true)
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
@@ -25,6 +26,9 @@
     */
    @Override
    public Executor getAsyncExecutor() {
        if(SpringUtils.isVirtual()) {
            return new VirtualThreadTaskExecutor("async-");
        }
        return SpringUtils.getBean("scheduledExecutorService");
    }
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -36,6 +36,11 @@
    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
    /**
     * å®¢æˆ·ç«¯
     */
    String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
    /**
     * ç”¨æˆ·è´¦æˆ·
     */
    String SYS_USER_NAME = "sys_user_name#30d";
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package org.dromara.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
 * å¸¸ç”¨æ­£åˆ™è¡¨è¾¾å¼å­—符串
 * <p>
 * å¸¸ç”¨æ­£åˆ™è¡¨è¾¾å¼é›†åˆï¼Œæ›´å¤šæ­£åˆ™è§: https://any86.github.io/any-rule/
 *
 * @author Feng
 */
public interface RegexConstants extends RegexPool {
    /**
     * å­—典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
     */
    String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
    /**
     * æƒé™æ ‡è¯†å¿…须符合 tool:build:list æ ¼å¼ï¼Œæˆ–者空字符串
     */
    String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
    /**
     * èº«ä»½è¯å·ç ï¼ˆåŽ6位)
     */
    String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
    /**
     * QQ号码
     */
    String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
    /**
     * é‚®æ”¿ç¼–码
     */
    String POSTAL_CODE = "^[1-9]\\d{5}$";
    /**
     * æ³¨å†Œè´¦å·
     */
    String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
    /**
     * å¯†ç ï¼šåŒ…含至少8个字符,包括大写字母、小写字母、数字和特殊字符
     */
    String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
    /**
     * é€šç”¨çŠ¶æ€ï¼ˆ0表示正常,1表示停用)
     */
    String STATUS = "^[01]$";
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
 * OSS对象
 *
 * @author Lion Li
 */
@Data
@NoArgsConstructor
public class OssDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * å¯¹è±¡å­˜å‚¨ä¸»é”®
     */
    private Long ossId;
    /**
     * æ–‡ä»¶å
     */
    private String fileName;
    /**
     * åŽŸå
     */
    private String originalName;
    /**
     * æ–‡ä»¶åŽç¼€å
     */
    private String fileSuffix;
    /**
     * URL地址
     */
    private String url;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java
@@ -3,6 +3,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
@@ -15,6 +16,9 @@
@NoArgsConstructor
public class RoleDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * è§’色ID
     */
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * ç”¨æˆ·
 *
 * @author Michelle.Chung
 */
@Data
@NoArgsConstructor
public class UserDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ç”¨æˆ·ID
     */
    private Long userId;
    /**
     * éƒ¨é—¨ID
     */
    private Long deptId;
    /**
     * ç”¨æˆ·è´¦å·
     */
    private String userName;
    /**
     * ç”¨æˆ·æ˜µç§°
     */
    private String nickName;
    /**
     * ç”¨æˆ·ç±»åž‹ï¼ˆsys_user系统用户)
     */
    private String userType;
    /**
     * ç”¨æˆ·é‚®ç®±
     */
    private String email;
    /**
     * æ‰‹æœºå·ç 
     */
    private String phonenumber;
    /**
     * ç”¨æˆ·æ€§åˆ«ï¼ˆ0男 1女 2未知)
     */
    private String sex;
    /**
     * å¸å·çŠ¶æ€ï¼ˆ0正常 1停用)
     */
    private String status;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java
@@ -14,7 +14,6 @@
 *
 * @author Lion Li
 */
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
@@ -38,6 +37,11 @@
    private Long deptId;
    /**
     * éƒ¨é—¨ç±»åˆ«ç¼–码
     */
    private String deptCategory;
    /**
     * éƒ¨é—¨å
     */
    private String deptName;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
@@ -1,9 +1,6 @@
package org.dromara.common.core.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.*;
import java.io.Serial;
@@ -45,17 +42,9 @@
        this.code = code;
    }
    public String getDetailMessage() {
        return detailMessage;
    }
    @Override
    public String getMessage() {
        return message;
    }
    public Integer getCode() {
        return code;
    }
    public ServiceException setMessage(String message) {
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.dromara.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.dromara.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
 * æ­£åˆ™è¡¨è¾¾å¼æ¨¡å¼æ± å·¥åŽ‚
 * <p>初始化的时候将正则表达式加入缓存池当中</p>
 * <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p>
 *
 * @author 21001
 */
public class RegexPatternPoolFactory extends PatternPool {
    /**
     * å­—典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
     */
    public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
    /**
     * èº«ä»½è¯å·ç ï¼ˆåŽ6位)
     */
    public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
    /**
     * QQ号码
     */
    public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
    /**
     * é‚®æ”¿ç¼–码
     */
    public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
    /**
     * æ³¨å†Œè´¦å·
     */
    public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
    /**
     * å¯†ç ï¼šåŒ…含至少8个字符,包括大写字母、小写字母、数字和特殊字符
     */
    public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
    /**
     * é€šç”¨çŠ¶æ€ï¼ˆ0表示正常,1表示停用)
     */
    public static final Pattern STATUS = get(RegexConstants.STATUS);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java
@@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.OssDTO;
import java.util.List;
/**
 * é€šç”¨ OSS服务
 *
@@ -15,4 +19,11 @@
     */
    String selectUrlByIds(String ossIds);
    /**
     * é€šè¿‡ossId查询列表
     *
     * @param ossIds ossId串逗号分隔
     * @return åˆ—表
     */
    List<OssDTO> selectByIds(String ossIds);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
@@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.UserDTO;
import java.util.List;
/**
 * é€šç”¨ ç”¨æˆ·æœåŠ¡
 *
@@ -19,8 +23,47 @@
     * é€šè¿‡ç”¨æˆ·ID查询用户账户
     *
     * @param userId ç”¨æˆ·ID
     * @return ç”¨æˆ·è´¦æˆ·
     * @return ç”¨æˆ·åç§°
     */
    String selectNicknameById(Long userId);
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户账户
     *
     * @param userIds ç”¨æˆ·ID å¤šä¸ªç”¨é€—号隔开
     * @return ç”¨æˆ·åç§°
     */
    String selectNicknameByIds(String userIds);
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户手机号
     *
     * @param userId ç”¨æˆ·id
     * @return ç”¨æˆ·æ‰‹æœºå·
     */
    String selectPhonenumberById(Long userId);
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户邮箱
     *
     * @param userId ç”¨æˆ·id
     * @return ç”¨æˆ·é‚®ç®±
     */
    String selectEmailById(Long userId);
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户列表
     *
     * @param userIds ç”¨æˆ·ids
     * @return ç”¨æˆ·åˆ—表
     */
    List<UserDTO> selectListByIds(List<Long> userIds);
    /**
     * é€šè¿‡è§’色ID查询用户ID
     *
     * @param roleIds è§’色ids
     * @return ç”¨æˆ·ids
     */
    List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
@@ -3,7 +3,9 @@
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
@@ -59,4 +61,8 @@
        return getApplicationContext();
    }
    public static boolean isVirtual() {
        return Threading.VIRTUAL.isActive(getBean(Environment.class));
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
@@ -22,6 +22,8 @@
    public static final String SEPARATOR = ",";
    public static final String SLASH = "/";
    /**
     * èŽ·å–å‚æ•°ä¸ä¸ºç©ºå€¼
     *
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
@@ -1,11 +1,11 @@
package org.dromara.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
@@ -18,6 +18,13 @@
    private static final Validator VALID = SpringUtils.getBean(Validator.class);
    /**
     * å¯¹ç»™å®šå¯¹è±¡è¿›è¡Œå‚数校验,并根据指定的校验组进行校验
     *
     * @param object è¦è¿›è¡Œæ ¡éªŒçš„对象
     * @param groups æ ¡éªŒç»„
     * @throws ConstraintViolationException å¦‚果校验不通过,则抛出参数校验异常
     */
    public static <T> void validate(T object, Class<?>... groups) {
        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
        if (!validate.isEmpty()) {
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package org.dromara.common.core.utils.regex;
import cn.hutool.core.util.ReUtil;
import org.dromara.common.core.constant.RegexConstants;
/**
 * æ­£åˆ™ç›¸å…³å·¥å…·ç±»
 *
 * @author Feng
 */
public final class RegexUtils extends ReUtil {
    /**
     * ä»Žè¾“入字符串中提取匹配的部分,如果没有匹配则返回默认值
     *
     * @param input        è¦æå–的输入字符串
     * @param regex        ç”¨äºŽåŒ¹é…çš„æ­£åˆ™è¡¨è¾¾å¼ï¼Œå¯ä»¥ä½¿ç”¨ {@link RegexConstants} ä¸­å®šä¹‰çš„常量
     * @param defaultInput å¦‚果没有匹配时返回的默认值
     * @return å¦‚果找到匹配的部分,则返回匹配的部分,否则返回默认值
     */
    public static String extractFromString(String input, String regex, String defaultInput) {
        try {
            return ReUtil.get(regex, input, 1);
        } catch (Exception e) {
            return defaultInput;
        }
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
package org.dromara.common.core.utils.regex;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.lang.Validator;
import org.dromara.common.core.factory.RegexPatternPoolFactory;
import java.util.regex.Pattern;
/**
 * æ­£åˆ™å­—段校验器
 * ä¸»è¦éªŒè¯å­—段非空、是否为满足指定格式等
 *
 * @author Feng
 */
public class RegexValidator extends Validator {
    /**
     * å­—典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
     */
    public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE;
    /**
     * èº«ä»½è¯å·ç ï¼ˆåŽ6位)
     */
    public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6;
    /**
     * QQ号码
     */
    public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER;
    /**
     * é‚®æ”¿ç¼–码
     */
    public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE;
    /**
     * æ³¨å†Œè´¦å·
     */
    public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT;
    /**
     * å¯†ç ï¼šåŒ…含至少8个字符,包括大写字母、小写字母、数字和特殊字符
     */
    public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD;
    /**
     * é€šç”¨çŠ¶æ€ï¼ˆ0表示正常,1表示停用)
     */
    public static final Pattern STATUS = RegexPatternPoolFactory.STATUS;
    /**
     * æ£€æŸ¥è¾“入的账号是否匹配预定义的规则
     *
     * @param value è¦éªŒè¯çš„账号
     * @return å¦‚果账号符合规则,返回 true;否则,返回 false。
     */
    public static boolean isAccount(CharSequence value) {
        return isMatchRegex(ACCOUNT, value);
    }
    /**
     * éªŒè¯è¾“入的账号是否符合规则,如果不符合,则抛出 ValidateException å¼‚常
     *
     * @param value    è¦éªŒè¯çš„账号
     * @param errorMsg éªŒè¯å¤±è´¥æ—¶æŠ›å‡ºçš„异常消息
     * @param <T>      CharSequence çš„子类型
     * @return å¦‚果验证通过,返回输入的账号
     * @throws ValidateException å¦‚果验证失败
     */
    public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException {
        if (!isAccount(value)) {
            throw new ValidateException(errorMsg);
        }
        return value;
    }
    /**
     * æ£€æŸ¥è¾“入的状态是否匹配预定义的规则
     *
     * @param value è¦éªŒè¯çš„状态
     * @return å¦‚果状态符合规则,返回 true;否则,返回 false。
     */
    public static boolean isStatus(CharSequence value) {
        return isMatchRegex(STATUS, value);
    }
    /**
     * éªŒè¯è¾“入的状态是否符合规则,如果不符合,则抛出 ValidateException å¼‚常
     *
     * @param value    è¦éªŒè¯çš„状态
     * @param errorMsg éªŒè¯å¤±è´¥æ—¶æŠ›å‡ºçš„异常消息
     * @param <T>      CharSequence çš„子类型
     * @return å¦‚果验证通过,返回输入的状态
     * @throws ValidateException å¦‚果验证失败
     */
    public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException {
        if (!isStatus(value)) {
            throw new ValidateException(errorMsg);
        }
        return value;
    }
}
ruoyi-common/ruoyi-common-encrypt/pom.xml
@@ -23,11 +23,6 @@
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
        </dependency>
@@ -42,6 +37,18 @@
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <optional>true</optional>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
@@ -1,5 +1,8 @@
package org.dromara.common.encrypt.config;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.encrypt.core.EncryptorManager;
import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
@@ -16,17 +19,18 @@
 * @author è€é©¬
 * @version 4.6.0
 */
@AutoConfiguration
@AutoConfiguration(after = MybatisPlusAutoConfiguration.class)
@EnableConfigurationProperties(EncryptorProperties.class)
@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
@Slf4j
public class EncryptorAutoConfiguration {
    @Autowired
    private EncryptorProperties properties;
    @Bean
    public EncryptorManager encryptorManager() {
        return new EncryptorManager();
    public EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) {
        return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage());
    }
    @Bean
@@ -38,4 +42,8 @@
    public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
        return new MybatisDecryptInterceptor(encryptorManager, properties);
    }
}
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
@@ -1,14 +1,23 @@
package org.dromara.common.encrypt.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -19,6 +28,7 @@
 * @version 4.6.0
 */
@Slf4j
@NoArgsConstructor
public class EncryptorManager {
    /**
@@ -32,24 +42,23 @@
    Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
    /**
     * æž„造方法传入类加密字段缓存
     *
     * @param typeAliasesPackage å®žä½“类包
     */
    public EncryptorManager(String typeAliasesPackage) {
        scanEncryptClasses(typeAliasesPackage);
    }
    /**
     * èŽ·å–ç±»åŠ å¯†å­—æ®µç¼“å­˜
     */
    public Set<Field> getFieldCache(Class<?> sourceClazz) {
        return fieldCache.computeIfAbsent(sourceClazz, clazz -> {
            Set<Field> fieldSet = new HashSet<>();
            while (clazz != null) {
                Field[] fields = clazz.getDeclaredFields();
                fieldSet.addAll(Arrays.asList(fields));
                clazz = clazz.getSuperclass();
            }
            fieldSet = fieldSet.stream().filter(field ->
                    field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
                .collect(Collectors.toSet());
            for (Field field : fieldSet) {
                field.setAccessible(true);
            }
            return fieldSet;
        });
        if (ObjectUtil.isNotNull(fieldCache)) {
            return fieldCache.get(sourceClazz);
        }
        return null;
    }
    /**
@@ -97,4 +106,53 @@
        return encryptor.decrypt(value);
    }
    /**
     * é€šè¿‡ typeAliasesPackage è®¾ç½®çš„æ‰«æåŒ… æ‰«æç¼“存实体
     */
    private void scanEncryptClasses(String typeAliasesPackage) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
        try {
            for (String packagePattern : packagePatternArray) {
                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
                for (Resource resource : resources) {
                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
                    if (CollUtil.isNotEmpty(encryptFieldSet)) {
                        fieldCache.put(clazz, encryptFieldSet);
                    }
                }
            }
        } catch (Exception e) {
            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
        }
    }
    /**
     * èŽ·å¾—ä¸€ä¸ªç±»çš„åŠ å¯†å­—æ®µé›†åˆ
     */
    private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
        Set<Field> fieldSet = new HashSet<>();
        // åˆ¤æ–­clazz如果是接口,内部类,匿名类就直接返回
        if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
            return fieldSet;
        }
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            fieldSet.addAll(Arrays.asList(fields));
            clazz = clazz.getSuperclass();
        }
        fieldSet = fieldSet.stream().filter(field ->
                field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
            .collect(Collectors.toSet());
        for (Field field : fieldSet) {
            field.setAccessible(true);
        }
        return fieldSet;
    }
}
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -11,14 +11,12 @@
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -37,42 +35,38 @@
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        boolean responseFlag = false;
        // èŽ·å–åŠ å¯†æ³¨è§£
        ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
        boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
        ServletRequest requestWrapper = null;
        ServletResponse responseWrapper = null;
        EncryptResponseBodyWrapper responseBodyWrapper = null;
        // æ˜¯å¦ä¸º json è¯·æ±‚
        if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
            // æ˜¯å¦ä¸º put æˆ–者 post è¯·æ±‚
            if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
                // æ˜¯å¦å­˜åœ¨åŠ å¯†æ ‡å¤´
                String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
                // èŽ·å–åŠ å¯†æ³¨è§£
                ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
                responseFlag = apiEncrypt != null && apiEncrypt.response();
                if (StringUtils.isNotBlank(headerValue)) {
                    // è¯·æ±‚解密
                    requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
                } else {
                    // æ˜¯å¦æœ‰æ³¨è§£ï¼Œæœ‰å°±æŠ¥é”™ï¼Œæ²¡æœ‰æ”¾è¡Œ
                    if (ObjectUtil.isNotNull(apiEncrypt)) {
                        HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
                        exceptionResolver.resolveException(
                            servletRequest, servletResponse, null,
                            new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));
                        return;
                    }
                }
                // åˆ¤æ–­æ˜¯å¦å“åº”加密
                if (responseFlag) {
                    responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
                    responseWrapper = responseBodyWrapper;
        // æ˜¯å¦ä¸º put æˆ–者 post è¯·æ±‚
        if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
            // æ˜¯å¦å­˜åœ¨åŠ å¯†æ ‡å¤´
            String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
            if (StringUtils.isNotBlank(headerValue)) {
                // è¯·æ±‚解密
                requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
            } else {
                // æ˜¯å¦æœ‰æ³¨è§£ï¼Œæœ‰å°±æŠ¥é”™ï¼Œæ²¡æœ‰æ”¾è¡Œ
                if (ObjectUtil.isNotNull(apiEncrypt)) {
                    HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
                    exceptionResolver.resolveException(
                        servletRequest, servletResponse, null,
                        new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));
                    return;
                }
            }
        }
        // åˆ¤æ–­æ˜¯å¦å“åº”加密
        if (responseFlag) {
            responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
            responseWrapper = responseBodyWrapper;
        }
        chain.doFilter(
            ObjectUtil.defaultIfNull(requestWrapper, request),
            ObjectUtil.defaultIfNull(responseWrapper, response));
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
@@ -76,6 +76,7 @@
        String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
        // è®¾ç½®å“åº”头
        servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
        servletResponse.setHeader(headerFlag, encryptPassword);
        servletResponse.setHeader("Access-Control-Allow-Origin", "*");
        servletResponse.setHeader("Access-Control-Allow-Methods", "*");
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
@@ -73,7 +73,11 @@
            list.forEach(this::decryptHandler);
            return;
        }
        // ä¸åœ¨ç¼“存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
        if(ObjectUtil.isNull(fields)){
            return;
        }
        try {
            for (Field field : fields) {
                field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
@@ -82,7 +82,11 @@
            list.forEach(this::encryptHandler);
            return;
        }
        // ä¸åœ¨ç¼“存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
        if(ObjectUtil.isNull(fields)){
            return;
        }
        try {
            for (Field field : fields) {
                field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
@@ -21,4 +21,9 @@
     */
    int index() default -1;
    /**
     * åˆå¹¶éœ€è¦ä¾èµ–的其他字段名称
     */
    String[] mergeBy() default {};
}
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
@@ -1,8 +1,12 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -15,10 +19,7 @@
import org.dromara.common.excel.annotation.CellMerge;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
 * åˆ—值重复合并策略
@@ -26,7 +27,7 @@
 * @author Lion Li
 */
@Slf4j
public class CellMergeStrategy extends AbstractMergeStrategy {
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
    private final List<CellRangeAddress> cellList;
    private final boolean hasTitle;
@@ -41,13 +42,24 @@
    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
        // judge the list is not null
        if (CollUtil.isNotEmpty(cellList)) {
            // the judge is necessary
            if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
                for (CellRangeAddress item : cellList) {
                    sheet.addMergedRegion(item);
        //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
        final int rowIndex = cell.getRowIndex();
        if (CollUtil.isNotEmpty(cellList)){
            for (CellRangeAddress cellAddresses : cellList) {
                final int firstRow = cellAddresses.getFirstRow();
                if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
                    cell.setBlank();
                }
            }
        }
    }
    @Override
    public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
        //当前表格写完后,统一写入
        if (CollUtil.isNotEmpty(cellList)){
            for (CellRangeAddress item : cellList) {
                context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
            }
        }
    }
@@ -93,35 +105,15 @@
                        // ç©ºå€¼è·³è¿‡ä¸åˆå¹¶
                        continue;
                    }
                    if (!cellValue.equals(val)) {
                        if (i - repeatCell.getCurrent() > 1) {
                        if ((i - repeatCell.getCurrent() > 1) && isMerge(list, i, field)) {
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
                        }
                        map.put(field, new RepeatCell(val, i));
                    } else if (j == 0) {
                        if (i == list.size() - 1) {
                            if (i > repeatCell.getCurrent()) {
                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                            }
                        }
                    } else {
                        // åˆ¤æ–­å‰é¢çš„æ˜¯å¦åˆå¹¶äº†
                        RepeatCell firstCell = map.get(mergeFields.get(0));
                        if (repeatCell.getCurrent() != firstCell.getCurrent()) {
                            if (i == list.size() - 1) {
                                if (i > repeatCell.getCurrent()) {
                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                                }
                            } else if (repeatCell.getCurrent() < firstCell.getCurrent()) {
                                if (i - repeatCell.getCurrent() > 1) {
                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
                                }
                                map.put(field, new RepeatCell(val, i));
                            }
                        } else if (i == list.size() - 1) {
                            if (i > repeatCell.getCurrent()) {
                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                            }
                    } else if (i == list.size() - 1) {
                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                        }
                    }
                }
@@ -130,6 +122,24 @@
        return cellList;
    }
    private boolean isMerge(List<?> list, int i, Field field) {
        boolean isMerge = true;
        CellMerge cm = field.getAnnotation(CellMerge.class);
        final String[] mergeBy = cm.mergeBy();
        if (StrUtil.isAllNotBlank(mergeBy)) {
            //比对当前list(i)和list(i - 1)的各个属性值一一比对 å¦‚果全为真 åˆ™ä¸ºçœŸ
            for (String fieldName : mergeBy) {
                final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
                final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
                if (!Objects.equals(valPre, valCurrent)) {
                    //依赖字段如有任一不等值,则标记为不可合并
                    isMerge = false;
                }
            }
        }
        return isMerge;
    }
    @Data
    @AllArgsConstructor
    static class RepeatCell {
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
@@ -20,6 +20,7 @@
import org.dromara.common.core.service.DictService;
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.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.annotation.ExcelEnumFormat;
@@ -99,15 +100,16 @@
                ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
                String dictType = format.dictType();
                String converterExp = format.readConverterExp();
                if (StrUtil.isNotBlank(dictType)) {
                if (StringUtils.isNotBlank(dictType)) {
                    // å¦‚果传递了字典名,则依据字典建立下拉
                    Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
                        .orElseThrow(() -> new ServiceException(String.format("字典 %s ä¸å­˜åœ¨", dictType)))
                        .values();
                    options = new ArrayList<>(values);
                } else if (StrUtil.isNotBlank(converterExp)) {
                } else if (StringUtils.isNotBlank(converterExp)) {
                    // å¦‚果指定了确切的值,则直接解析确切的值
                    options = StrUtil.split(converterExp, format.separator(), true, true);
                    List<String> strList = StringUtils.splitList(converterExp, format.separator());
                    options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
                }
            } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
                // å¦åˆ™å¦‚果指定了@ExcelEnumFormat,则使用枚举的逻辑
ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java
@@ -4,6 +4,13 @@
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
@@ -13,13 +20,6 @@
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
@@ -127,7 +127,7 @@
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
            return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
ruoyi-common/ruoyi-common-job/pom.xml
@@ -22,20 +22,14 @@
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!--PowerJob-->
        <!-- SnailJob client -->
        <dependency>
            <groupId>tech.powerjob</groupId>
            <artifactId>powerjob-worker-spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>powerjob-remote-impl-akka</artifactId>
                    <groupId>tech.powerjob</groupId>
                </exclusion>
            </exclusions>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>tech.powerjob</groupId>
            <artifactId>powerjob-official-processors</artifactId>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-job-core</artifactId>
        </dependency>
        <dependency>
ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.common.job.config;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender;
import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent;
import com.aizuda.snailjob.client.starter.EnableSnailJob;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * å¯åŠ¨å®šæ—¶ä»»åŠ¡
 *
 * @author opensnail
 * @date 2024-05-17
 */
@AutoConfiguration
@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
@EnableScheduling
@EnableSnailJob(group = "${snail-job.group-name}")
public class SnailJobConfig {
    @EventListener(SnailClientStartingEvent.class)
    public void onStarting(SnailClientStartingEvent event) {
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        SnailLogbackAppender<ILoggingEvent> ca = new SnailLogbackAppender<>();
        ca.setName("snail_log_appender");
        ca.start();
        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(ca);
    }
}
ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
org.dromara.common.job.config.SnailJobConfig
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
@@ -7,10 +7,10 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -30,6 +30,13 @@
        return OBJECT_MAPPER;
    }
    /**
     * å°†å¯¹è±¡è½¬æ¢ä¸ºJSON格式的字符串
     *
     * @param object è¦è½¬æ¢çš„对象
     * @return JSON格式的字符串,如果对象为null,则返回null
     * @throws RuntimeException å¦‚果转换过程中发生JSON处理异常,则抛出运行时异常
     */
    public static String toJsonString(Object object) {
        if (ObjectUtil.isNull(object)) {
            return null;
@@ -41,6 +48,15 @@
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型的对象
     *
     * @param text  JSON格式的字符串
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return null;
@@ -52,6 +68,15 @@
        }
    }
    /**
     * å°†å­—节数组转换为指定类型的对象
     *
     * @param bytes å­—节数组
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字节数组为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
        if (ArrayUtil.isEmpty(bytes)) {
            return null;
@@ -63,6 +88,15 @@
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型的对象,支持复杂类型
     *
     * @param text          JSON格式的字符串
     * @param typeReference æŒ‡å®šç±»åž‹çš„TypeReference对象
     * @param <T>           ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
        if (StringUtils.isBlank(text)) {
            return null;
@@ -74,6 +108,13 @@
        }
    }
    /**
     * å°†JSON格式的字符串转换为Dict对象
     *
     * @param text JSON格式的字符串
     * @return è½¬æ¢åŽçš„Dict对象,如果字符串为空或者不是JSON格式则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static Dict parseMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
@@ -88,6 +129,13 @@
        }
    }
    /**
     * å°†JSON格式的字符串转换为Dict对象的列表
     *
     * @param text JSON格式的字符串
     * @return è½¬æ¢åŽçš„Dict对象的列表,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static List<Dict> parseArrayMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
@@ -99,6 +147,15 @@
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型对象的列表
     *
     * @param text  JSON格式的字符串
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象的列表,如果字符串为空则返回空列表
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> List<T> parseArray(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return new ArrayList<>();
ruoyi-common/ruoyi-common-log/pom.xml
@@ -27,11 +27,6 @@
            <artifactId>ruoyi-common-json</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
@@ -4,16 +4,6 @@
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils;
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.log.annotation.Log;
import org.dromara.common.log.enums.BusinessStatus;
import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.satoken.utils.LoginHelper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@@ -23,6 +13,15 @@
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils;
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.log.annotation.Log;
import org.dromara.common.log.enums.BusinessStatus;
import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
@@ -49,9 +48,9 @@
    /**
     * è®¡ç®—操作消耗时间
     * è®¡æ—¶ key
     */
    private static final ThreadLocal<StopWatch> TIME_THREADLOCAL = new TransmittableThreadLocal<>();
    private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
    /**
     * å¤„理请求前执行
@@ -59,7 +58,7 @@
    @Before(value = "@annotation(controllerLog)")
    public void boBefore(JoinPoint joinPoint, Log controllerLog) {
        StopWatch stopWatch = new StopWatch();
        TIME_THREADLOCAL.set(stopWatch);
        KEY_CACHE.set(stopWatch);
        stopWatch.start();
    }
@@ -112,7 +111,7 @@
            // å¤„理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // è®¾ç½®æ¶ˆè€—æ—¶é—´
            StopWatch stopWatch = TIME_THREADLOCAL.get();
            StopWatch stopWatch = KEY_CACHE.get();
            stopWatch.stop();
            operLog.setCostTime(stopWatch.getTime());
            // å‘布事件保存数据库
@@ -122,7 +121,7 @@
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            TIME_THREADLOCAL.remove();
            KEY_CACHE.remove();
        }
    }
@@ -204,7 +203,7 @@
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
            return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
ruoyi-common/ruoyi-common-mybatis/pom.xml
@@ -33,19 +33,8 @@
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>
        <!-- sql性能分析插件 -->
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java
@@ -1,26 +1,23 @@
package org.dromara.common.mybatis.config;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.autoconfigure.DdlApplicationRunner;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.ddl.IDdl;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.beans.BeansException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.List;
/**
 * mybatis-plus配置类(下方注释有插件介绍)
@@ -28,7 +25,6 @@
 * @author Lion Li
 */
@EnableTransactionManagement(proxyTargetClass = true)
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class)
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
public class MybatisPlusConfig {
@@ -36,6 +32,12 @@
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // å¤šç§Ÿæˆ·æ’ä»¶ å¿…须放到第一位
        try {
            TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class);
            interceptor.addInnerInterceptor(tenant);
        } catch (BeansException ignore) {
        }
        // æ•°æ®æƒé™å¤„理
        interceptor.addInnerInterceptor(dataPermissionInterceptor());
        // åˆ†é¡µæ’ä»¶
@@ -49,7 +51,7 @@
     * æ•°æ®æƒé™æ‹¦æˆªå™¨
     */
    public PlusDataPermissionInterceptor dataPermissionInterceptor() {
        return new PlusDataPermissionInterceptor();
        return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
    }
    /**
@@ -57,8 +59,6 @@
     */
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // è®¾ç½®æœ€å¤§å•页限制数量,默认 500 æ¡ï¼Œ-1 ä¸å—限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        // åˆ†é¡µåˆç†åŒ–
        paginationInnerInterceptor.setOverflow(true);
        return paginationInnerInterceptor;
@@ -89,6 +89,14 @@
    }
    /**
     * å¼‚常处理器
     */
    @Bean
    public MybatisExceptionHandler mybatisExceptionHandler() {
        return new MybatisExceptionHandler();
    }
    /**
     * PaginationInnerInterceptor åˆ†é¡µæ’件,自动识别数据库类型
     * https://baomidou.com/pages/97710a/
     * OptimisticLockerInnerInterceptor ä¹è§‚锁插件
@@ -107,10 +115,5 @@
     * DynamicTableNameInnerInterceptor åŠ¨æ€è¡¨åæ’ä»¶
     * https://baomidou.com/pages/2a45ff/
     */
    @Bean
    public DdlApplicationRunner ddlApplicationRunner(@Autowired(required = false) List<IDdl> ddlList) {
        return new DdlApplicationRunner(ddlList);
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -35,7 +35,6 @@
    Log log = LogFactory.getLog(BaseMapperPlus.class);
    default Class<V> currentVoClass() {
        GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class);
        return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
    }
@@ -145,11 +144,22 @@
        return selectVoOne(wrapper, this.currentVoClass());
    }
    default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
        return selectVoOne(wrapper, this.currentVoClass(), throwEx);
    }
    /**
     * æ ¹æ® entity æ¡ä»¶ï¼ŒæŸ¥è¯¢ä¸€æ¡è®°å½•
     */
    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
        T obj = this.selectOne(wrapper);
        return selectVoOne(wrapper, voClass, true);
    }
    /**
     * æ ¹æ® entity æ¡ä»¶ï¼ŒæŸ¥è¯¢ä¸€æ¡è®°å½•
     */
    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
        T obj = this.selectOne(wrapper, throwEx);
        if (ObjectUtil.isNull(obj)) {
            return null;
        }
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -111,4 +111,8 @@
        return list;
    }
    public Integer getFirstNum() {
        return (pageNum - 1) * pageSize;
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -29,16 +29,17 @@
                    ? baseEntity.getCreateTime() : new Date();
                baseEntity.setCreateTime(current);
                baseEntity.setUpdateTime(current);
                LoginUser loginUser = getLoginUser();
                if (ObjectUtil.isNotNull(loginUser)) {
                    Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy())
                        ? baseEntity.getCreateBy() : loginUser.getUserId();
                    // å½“前已登录 ä¸” åˆ›å»ºäººä¸ºç©º åˆ™å¡«å……
                    baseEntity.setCreateBy(userId);
                    // å½“前已登录 ä¸” æ›´æ–°äººä¸ºç©º åˆ™å¡«å……
                    baseEntity.setUpdateBy(userId);
                    baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
                        ? baseEntity.getCreateDept() : loginUser.getDeptId());
                if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
                    LoginUser loginUser = getLoginUser();
                    if (ObjectUtil.isNotNull(loginUser)) {
                        Long userId = loginUser.getUserId();
                        // å½“前已登录 ä¸” åˆ›å»ºäººä¸ºç©º åˆ™å¡«å……
                        baseEntity.setCreateBy(userId);
                        // å½“前已登录 ä¸” æ›´æ–°äººä¸ºç©º åˆ™å¡«å……
                        baseEntity.setUpdateBy(userId);
                        baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
                            ? baseEntity.getCreateDept() : loginUser.getDeptId());
                    }
                }
            }
        } catch (Exception e) {
@@ -53,11 +54,12 @@
                Date current = new Date();
                // æ›´æ–°æ—¶é—´å¡«å……(不管为不为空)
                baseEntity.setUpdateTime(current);
                LoginUser loginUser = getLoginUser();
                // å½“前已登录 æ›´æ–°äººå¡«å……(不管为不为空)
                if (ObjectUtil.isNotNull(loginUser)) {
                    baseEntity.setUpdateBy(loginUser.getUserId());
                Long userId = LoginHelper.getUserId();
                if (ObjectUtil.isNotNull(userId)) {
                    baseEntity.setUpdateBy(userId);
                }
            }
        } catch (Exception e) {
            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
@@ -2,6 +2,7 @@
import org.dromara.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -35,7 +36,7 @@
    public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String message = e.getMessage();
        if ("CannotFindDataSourceException".contains(message)) {
        if (StringUtils.contains("CannotFindDataSourceException", message)) {
            log.error("请求地址'{}', æœªæ‰¾åˆ°æ•°æ®æº", requestURI);
            return R.fail("未找到数据源,请联系管理员确认");
        }
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -2,7 +2,6 @@
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
@@ -10,6 +9,7 @@
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
@@ -21,16 +21,26 @@
import org.dromara.common.mybatis.enums.DataScopeType;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -58,9 +68,13 @@
     */
    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
    public PlusDataPermissionHandler(String mapperPackage) {
        scanMapperClasses(mapperPackage);
    }
    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
        DataColumn[] dataColumns = findAnnotation(mappedStatementId);
        DataPermission dataPermission = getDataPermission(mappedStatementId);
        LoginUser currentUser = DataPermissionHelper.getVariable("user");
        if (ObjectUtil.isNull(currentUser)) {
            currentUser = LoginHelper.getLoginUser();
@@ -70,7 +84,7 @@
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
            return where;
        }
        String dataFilterSql = buildDataFilter(dataColumns, isSelect);
        String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect);
        if (StringUtils.isBlank(dataFilterSql)) {
            return where;
        }
@@ -144,43 +158,64 @@
        return "";
    }
    public DataColumn[] findAnnotation(String mappedStatementId) {
        StringBuilder sb = new StringBuilder(mappedStatementId);
        int index = sb.lastIndexOf(".");
        String clazzName = sb.substring(0, index);
        String methodName = sb.substring(index + 1, sb.length());
        Class<?> clazz;
    /**
     * é€šè¿‡ mapperPackage è®¾ç½®çš„æ‰«æåŒ… æ‰«æç¼“存有注解的方法与类
     */
    private void scanMapperClasses(String mapperPackage) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
        try {
            clazz = ClassUtil.loadClass(clazzName);
            for (String packagePattern : packagePatternArray) {
                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
                for (Resource resource : resources) {
                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    findAnnotation(clazz);
                }
            }
        } catch (Exception e) {
            return null;
            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
        }
        List<Method> methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz))
            .filter(method -> method.getName().equals(methodName)).toList();
    }
    private void findAnnotation(Class<?> clazz) {
        DataPermission dataPermission;
        // èŽ·å–æ–¹æ³•æ³¨è§£
        for (Method method : methods) {
            dataPermission = dataPermissionCacheMap.get(mappedStatementId);
            if (ObjectUtil.isNotNull(dataPermission)) {
                return dataPermission.value();
        for (Method method : clazz.getMethods()) {
            if (method.isDefault() || method.isVarArgs()) {
                continue;
            }
            String mappedStatementId = clazz.getName() + "." + method.getName();
            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
                return dataPermission.value();
            }
        }
        dataPermission = dataPermissionCacheMap.get(clazz.getName());
        if (ObjectUtil.isNotNull(dataPermission)) {
            return dataPermission.value();
        }
        // èŽ·å–ç±»æ³¨è§£
        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
            return dataPermission.value();
        }
    }
    public DataPermission getDataPermission(String mapperId) {
        if (dataPermissionCacheMap.containsKey(mapperId)) {
            return dataPermissionCacheMap.get(mapperId);
        }
        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
        if (dataPermissionCacheMap.containsKey(clazzName)) {
            return dataPermissionCacheMap.get(clazzName);
        }
        return null;
    }
    /**
     * æ˜¯å¦æ— æ•ˆ
     */
    public boolean invalid(String mapperId) {
        return getDataPermission(mapperId) == null;
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -1,18 +1,16 @@
package org.dromara.common.mybatis.interceptor;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
@@ -22,11 +20,11 @@
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
/**
 * æ•°æ®æƒé™æ‹¦æˆªå™¨
@@ -34,13 +32,14 @@
 * @author Lion Li
 * @version 3.5.0
 */
public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
@Slf4j
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
    private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
    /**
     * æ— æ•ˆæ³¨è§£æ–¹æ³•缓存用于快速返回
     */
    private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
    private final PlusDataPermissionHandler dataPermissionHandler;
    public PlusDataPermissionInterceptor(String mapperPackage) {
        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
    }
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
@@ -49,12 +48,7 @@
            return;
        }
        // æ£€æŸ¥æ˜¯å¦æ— æ•ˆ æ— æ•°æ®æƒé™æ³¨è§£
        if (invalidCacheSet.contains(ms.getId())) {
            return;
        }
        DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
        if (ArrayUtil.isEmpty(dataColumns)) {
            invalidCacheSet.add(ms.getId());
        if (dataPermissionHandler.invalid(ms.getId())) {
            return;
        }
        // è§£æž sql åˆ†é…å¯¹åº”方法
@@ -72,12 +66,7 @@
                return;
            }
            // æ£€æŸ¥æ˜¯å¦æ— æ•ˆ æ— æ•°æ®æƒé™æ³¨è§£
            if (invalidCacheSet.contains(ms.getId())) {
                return;
            }
            DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
            if (ArrayUtil.isEmpty(dataColumns)) {
                invalidCacheSet.add(ms.getId());
            if (dataPermissionHandler.invalid(ms.getId())) {
                return;
            }
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
@@ -87,11 +76,10 @@
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect plainSelect) {
            this.setWhere(plainSelect, (String) obj);
        } else if (selectBody instanceof SetOperationList setOperationList) {
            List<SelectBody> selectBodyList = setOperationList.getSelects();
        if (select instanceof PlainSelect) {
            this.setWhere((PlainSelect) select, (String) obj);
        } else if (select instanceof SetOperationList setOperationList) {
            List<Select> selectBodyList = setOperationList.getSelects();
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }
@@ -125,5 +113,11 @@
        }
    }
    @Override
    public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
        // åªæœ‰æ–°ç‰ˆæ•°æ®æƒé™å¤„理器才会执行到这里
        final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
        return handler.getSqlSegment(table, where, whereSegment);
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties
ÎļþÃû´Ó ruoyi-admin/src/main/resources/spy.properties ÐÞ¸Ä
@@ -6,8 +6,6 @@
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# ä½¿ç”¨æ—¥å¿—系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# è®¾ç½® p6spy driver ä»£ç†
#deregisterdrivers=true
# å–消JDBC URL前缀
useprefix=true
# é…ç½®è®°å½• Log ä¾‹å¤–,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
@@ -16,12 +14,6 @@
dateformat=yyyy-MM-dd HH:mm:ss
# SQL语句打印时间格式
databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
# å®žé™…驱动可多个
#driverlist=org.h2.Driver
# æ˜¯å¦å¼€å¯æ…¢SQL记录
outagedetection=true
# æ…¢SQL记录标准 2 ç§’
outagedetectioninterval=2
# æ˜¯å¦è¿‡æ»¤ Log
filter=true
# è¿‡æ»¤ Log æ—¶æ‰€æŽ’除的 sql å…³é”®å­—,以逗号分隔
ruoyi-common/ruoyi-common-oss/pom.xml
@@ -26,10 +26,46 @@
            <artifactId>ruoyi-common-redis</artifactId>
        </dependency>
        <!--  AWS SDK for Java 2.x  -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <exclusions>
                <!-- å°†åŸºäºŽ Netty çš„ HTTP å®¢æˆ·ç«¯ä»Žç±»è·¯å¾„中移除 -->
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>netty-nio-client</artifactId>
                </exclusion>
                <!-- å°†åŸºäºŽ CRT çš„ HTTP å®¢æˆ·ç«¯ä»Žç±»è·¯å¾„中移除 -->
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>aws-crt-client</artifactId>
                </exclusion>
                <!-- å°†åŸºäºŽ Apache çš„ HTTP å®¢æˆ·ç«¯ä»Žç±»è·¯å¾„中移除 -->
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>apache-client</artifactId>
                </exclusion>
                <!-- å°†é…ç½®åŸºäºŽ URL è¿žæŽ¥çš„ HTTP å®¢æˆ·ç«¯ä»Žç±»è·¯å¾„中移除 -->
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>url-connection-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- ä½¿ç”¨AWS基于 CRT çš„ S3 å®¢æˆ·ç«¯ -->
        <dependency>
            <groupId>software.amazon.awssdk.crt</groupId>
            <artifactId>aws-crt</artifactId>
        </dependency>
        <!-- åŸºäºŽ AWS CRT çš„ S3 å®¢æˆ·ç«¯çš„æ€§èƒ½å¢žå¼ºçš„ S3 ä¼ è¾“管理器 -->
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3-transfer-manager</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -2,73 +2,117 @@
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.enumd.PolicyType;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.*;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.util.Date;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
/**
 * S3 å­˜å‚¨åè®® æ‰€æœ‰å…¼å®¹S3协议的云厂商均支持
 * é˜¿é‡Œäº‘ è…¾è®¯äº‘ ä¸ƒç‰›äº‘ minio
 *
 * @author Lion Li
 * @author AprilWind
 */
public class OssClient {
    /**
     * æœåС商
     */
    private final String configKey;
    /**
     * é…ç½®å±žæ€§
     */
    private final OssProperties properties;
    private final AmazonS3 client;
    /**
     * Amazon S3 å¼‚步客户端
     */
    private final S3AsyncClient client;
    /**
     * ç”¨äºŽç®¡ç† S3 æ•°æ®ä¼ è¾“的高级工具
     */
    private final S3TransferManager transferManager;
    /**
     * AWS S3 é¢„签名 URL çš„生成器
     */
    private final S3Presigner presigner;
    /**
     * æž„造方法
     *
     * @param configKey     é…ç½®é”®
     * @param ossProperties Oss配置属性
     */
    public OssClient(String configKey, OssProperties ossProperties) {
        this.configKey = configKey;
        this.properties = ossProperties;
        try {
            AwsClientBuilder.EndpointConfiguration endpointConfig =
                new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
            // åˆ›å»º AWS è®¤è¯ä¿¡æ¯
            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
            AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
            AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
            ClientConfiguration clientConfig = new ClientConfiguration();
            if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
                clientConfig.setProtocol(Protocol.HTTPS);
            } else {
                clientConfig.setProtocol(Protocol.HTTP);
            }
            AmazonS3ClientBuilder build = AmazonS3Client.builder()
                .withEndpointConfiguration(endpointConfig)
                .withClientConfiguration(clientConfig)
                .withCredentials(credentialsProvider)
                .disableChunkedEncoding();
            if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
                // minio ä½¿ç”¨https限制使用域名访问 éœ€è¦æ­¤é…ç½® ç«™ç‚¹å¡«åŸŸå
                build.enablePathStyleAccess();
            }
            this.client = build.build();
            //MinIO ä½¿ç”¨ HTTPS é™åˆ¶ä½¿ç”¨åŸŸåè®¿é—®ï¼Œç«™ç‚¹å¡«åŸŸåã€‚需要启用路径样式访问
            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
            //创建AWS基于 CRT çš„ S3 å®¢æˆ·ç«¯
            this.client = S3AsyncClient.crtBuilder()
                .credentialsProvider(credentialsProvider)
                .endpointOverride(URI.create(getEndpoint()))
                .region(of())
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(10 * 1025 * 1024L)
                .checksumValidationEnabled(false)
                .forcePathStyle(isStyle)
                .build();
            //AWS基于 CRT çš„ S3 AsyncClient å®žä¾‹ç”¨ä½œ S3 ä¼ è¾“管理器的底层客户端
            this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
            // åˆ›å»º S3 é…ç½®å¯¹è±¡
            S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
                .pathStyleAccessEnabled(isStyle).build();
            // åˆ›å»º é¢„签名 URL çš„生成器 å®žä¾‹ï¼Œç”¨äºŽç”Ÿæˆ S3 é¢„签名 URL
            this.presigner = S3Presigner.builder()
                .region(of())
                .credentialsProvider(credentialsProvider)
                .endpointOverride(URI.create(getDomain()))
                .serviceConfiguration(config)
                .build();
            // åˆ›å»ºå­˜å‚¨æ¡¶
            createBucket();
        } catch (Exception e) {
            if (e instanceof OssException) {
@@ -78,126 +122,189 @@
        }
    }
    /**
     * åŒæ­¥åˆ›å»ºå­˜å‚¨æ¡¶
     * å¦‚果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
     *
     * @throws OssException å½“创建存储桶时发生异常时抛出
     */
    public void createBucket() {
        String bucketName = properties.getBucketName();
        try {
            String bucketName = properties.getBucketName();
            if (client.doesBucketExistV2(bucketName)) {
                return;
            // å°è¯•获取存储桶的信息
            client.headBucket(
                    x -> x.bucket(bucketName)
                        .build())
                .join();
        } catch (Exception ex) {
            if (ex.getCause() instanceof NoSuchBucketException) {
                try {
                    // å­˜å‚¨æ¡¶ä¸å­˜åœ¨ï¼Œå°è¯•创建存储桶
                    client.createBucket(
                            x -> x.bucket(bucketName))
                        .join();
                    // è®¾ç½®å­˜å‚¨æ¡¶çš„访问策略(Bucket Policy)
                    client.putBucketPolicy(
                            x -> x.bucket(bucketName)
                                .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
                        .join();
                } catch (S3Exception e) {
                    // å­˜å‚¨æ¡¶åˆ›å»ºæˆ–策略设置失败
                    throw new OssException("创建Bucket失败, è¯·æ ¸å¯¹é…ç½®ä¿¡æ¯:[" + e.getMessage() + "]");
                }
            } else {
                throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]");
            }
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            AccessPolicyType accessPolicy = getAccessPolicy();
            createBucketRequest.setCannedAcl(accessPolicy.getAcl());
            client.createBucket(createBucketRequest);
            client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
        } catch (Exception e) {
            throw new OssException("创建Bucket失败, è¯·æ ¸å¯¹é…ç½®ä¿¡æ¯:[" + e.getMessage() + "]");
        }
    }
    public UploadResult upload(byte[] data, String path, String contentType) {
        return upload(new ByteArrayInputStream(data), path, contentType);
    /**
     * ä¸Šä¼ æ–‡ä»¶åˆ° Amazon S3,并返回上传结果
     *
     * @param filePath  æœ¬åœ°æ–‡ä»¶è·¯å¾„
     * @param key       åœ¨ Amazon S3 ä¸­çš„对象键
     * @param md5Digest æœ¬åœ°æ–‡ä»¶çš„ MD5 å“ˆå¸Œå€¼ï¼ˆå¯é€‰ï¼‰
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult upload(Path filePath, String key, String md5Digest) {
        try {
            // æž„建上传请求对象
            FileUpload fileUpload = transferManager.uploadFile(
                x -> x.putObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
                            .build())
                    .addTransferListener(LoggingTransferListener.create())
                    .source(filePath).build());
            // ç­‰å¾…上传完成并获取上传结果
            CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
            String eTag = uploadResult.response().eTag();
            // æå–上传结果中的 ETag,并构建一个自定义的 UploadResult å¯¹è±¡
            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
        } catch (Exception e) {
            // æ•获异常并抛出自定义异常
            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
        } finally {
            // æ— è®ºä¸Šä¼ æ˜¯å¦æˆåŠŸï¼Œæœ€ç»ˆéƒ½ä¼šåˆ é™¤ä¸´æ—¶æ–‡ä»¶
            FileUtils.del(filePath);
        }
    }
    public UploadResult upload(InputStream inputStream, String path, String contentType) {
    /**
     * ä¸Šä¼  InputStream åˆ° Amazon S3
     *
     * @param inputStream è¦ä¸Šä¼ çš„输入流
     * @param key         åœ¨ Amazon S3 ä¸­çš„对象键
     * @param length      è¾“入流的长度
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult upload(InputStream inputStream, String key, Long length) {
        // å¦‚果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
        if (!(inputStream instanceof ByteArrayInputStream)) {
            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
        }
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            metadata.setContentLength(inputStream.available());
            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
            // è®¾ç½®ä¸Šä¼ å¯¹è±¡çš„ Acl ä¸ºå…¬å…±è¯»
            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
            client.putObject(putObjectRequest);
            // åˆ›å»ºå¼‚步请求体(length如果为空会报错)
            BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
            // ä½¿ç”¨ transferManager è¿›è¡Œä¸Šä¼ 
            Upload upload = transferManager.upload(
                x -> x.requestBody(body)
                    .putObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .build())
                    .build());
            // å°†è¾“入流写入请求体
            body.writeInputStream(inputStream);
            // ç­‰å¾…文件上传操作完成
            CompletedUpload uploadResult = upload.completionFuture().join();
            String eTag = uploadResult.response().eTag();
            // æå–上传结果中的 ETag,并构建一个自定义的 UploadResult å¯¹è±¡
            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
    }
    public UploadResult upload(File file, String path) {
        try {
            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
            // è®¾ç½®ä¸Šä¼ å¯¹è±¡çš„ Acl ä¸ºå…¬å…±è¯»
            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
            client.putObject(putObjectRequest);
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
    }
    public void delete(String path) {
        path = path.replace(getUrl() + "/", "");
        try {
            client.deleteObject(properties.getBucketName(), path);
        } catch (Exception e) {
            throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
        }
    }
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
    }
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
    }
    public UploadResult uploadSuffix(File file, String suffix) {
        return upload(file, getPath(properties.getPrefix(), suffix));
    }
    /**
     * èŽ·å–æ–‡ä»¶å…ƒæ•°æ®
     * ä¸‹è½½æ–‡ä»¶ä»Ž Amazon S3 åˆ°ä¸´æ—¶ç›®å½•
     *
     * @param path å®Œæ•´æ–‡ä»¶è·¯å¾„
     * @param path æ–‡ä»¶åœ¨ Amazon S3 ä¸­çš„对象键
     * @return ä¸‹è½½åŽçš„æ–‡ä»¶åœ¨æœ¬åœ°çš„临时路径
     * @throws OssException å¦‚果下载失败,抛出自定义异常
     */
    public ObjectMetadata getObjectMetadata(String path) {
        path = path.replace(getUrl() + "/", "");
        S3Object object = client.getObject(properties.getBucketName(), path);
        return object.getObjectMetadata();
    public Path fileDownload(String path) {
        // æž„建临时文件
        Path tempFilePath = FileUtils.createTempFile().toPath();
        // ä½¿ç”¨ S3TransferManager ä¸‹è½½æ–‡ä»¶
        FileDownload downloadFile = transferManager.downloadFile(
            x -> x.getObjectRequest(
                    y -> y.bucket(properties.getBucketName())
                        .key(removeBaseUrl(path))
                        .build())
                .addTransferListener(LoggingTransferListener.create())
                .destination(tempFilePath)
                .build());
        // ç­‰å¾…文件下载操作完成
        downloadFile.completionFuture().join();
        return tempFilePath;
    }
    public InputStream getObjectContent(String path) {
        path = path.replace(getUrl() + "/", "");
        S3Object object = client.getObject(properties.getBucketName(), path);
        return object.getObjectContent();
    }
    public String getUrl() {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
        // äº‘服务商直接返回
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            if (StringUtils.isNotBlank(domain)) {
                return header + domain;
    /**
     * ä¸‹è½½æ–‡ä»¶ä»Ž Amazon S3 åˆ° è¾“出流
     *
     * @param key æ–‡ä»¶åœ¨ Amazon S3 ä¸­çš„对象键
     * @param out è¾“出流
     * @return è¾“出流中写入的字节数(长度)
     * @throws OssException å¦‚果下载失败,抛出自定义异常
     */
    public long download(String key, OutputStream out) {
        try {
            // æž„建下载请求
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
                // æ–‡ä»¶å¯¹è±¡
                .getObjectRequest(y -> y.bucket(properties.getBucketName())
                    .key(key)
                    .build())
                .addTransferListener(LoggingTransferListener.create())
                // ä½¿ç”¨è®¢é˜…转换器
                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
                .build();
            // ä½¿ç”¨ S3TransferManager ä¸‹è½½æ–‡ä»¶
            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
            // è¾“出到流中
            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
                return responseStream.transferTo(out); // é˜»å¡žè°ƒç”¨çº¿ç¨‹ blocks the calling thread
            }
            return header + properties.getBucketName() + "." + endpoint;
        } catch (Exception e) {
            throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
        }
        // minio å•独处理
        if (StringUtils.isNotBlank(domain)) {
            return header + domain + "/" + properties.getBucketName();
        }
        return header + endpoint + "/" + properties.getBucketName();
    }
    public String getPath(String prefix, String suffix) {
        // ç”Ÿæˆuuid
        String uuid = IdUtil.fastSimpleUUID();
        // æ–‡ä»¶è·¯å¾„
        String path = DateUtils.datePath() + "/" + uuid;
        if (StringUtils.isNotBlank(prefix)) {
            path = prefix + "/" + path;
    /**
     * åˆ é™¤äº‘存储服务中指定路径下文件
     *
     * @param path æŒ‡å®šè·¯å¾„
     */
    public void delete(String path) {
        try {
            client.deleteObject(
                x -> x.bucket(properties.getBucketName())
                    .key(removeBaseUrl(path))
                    .build());
        } catch (Exception e) {
            throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
        }
        return path + suffix;
    }
    public String getConfigKey() {
        return configKey;
    }
    /**
@@ -207,12 +314,187 @@
     * @param second    æŽˆæƒæ—¶é—´
     */
    public String getPrivateUrl(String objectKey, Integer second) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
                .withMethod(HttpMethod.GET)
                .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
        // ä½¿ç”¨ AWS S3 é¢„签名 URL çš„生成器 èŽ·å–å¯¹è±¡çš„é¢„ç­¾å URL
        URL url = presigner.presignGetObject(
                x -> x.signatureDuration(Duration.ofSeconds(second))
                    .getObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(objectKey)
                            .build())
                    .build())
            .url();
        return url.toString();
    }
    /**
     * ä¸Šä¼  byte[] æ•°æ®åˆ° Amazon S3,使用指定的后缀构造对象键。
     *
     * @param data   è¦ä¸Šä¼ çš„ byte[] æ•°æ®
     * @param suffix å¯¹è±¡é”®çš„后缀
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix) {
        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
    }
    /**
     * ä¸Šä¼  InputStream åˆ° Amazon S3,使用指定的后缀构造对象键。
     *
     * @param inputStream è¦ä¸Šä¼ çš„输入流
     * @param suffix      å¯¹è±¡é”®çš„后缀
     * @param length      è¾“入流的长度
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
    }
    /**
     * ä¸Šä¼ æ–‡ä»¶åˆ° Amazon S3,使用指定的后缀构造对象键
     *
     * @param file   è¦ä¸Šä¼ çš„æ–‡ä»¶
     * @param suffix å¯¹è±¡é”®çš„后缀
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(File file, String suffix) {
        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
    }
    /**
     * èŽ·å–æ–‡ä»¶è¾“å…¥æµ
     *
     * @param path å®Œæ•´æ–‡ä»¶è·¯å¾„
     * @return è¾“入流
     */
    public InputStream getObjectContent(String path) throws IOException {
        // ä¸‹è½½æ–‡ä»¶åˆ°ä¸´æ—¶ç›®å½•
        Path tempFilePath = fileDownload(path);
        // åˆ›å»ºè¾“入流
        InputStream inputStream = Files.newInputStream(tempFilePath);
        // åˆ é™¤ä¸´æ—¶æ–‡ä»¶
        FileUtils.del(tempFilePath);
        // è¿”回对象内容的输入流
        return inputStream;
    }
    /**
     * èŽ·å– S3 å®¢æˆ·ç«¯çš„终端点 URL
     *
     * @return ç»ˆç«¯ç‚¹ URL
     */
    public String getEndpoint() {
        // æ ¹æ®é…ç½®æ–‡ä»¶ä¸­çš„æ˜¯å¦ä½¿ç”¨ HTTPS,设置协议头部
        String header = getIsHttps();
        // æ‹¼æŽ¥åè®®å¤´éƒ¨å’Œç»ˆç«¯ç‚¹ï¼Œå¾—到完整的终端点 URL
        return header + properties.getEndpoint();
    }
    /**
     * èŽ·å– S3 å®¢æˆ·ç«¯çš„终端点 URL(自定义域名)
     *
     * @return ç»ˆç«¯ç‚¹ URL
     */
    public String getDomain() {
        // ä»Žé…ç½®ä¸­èŽ·å–åŸŸåã€ç»ˆç«¯ç‚¹ã€æ˜¯å¦ä½¿ç”¨ HTTPS ç­‰ä¿¡æ¯
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getIsHttps();
        // å¦‚果是云服务商,直接返回域名或终端点
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
        }
        // å¦‚果是 MinIO,处理域名并返回
        if (StringUtils.isNotEmpty(domain)) {
            return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
        }
        // è¿”回终端点
        return header + endpoint;
    }
    /**
     * æ ¹æ®ä¼ å…¥çš„ region å‚数返回相应的 AWS åŒºåŸŸ
     * å¦‚æžœ region å‚数非空,使用 Region.of æ–¹æ³•创建并返回对应的 AWS åŒºåŸŸå¯¹è±¡
     * å¦‚æžœ region å‚数为空,返回一个默认的 AWS åŒºåŸŸï¼ˆä¾‹å¦‚,us-east-1),作为广泛支持的区域
     *
     * @return å¯¹åº”çš„ AWS åŒºåŸŸå¯¹è±¡ï¼Œæˆ–者默认的广泛支持的区域(us-east-1)
     */
    public Region of() {
        //AWS åŒºåŸŸå­—符串
        String region = properties.getRegion();
        // å¦‚æžœ region å‚数非空,使用 Region.of æ–¹æ³•创建对应的 AWS åŒºåŸŸå¯¹è±¡ï¼Œå¦åˆ™è¿”回默认区域
        return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
    }
    /**
     * èŽ·å–äº‘å­˜å‚¨æœåŠ¡çš„URL
     *
     * @return æ–‡ä»¶è·¯å¾„
     */
    public String getUrl() {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getIsHttps();
        // äº‘服务商直接返回
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
        }
        // MinIO å•独处理
        if (StringUtils.isNotEmpty(domain)) {
            // å¦‚æžœ domain ä»¥ "https://" æˆ– "http://" å¼€å¤´
            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
                domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
        }
        return header + endpoint + StringUtils.SLASH + properties.getBucketName();
    }
    /**
     * ç”Ÿæˆä¸€ä¸ªç¬¦åˆç‰¹å®šè§„则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性
     *
     * @param prefix å‰ç¼€
     * @param suffix åŽç¼€
     * @return æ–‡ä»¶è·¯å¾„
     */
    public String getPath(String prefix, String suffix) {
        // ç”Ÿæˆuuid
        String uuid = IdUtil.fastSimpleUUID();
        // ç”Ÿæˆæ—¥æœŸè·¯å¾„
        String datePath = DateUtils.datePath();
        // æ‹¼æŽ¥è·¯å¾„
        String path = StringUtils.isNotEmpty(prefix) ?
            prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
        return path + suffix;
    }
    /**
     * ç§»é™¤è·¯å¾„中的基础URL部分,得到相对路径
     *
     * @param path å®Œæ•´çš„路径,包括基础URL和相对路径
     * @return åŽ»é™¤åŸºç¡€URL后的相对路径
     */
    public String removeBaseUrl(String path) {
        return path.replace(getUrl() + StringUtils.SLASH, "");
    }
    /**
     * æœåС商
     */
    public String getConfigKey() {
        return configKey;
    }
    /**
     * èŽ·å–æ˜¯å¦ä½¿ç”¨ HTTPS çš„配置,并返回相应的协议头部。
     *
     * @return åè®®å¤´éƒ¨ï¼Œæ ¹æ®æ˜¯å¦ä½¿ç”¨ HTTPS è¿”回 "https://" æˆ– "http://"
     */
    public String getIsHttps() {
        return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
    }
    /**
@@ -231,32 +513,77 @@
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }
    /**
     * ç”Ÿæˆ AWS S3 å­˜å‚¨æ¡¶è®¿é—®ç­–ç•¥
     *
     * @param bucketName å­˜å‚¨æ¡¶
     * @param policyType æ¡¶ç­–略类型
     * @return ç¬¦åˆ AWS S3 å­˜å‚¨æ¡¶è®¿é—®ç­–略格式的字符串
     */
    private static String getPolicy(String bucketName, PolicyType policyType) {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
        builder.append(switch (policyType) {
            case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n";
            case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n";
            default -> "\"s3:GetBucketLocation\"\n";
        });
        builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("\"\n},\n");
        if (policyType == PolicyType.READ) {
            builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n},\n");
        }
        builder.append("{\n\"Action\": ");
        builder.append(switch (policyType) {
            case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
            case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
            default -> "\"s3:GetObject\",\n";
        });
        builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
        return builder.toString();
        String policy = switch (policyType) {
            case WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": []
                }
                """;
            case READ_WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:GetBucketLocation",
                        "s3:ListBucket",
                        "s3:ListBucketMultipartUploads"
                      ],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:AbortMultipartUpload",
                        "s3:DeleteObject",
                        "s3:GetObject",
                        "s3:ListMultipartUploadParts",
                        "s3:PutObject"
                      ],
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
            case READ -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": ["s3:GetBucketLocation"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Deny",
                      "Principal": "*",
                      "Action": ["s3:ListBucket"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": "s3:GetObject",
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
        };
        return policy.replaceAll("bucketName", bucketName);
    }
}
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java
@@ -21,4 +21,10 @@
     * æ–‡ä»¶å
     */
    private String filename;
    /**
     * å·²ä¸Šä¼ å¯¹è±¡çš„实体标记(用来校验文件)
     */
    private String eTag;
}
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java
@@ -1,8 +1,9 @@
package org.dromara.common.oss.enumd;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import lombok.AllArgsConstructor;
import lombok.Getter;
import software.amazon.awssdk.services.s3.model.BucketCannedACL;
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
/**
 * æ¡¶è®¿é—®ç­–略配置
@@ -16,27 +17,32 @@
    /**
     * private
     */
    PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
    PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
    /**
     * public
     */
    PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
    PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
    /**
     * custom
     */
    CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ);
    CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
    /**
     * æ¡¶ æƒé™ç±»åž‹
     * æ¡¶ æƒé™ç±»åž‹ï¼ˆæ•°æ®åº“值)
     */
    private final String type;
    /**
     * æ¡¶ æƒé™ç±»åž‹
     */
    private final BucketCannedACL bucketCannedACL;
    /**
     * æ–‡ä»¶å¯¹è±¡ æƒé™ç±»åž‹
     */
    private final CannedAccessControlList acl;
    private final ObjectCannedACL objectCannedACL;
    /**
     * æ¡¶ç­–略类型
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java
@@ -1,5 +1,6 @@
package org.dromara.common.oss.factory;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
@@ -9,10 +10,10 @@
import org.dromara.common.oss.properties.OssProperties;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.common.redis.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
 * æ–‡ä»¶ä¸Šä¼ Factory
@@ -23,6 +24,7 @@
public class OssFactory {
    private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
    private static final ReentrantLock LOCK = new ReentrantLock();
    /**
     * èŽ·å–é»˜è®¤å®žä¾‹
@@ -39,7 +41,7 @@
    /**
     * æ ¹æ®ç±»åž‹èŽ·å–å®žä¾‹
     */
    public static synchronized OssClient instance(String configKey) {
    public static OssClient instance(String configKey) {
        String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
        if (json == null) {
            throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
@@ -48,16 +50,19 @@
        // ä½¿ç”¨ç§Ÿæˆ·æ ‡è¯†é¿å…å¤šä¸ªç§Ÿæˆ·ç›¸åŒkey实例覆盖
        String key = properties.getTenantId() + ":" + configKey;
        OssClient client = CLIENT_CACHE.get(key);
        if (client == null) {
            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
            log.info("创建OSS实例 key => {}", configKey);
            return CLIENT_CACHE.get(key);
        }
        // é…ç½®ä¸ç›¸åŒåˆ™é‡æ–°æž„建
        if (!client.checkPropertiesSame(properties)) {
            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
            log.info("重载OSS实例 key => {}", configKey);
            return CLIENT_CACHE.get(key);
        // å®¢æˆ·ç«¯ä¸å­˜åœ¨æˆ–配置不相同则重新构建
        if (client == null || !client.checkPropertiesSame(properties)) {
            LOCK.lock();
            try {
                client = CLIENT_CACHE.get(key);
                if (client == null || !client.checkPropertiesSame(properties)) {
                    CLIENT_CACHE.put(key, new OssClient(configKey, properties));
                    log.info("创建OSS实例 key => {}", configKey);
                    return CLIENT_CACHE.get(key);
                }
            } finally {
                LOCK.unlock();
            }
        }
        return client;
    }
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
@@ -1,29 +1,29 @@
package org.dromara.common.ratelimiter.aspectj;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RateType;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
@@ -45,20 +45,17 @@
     */
    private final ParserContext parserContext = new TemplateParserContext();
    /**
     * å®šä¹‰spel上下文对象进行解析
     */
    private final EvaluationContext context = new StandardEvaluationContext();
    /**
     * æ–¹æ³•参数解析器
     */
    private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        String combineKey = getCombineKey(rateLimiter, point);
        try {
            String combineKey = getCombineKey(rateLimiter, point);
            RateType rateType = RateType.OVERALL;
            if (rateLimiter.limitType() == LimitType.CLUSTER) {
                rateType = RateType.PER_CLIENT;
@@ -76,42 +73,29 @@
            if (e instanceof ServiceException) {
                throw e;
            } else {
                throw new RuntimeException("服务器限流异常,请稍候再试");
                throw new RuntimeException("服务器限流异常,请稍候再试", e);
            }
        }
    }
    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
    private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        String key = rateLimiter.key();
        // èŽ·å–æ–¹æ³•(通过方法签名来获取)
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        // åˆ¤æ–­æ˜¯å¦æ˜¯spel格式
        if (StringUtils.containsAny(key, "#")) {
            // èŽ·å–å‚æ•°å€¼
        if (StringUtils.isNotBlank(key)) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method targetMethod = signature.getMethod();
            Object[] args = point.getArgs();
            // èŽ·å–æ–¹æ³•ä¸Šå‚æ•°çš„åç§°
            String[] parameterNames = pnd.getParameterNames(method);
            if (ArrayUtil.isEmpty(parameterNames)) {
                throw new ServiceException("限流key解析异常!请联系管理员!");
            //noinspection DataFlowIssue
            MethodBasedEvaluationContext context =
                new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
            context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
            Expression expression;
            if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
                && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
                expression = parser.parseExpression(key, parserContext);
            } else {
                expression = parser.parseExpression(key);
            }
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            // è§£æžè¿”回给key
            try {
                Expression expression;
                if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
                    && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
                    expression = parser.parseExpression(key, parserContext);
                } else {
                    expression = parser.parseExpression(key);
                }
                key = expression.getValue(context, String.class) + ":";
            } catch (Exception e) {
                throw new ServiceException("限流key解析异常!请联系管理员!");
            }
            key = expression.getValue(context, String.class);
        }
        StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
        stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
{
  "org.dromara.common.ratelimiter.annotation.RateLimiter@key": {
    "method": {
      "parameters": true
    }
  }
}
ruoyi-common/ruoyi-common-redis/pom.xml
@@ -32,6 +32,16 @@
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package org.dromara.common.redis.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.TimeUnit;
/**
 * ç¼“存配置
 *
 * @author Lion Li
 */
@AutoConfiguration
@EnableCaching
public class CacheConfig {
    /**
     * caffeine æœ¬åœ°ç¼“存处理器
     */
    @Bean
    public Cache<Object, Object> caffeine() {
        return Caffeine.newBuilder()
            // è®¾ç½®æœ€åŽä¸€æ¬¡å†™å…¥æˆ–访问后经过固定时间过期
            .expireAfterWrite(30, TimeUnit.SECONDS)
            // åˆå§‹çš„缓存空间大小
            .initialCapacity(100)
            // ç¼“存的最大条数
            .maximumSize(1000)
            .build();
    }
    /**
     * è‡ªå®šä¹‰ç¼“存管理器 æ•´åˆspring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new PlusSpringCacheManager();
    }
}
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java
@@ -5,10 +5,14 @@
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.dromara.common.redis.handler.RedisExceptionHandler;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec;
@@ -16,9 +20,12 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
 * redis配置
@@ -27,20 +34,22 @@
 */
@Slf4j
@AutoConfiguration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
    @Autowired
    private RedissonProperties redissonProperties;
    @Autowired
    private ObjectMapper objectMapper;
    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            ObjectMapper om = objectMapper.copy();
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
            ObjectMapper om = new ObjectMapper();
            om.registerModule(javaTimeModule);
            om.setTimeZone(TimeZone.getDefault());
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // æŒ‡å®šåºåˆ—化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
            om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
@@ -52,6 +61,9 @@
                // ç¼“å­˜ Lua è„šæœ¬ å‡å°‘网络传输(redisson å¤§éƒ¨åˆ†çš„功能都是基于 Lua è„šæœ¬å®žçŽ°)
                .setUseScriptCache(true)
                .setCodec(codec);
            if (SpringUtils.isVirtual()) {
                config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
            }
            RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
            if (ObjectUtil.isNotNull(singleServerConfig)) {
                // ä½¿ç”¨å•机模式
@@ -87,11 +99,11 @@
    }
    /**
     * è‡ªå®šä¹‰ç¼“存管理器 æ•´åˆspring-cache
     * å¼‚常处理器
     */
    @Bean
    public CacheManager cacheManager() {
        return new PlusSpringCacheManager();
    public RedisExceptionHandler redisExceptionHandler() {
        return new RedisExceptionHandler();
    }
    /**
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package org.dromara.common.redis.handler;
import cn.hutool.http.HttpStatus;
import com.baomidou.lock.exception.LockFailureException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
 * Redis异常处理器
 *
 * @author AprilWind
 */
@Slf4j
@RestControllerAdvice
public class RedisExceptionHandler {
    /**
     * åˆ†å¸ƒå¼é”Lock4j异常
     */
    @ExceptionHandler(LockFailureException.class)
    public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("获取锁失败了'{}',发生Lock4j异常." + requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试...");
    }
}
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
package org.dromara.common.redis.manager;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
/**
 * Cache è£…饰器模式(用于扩展 Caffeine ä¸€çº§ç¼“å­˜)
 *
 * @author LionLi
 */
public class CaffeineCacheDecorator implements Cache {
    private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
        CAFFEINE = SpringUtils.getBean("caffeine");
    private final Cache cache;
    public CaffeineCacheDecorator(Cache cache) {
        this.cache = cache;
    }
    @Override
    public String getName() {
        return cache.getName();
    }
    @Override
    public Object getNativeCache() {
        return cache.getNativeCache();
    }
    public String getUniqueKey(Object key) {
        return cache.getName() + ":" + key;
    }
    @Override
    public ValueWrapper get(Object key) {
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
        return (ValueWrapper) o;
    }
    @SuppressWarnings("unchecked")
    public <T> T get(Object key, Class<T> type) {
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
        return (T) o;
    }
    @Override
    public void put(Object key, Object value) {
        CAFFEINE.invalidate(getUniqueKey(key));
        cache.put(key, value);
    }
    public ValueWrapper putIfAbsent(Object key, Object value) {
        CAFFEINE.invalidate(getUniqueKey(key));
        return cache.putIfAbsent(key, value);
    }
    @Override
    public void evict(Object key) {
        evictIfPresent(key);
    }
    public boolean evictIfPresent(Object key) {
        boolean b = cache.evictIfPresent(key);
        if (b) {
            CAFFEINE.invalidate(getUniqueKey(key));
        }
        return b;
    }
    @Override
    public void clear() {
        cache.clear();
    }
    public boolean invalidate() {
        return cache.invalidate();
    }
    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
        return (T) o;
    }
}
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 RedissonCache(map, allowNullValues);
        Cache cache = new CaffeineCacheDecorator(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 RedissonCache(map, config, allowNullValues);
        Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
@@ -65,6 +65,12 @@
        consumer.accept(msg);
    }
    /**
     * å‘布消息到指定的频道
     *
     * @param channelKey é€šé“key
     * @param msg        å‘送数据
     */
    public static <T> void publish(String channelKey, T msg) {
        RTopic topic = CLIENT.getTopic(channelKey);
        topic.publish(msg);
@@ -107,7 +113,11 @@
                bucket.setAndKeepTTL(value);
            } catch (Exception e) {
                long timeToLive = bucket.remainTimeToLive();
                setCacheObject(key, value, Duration.ofMillis(timeToLive));
                if (timeToLive == -1) {
                    setCacheObject(key, value);
                } else {
                    setCacheObject(key, value, Duration.ofMillis(timeToLive));
                }
            }
        } else {
            bucket.set(value);
ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1,2 @@
org.dromara.common.redis.config.RedisConfig
org.dromara.common.redis.config.CacheConfig
ruoyi-common/ruoyi-common-satoken/pom.xml
@@ -36,6 +36,11 @@
            <artifactId>sa-token-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java
@@ -7,6 +7,7 @@
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
import org.dromara.common.satoken.core.service.SaPermissionImpl;
import org.dromara.common.satoken.handler.SaTokenExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
@@ -42,4 +43,12 @@
        return new PlusSaTokenDao();
    }
    /**
     * å¼‚常处理器
     */
    @Bean
    public SaTokenExceptionHandler saTokenExceptionHandler() {
        return new SaTokenExceptionHandler();
    }
}
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
@@ -2,26 +2,42 @@
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.hutool.core.lang.Console;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.utils.RedisUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * Sa-Token持久层接口(使用框架自带RedisUtils实现 åè®®ç»Ÿä¸€)
 * <p>
 * é‡‡ç”¨ caffeine + redis å¤šçº§ç¼“å­˜ ä¼˜åŒ–并发查询效率
 *
 * @author Lion Li
 */
public class PlusSaTokenDao implements SaTokenDao {
    private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
        // è®¾ç½®æœ€åŽä¸€æ¬¡å†™å…¥æˆ–访问后经过固定时间过期
        .expireAfterWrite(5, TimeUnit.SECONDS)
        // åˆå§‹çš„缓存空间大小
        .initialCapacity(100)
        // ç¼“存的最大条数
        .maximumSize(1000)
        .build();
    /**
     * èŽ·å–Value,如无返空
     */
    @Override
    public String get(String key) {
        return RedisUtils.getCacheObject(key);
        Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
        return (String) o;
    }
    /**
@@ -38,6 +54,7 @@
        } else {
            RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
        }
        CAFFEINE.put(key, value);
    }
    /**
@@ -47,6 +64,7 @@
    public void update(String key, String value) {
        if (RedisUtils.hasKey(key)) {
            RedisUtils.setCacheObject(key, value, true);
            CAFFEINE.put(key, value);
        }
    }
@@ -81,7 +99,8 @@
     */
    @Override
    public Object getObject(String key) {
        return RedisUtils.getCacheObject(key);
        Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
        return o;
    }
    /**
@@ -98,6 +117,7 @@
        } else {
            RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
        }
        CAFFEINE.put(key, object);
    }
    /**
@@ -107,6 +127,7 @@
    public void updateObject(String key, Object object) {
        if (RedisUtils.hasKey(key)) {
            RedisUtils.setCacheObject(key, object, true);
            CAFFEINE.put(key, object);
        }
    }
@@ -139,10 +160,14 @@
    /**
     * æœç´¢æ•°æ®
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
        Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*");
        List<String> list = new ArrayList<>(keys);
        return SaFoxUtil.searchList(list, start, size, sortType);
        String keyStr = prefix + "*" + keyword + "*";
        return (List<String>) CAFFEINE.get(keyStr, k -> {
            Collection<String> keys = RedisUtils.keys(keyStr);
            List<String> list = new ArrayList<>(keys);
            return SaFoxUtil.searchList(list, start, size, sortType);
        });
    }
}
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.dromara.common.satoken.handler;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
 * SaToken异常处理器
 *
 * @author Lion Li
 */
@Slf4j
@RestControllerAdvice
public class SaTokenExceptionHandler {
    /**
     * æƒé™ç å¼‚常
     */
    @ExceptionHandler(NotPermissionException.class)
    public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
    /**
     * è§’色权限异常
     */
    @ExceptionHandler(NotRoleException.class)
    public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
    /**
     * è®¤è¯å¤±è´¥
     */
    @ExceptionHandler(NotLoginException.class)
    public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
    }
}
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -1,7 +1,5 @@
package org.dromara.common.satoken.utils;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
@@ -15,7 +13,6 @@
import org.dromara.common.core.enums.UserType;
import java.util.Set;
import java.util.function.Supplier;
/**
 * ç™»å½•鉴权助手
@@ -35,9 +32,11 @@
    public static final String LOGIN_USER_KEY = "loginUser";
    public static final String TENANT_KEY = "tenantId";
    public static final String USER_KEY = "userId";
    public static final String USER_NAME_KEY = "userName";
    public static final String DEPT_KEY = "deptId";
    public static final String DEPT_NAME_KEY = "deptName";
    public static final String DEPT_CATEGORY_KEY = "deptCategory";
    public static final String CLIENT_KEY = "clientid";
    public static final String TENANT_ADMIN_KEY = "isTenantAdmin";
    /**
     * ç™»å½•系统 åŸºäºŽ è®¾å¤‡ç±»åž‹
@@ -47,32 +46,27 @@
     * @param model     é…ç½®å‚æ•°
     */
    public static void login(LoginUser loginUser, SaLoginModel model) {
        SaStorage storage = SaHolder.getStorage();
        storage.set(LOGIN_USER_KEY, loginUser);
        storage.set(TENANT_KEY, loginUser.getTenantId());
        storage.set(USER_KEY, loginUser.getUserId());
        storage.set(DEPT_KEY, loginUser.getDeptId());
        model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
        StpUtil.login(loginUser.getLoginId(),
            model.setExtra(TENANT_KEY, loginUser.getTenantId())
                .setExtra(USER_KEY, loginUser.getUserId())
                .setExtra(DEPT_KEY, loginUser.getDeptId()));
        SaSession tokenSession = StpUtil.getTokenSession();
        tokenSession.updateTimeout(model.getTimeout());
        tokenSession.set(LOGIN_USER_KEY, loginUser);
                .setExtra(USER_NAME_KEY, loginUser.getUsername())
                .setExtra(DEPT_KEY, loginUser.getDeptId())
                .setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
                .setExtra(DEPT_CATEGORY_KEY, loginUser.getDeptCategory())
        );
        StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
    }
    /**
     * èŽ·å–ç”¨æˆ·(多级缓存)
     */
    public static LoginUser getLoginUser() {
        return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
            SaSession session = StpUtil.getTokenSession();
            if (ObjectUtil.isNull(session)) {
                return null;
            }
            return session.get(LOGIN_USER_KEY);
        });
        SaSession session = StpUtil.getTokenSession();
        if (ObjectUtil.isNull(session)) {
            return null;
        }
        return (LoginUser) session.get(LOGIN_USER_KEY);
    }
    /**
@@ -90,7 +84,7 @@
     * èŽ·å–ç”¨æˆ·id
     */
    public static Long getUserId() {
        return  Convert.toLong(getExtra(USER_KEY));
        return Convert.toLong(getExtra(USER_KEY));
    }
    /**
@@ -107,8 +101,32 @@
        return Convert.toLong(getExtra(DEPT_KEY));
    }
    /**
     * èŽ·å–éƒ¨é—¨å
     */
    public static String getDeptName() {
        return Convert.toStr(getExtra(DEPT_NAME_KEY));
    }
    /**
     * èŽ·å–éƒ¨é—¨ç±»åˆ«ç¼–ç 
     */
    public static String getDeptCategory() {
        return Convert.toStr(getExtra(DEPT_CATEGORY_KEY));
    }
    /**
     * èŽ·å–å½“å‰ Token çš„æ‰©å±•信息
     *
     * @param key é”®å€¼
     * @return å¯¹åº”的扩展数据
     */
    private static Object getExtra(String key) {
        return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key));
        try {
            return StpUtil.getExtra(key);
        } catch (Exception e) {
            return null;
        }
    }
    /**
@@ -136,12 +154,17 @@
        return UserConstants.SUPER_ADMIN_ID.equals(userId);
    }
    /**
     * æ˜¯å¦ä¸ºè¶…级管理员
     *
     * @return ç»“æžœ
     */
    public static boolean isSuperAdmin() {
        return isSuperAdmin(getUserId());
    }
    /**
     * æ˜¯å¦ä¸ºè¶…级管理员
     * æ˜¯å¦ä¸ºç§Ÿæˆ·ç®¡ç†å‘˜
     *
     * @param rolePermission è§’色权限标识组
     * @return ç»“æžœ
@@ -150,27 +173,22 @@
        return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
    }
    /**
     * æ˜¯å¦ä¸ºç§Ÿæˆ·ç®¡ç†å‘˜
     *
     * @return ç»“æžœ
     */
    public static boolean isTenantAdmin() {
        Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> {
            return isTenantAdmin(getLoginUser().getRolePermission());
        });
        return Convert.toBool(value);
        return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
    }
    /**
     * æ£€æŸ¥å½“前用户是否已登录
     *
     * @return ç»“æžœ
     */
    public static boolean isLogin() {
        return getLoginUser() != null;
    }
    public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
        try {
            Object obj = SaHolder.getStorage().get(key);
            if (ObjectUtil.isNull(obj)) {
                obj = handle.get();
                SaHolder.getStorage().set(key, obj);
            }
            return obj;
        } catch (Exception e) {
            return null;
        }
    }
}
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
@@ -4,14 +4,14 @@
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.security.config.properties.SecurityProperties;
import org.dromara.common.security.handler.AllUrlHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@@ -38,35 +38,35 @@
    public void addInterceptors(InterceptorRegistry registry) {
        // æ³¨å†Œè·¯ç”±æ‹¦æˆªå™¨ï¼Œè‡ªå®šä¹‰éªŒè¯è§„则
        registry.addInterceptor(new SaInterceptor(handler -> {
            AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
            // ç™»å½•验证 -- æŽ’除多个路径
            SaRouter
                // èŽ·å–æ‰€æœ‰çš„
                .match(allUrlHandler.getUrls())
                // å¯¹æœªæŽ’除的路径进行检查
                .check(() -> {
                    // æ£€æŸ¥æ˜¯å¦ç™»å½• æ˜¯å¦æœ‰token
                    StpUtil.checkLogin();
                AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
                // ç™»å½•验证 -- æŽ’除多个路径
                SaRouter
                    // èŽ·å–æ‰€æœ‰çš„
                    .match(allUrlHandler.getUrls())
                    // å¯¹æœªæŽ’除的路径进行检查
                    .check(() -> {
                        // æ£€æŸ¥æ˜¯å¦ç™»å½• æ˜¯å¦æœ‰token
                        StpUtil.checkLogin();
                    // æ£€æŸ¥ header ä¸Ž param é‡Œçš„ clientid ä¸Ž token é‡Œçš„æ˜¯å¦ä¸€è‡´
                    String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
                    String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
                    String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
                    if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
                        // token æ— æ•ˆ
                        throw NotLoginException.newInstance(StpUtil.getLoginType(),
                            "-100", "客户端ID与Token不匹配",
                            StpUtil.getTokenValue());
                    }
                        // æ£€æŸ¥ header ä¸Ž param é‡Œçš„ clientid ä¸Ž token é‡Œçš„æ˜¯å¦ä¸€è‡´
                        String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
                        String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
                        String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
                        if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
                            // token æ— æ•ˆ
                            throw NotLoginException.newInstance(StpUtil.getLoginType(),
                                "-100", "客户端ID与Token不匹配",
                                StpUtil.getTokenValue());
                        }
                    // æœ‰æ•ˆçŽ‡å½±å“ ç”¨äºŽä¸´æ—¶æµ‹è¯•
                    // if (log.isDebugEnabled()) {
                    //     log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
                    //     log.info("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
                    // }
                        // æœ‰æ•ˆçŽ‡å½±å“ ç”¨äºŽä¸´æ—¶æµ‹è¯•
                        // if (log.isDebugEnabled()) {
                        //     log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
                        //     log.info("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
                        // }
                });
        })).addPathPatterns("/**")
                    });
            })).addPathPatterns("/**")
            // æŽ’除不需要拦截的路径
            .excludePathPatterns(securityProperties.getExcludes());
    }
ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,3 +1,2 @@
org.dromara.common.security.handler.GlobalExceptionHandler
org.dromara.common.security.handler.AllUrlHandler
org.dromara.common.security.config.SecurityConfig
ruoyi-common/ruoyi-common-sms/pom.xml
@@ -20,13 +20,12 @@
        <dependency>
            <groupId>org.dromara.sms4j</groupId>
            <artifactId>sms4j-spring-boot-starter</artifactId>
            <exclusions>
                <!-- æŽ’除京东短信内存在的fastjson等待作者后续修复 -->
                <exclusion>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- RuoYi Common Redis-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-redis</artifactId>
        </dependency>
    </dependencies>
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
@@ -1,14 +1,24 @@
package org.dromara.common.sms.config;
import org.dromara.common.sms.core.dao.PlusSmsDao;
import org.dromara.sms4j.api.dao.SmsDao;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
 * çŸ­ä¿¡é…ç½®ç±»(暂时没用 é¢„留扩展)
 * çŸ­ä¿¡é…ç½®ç±»
 *
 * @author Lion Li
 * @version 4.2.0
 * @author Feng
 */
@AutoConfiguration
@AutoConfiguration(after = {RedisAutoConfiguration.class})
public class SmsAutoConfiguration {
    @Primary
    @Bean
    public SmsDao smsDao() {
        return new PlusSmsDao();
    }
}
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package org.dromara.common.sms.core.dao;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.sms4j.api.dao.SmsDao;
import java.time.Duration;
/**
 * SmsDao缓存配置 (使用框架自带RedisUtils实现 åè®®ç»Ÿä¸€)
 * <p>主要用于短信重试和拦截的缓存
 *
 * @author Feng
 */
public class PlusSmsDao implements SmsDao {
    /**
     * å­˜å‚¨
     *
     * @param key       é”®
     * @param value     å€¼
     * @param cacheTime ç¼“存时间(单位:秒)
     */
    @Override
    public void set(String key, Object value, long cacheTime) {
        RedisUtils.setCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key, value, Duration.ofSeconds(cacheTime));
    }
    /**
     * å­˜å‚¨
     *
     * @param key   é”®
     * @param value å€¼
     */
    @Override
    public void set(String key, Object value) {
        RedisUtils.setCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key, value, true);
    }
    /**
     * è¯»å–
     *
     * @param key é”®
     * @return å€¼
     */
    @Override
    public Object get(String key) {
        return RedisUtils.getCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }
    /**
     * remove
     * <p> æ ¹æ®key移除缓存
     *
     * @param key ç¼“存键
     * @return è¢«åˆ é™¤çš„value
     * @author :Wind
     */
    @Override
    public Object remove(String key) {
        return RedisUtils.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }
    /**
     * æ¸…空
     */
    @Override
    public void clean() {
        RedisUtils.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + "sms:");
    }
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
@@ -2,6 +2,8 @@
import lombok.Data;
import java.util.List;
/**
 * ç¤¾äº¤ç™»å½•配置
 *
@@ -65,4 +67,9 @@
     */
    private String serverUrl;
    /**
     * è¯·æ±‚范围
     */
    private List<String> scopes;
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
@@ -17,11 +17,6 @@
public class SocialProperties {
    /**
     * æ˜¯å¦å¯ç”¨
     */
    private Boolean enabled;
    /**
     * æŽˆæƒç±»åž‹
     */
    private Map<String, SocialLoginConfigProperties> type;
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package org.dromara.common.social.topiam;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;
import static org.dromara.common.social.topiam.AuthTopiamSource.TOPIAM;
/**
 * TopIAM è®¤è¯è¯·æ±‚
 *
 * @author xlsea
 * @since 2024-01-06
 */
@Slf4j
public class AuthTopIamRequest extends AuthDefaultRequest {
    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.topiam.server-url");
    /**
     * è®¾å®šå½’属域
     */
    public AuthTopIamRequest(AuthConfig config) {
        super(config, TOPIAM);
    }
    public AuthTopIamRequest(AuthConfig config, AuthStateCache authStateCache) {
        super(config, TOPIAM, authStateCache);
    }
    @Override
    protected AuthToken getAccessToken(AuthCallback authCallback) {
        String body = doPostAuthorizationCode(authCallback.getCode());
        Dict object = JsonUtils.parseMap(body);
        checkResponse(object);
        return AuthToken.builder()
            .accessToken(object.getStr("access_token"))
            .refreshToken(object.getStr("refresh_token"))
            .idToken(object.getStr("id_token"))
            .tokenType(object.getStr("token_type"))
            .scope(object.getStr("scope"))
            .build();
    }
    @Override
    protected AuthUser getUserInfo(AuthToken authToken) {
        String body = doGetUserInfo(authToken);
        Dict object = JsonUtils.parseMap(body);
        checkResponse(object);
        return AuthUser.builder()
            .uuid(object.getStr("sub"))
            .username(object.getStr("preferred_username"))
            .nickname(object.getStr("nickname"))
            .avatar(object.getStr("picture"))
            .email(object.getStr("email"))
            .token(authToken)
            .source(source.toString())
            .build();
    }
    @Override
    protected String doGetUserInfo(AuthToken authToken) {
        return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
            .add("Content-Type", "application/json")
            .add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
    }
    @Override
    public String authorize(String state) {
        return UrlBuilder.fromBaseUrl(super.authorize(state))
            .queryParam("scope", StrUtil.join("%20", config.getScopes()))
            .build();
    }
    public static void checkResponse(Dict object) {
        // oauth/token éªŒè¯å¼‚常
        if (object.containsKey("error")) {
            throw new AuthException(object.getStr("error_description"));
        }
        // user éªŒè¯å¼‚常
        if (object.containsKey("message")) {
            throw new AuthException(object.getStr("message"));
        }
    }
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopiamSource.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.common.social.topiam;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
/**
 * Oauth2 é»˜è®¤æŽ¥å£è¯´æ˜Ž
 *
 * @author xlsea
 * @since 2024-01-06
 */
public enum AuthTopiamSource implements AuthSource {
    /**
     * æµ‹è¯•
     */
    TOPIAM {
        /**
         * æŽˆæƒçš„api
         */
        @Override
        public String authorize() {
            return AuthTopIamRequest.SERVER_URL + "/oauth2/auth";
        }
        /**
         * èŽ·å–accessToken的api
         */
        @Override
        public String accessToken() {
            return AuthTopIamRequest.SERVER_URL + "/oauth2/token";
        }
        /**
         * èŽ·å–ç”¨æˆ·ä¿¡æ¯çš„api
         */
        @Override
        public String userInfo() {
            return AuthTopIamRequest.SERVER_URL + "/oauth2/userinfo";
        }
        /**
         * å¹³å°å¯¹åº”çš„ AuthRequest å®žçŽ°ç±»ï¼Œå¿…é¡»ç»§æ‰¿è‡ª {@link AuthDefaultRequest}
         */
        @Override
        public Class<? extends AuthDefaultRequest> getTargetClass() {
            return AuthTopIamRequest.class;
        }
    }
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -11,6 +11,7 @@
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
import org.dromara.common.social.topiam.AuthTopIamRequest;
/**
 * è®¤è¯æŽˆæƒå·¥å…·ç±»
@@ -38,7 +39,8 @@
        AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
            .clientId(obj.getClientId())
            .clientSecret(obj.getClientSecret())
            .redirectUri(obj.getRedirectUri());
            .redirectUri(obj.getRedirectUri())
            .scopes(obj.getScopes());
        return switch (source.toLowerCase()) {
            case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
            case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
@@ -63,6 +65,7 @@
            case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
            case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
            case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
            case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
            default -> throw new AuthException("未获取到有效的Auth配置");
        };
    }
ruoyi-common/ruoyi-common-tenant/pom.xml
@@ -19,16 +19,12 @@
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-mybatis</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
    </dependencies>
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java
@@ -2,8 +2,6 @@
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.config.MybatisPlusConfig;
@@ -18,14 +16,12 @@
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.ArrayList;
import java.util.List;
/**
 * ç§Ÿæˆ·é…ç½®ç±»
@@ -33,29 +29,22 @@
 * @author Lion Li
 */
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class})
@AutoConfiguration(after = {RedisConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
    /**
     * åˆå§‹åŒ–租户配置
     */
    @Bean
    public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor,
                              TenantProperties tenantProperties) {
        List<InnerInterceptor> interceptors = new ArrayList<>();
        // å¤šç§Ÿæˆ·æ’ä»¶ å¿…须放到第一位
        interceptors.add(tenantLineInnerInterceptor(tenantProperties));
        interceptors.addAll(mybatisPlusInterceptor.getInterceptors());
        mybatisPlusInterceptor.setInterceptors(interceptors);
        return true;
    }
    @ConditionalOnBean(MybatisPlusConfig.class)
    @AutoConfiguration(after = {MybatisPlusConfig.class})
    static class MybatisPlusConfigation {
    /**
     * å¤šç§Ÿæˆ·æ’ä»¶
     */
    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
        return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
        /**
         * å¤šç§Ÿæˆ·æ’ä»¶
         */
        @Bean
        public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
            return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
        }
    }
    @Bean
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
@@ -1,5 +1,6 @@
package org.dromara.common.tenant.handle;
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;
@@ -26,6 +27,9 @@
        if (StringUtils.isBlank(name)) {
            return null;
        }
        if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
            return super.map(name);
        }
        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
            return super.map(name);
        }
@@ -49,6 +53,9 @@
        if (StringUtils.isBlank(unmap)) {
            return null;
        }
        if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
            return super.unmap(name);
        }
        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
            return super.unmap(name);
        }
ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java
@@ -20,7 +20,9 @@
    @Override
    public String translation(Object key, String other) {
        if (key instanceof Long id) {
            return userService.selectNicknameById(id);
            return userService.selectNicknameByIds(id.toString());
        } else if (key instanceof String ids) {
            return userService.selectNicknameByIds(ids);
        }
        return null;
    }
ruoyi-common/ruoyi-common-web/pom.xml
@@ -57,11 +57,6 @@
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-crypto</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java
@@ -23,7 +23,7 @@
    private static final int WIDTH = 160;
    private static final int HEIGHT = 60;
    private static final Color BACKGROUND = Color.PINK;
    private static final Color BACKGROUND = Color.LIGHT_GRAY;
    private static final Font FONT = new Font("Arial", Font.BOLD, 48);
    /**
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java
@@ -1,5 +1,6 @@
package org.dromara.common.web.config;
import org.dromara.common.web.handler.GlobalExceptionHandler;
import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -49,4 +50,12 @@
        // è¿”回新的CorsFilter
        return new CorsFilter(source);
    }
    /**
     * å…¨å±€å¼‚常处理器
     */
    @Bean
    public GlobalExceptionHandler globalExceptionHandler() {
        return new GlobalExceptionHandler();
    }
}
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
@@ -2,9 +2,11 @@
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.task.VirtualThreadTaskExecutor;
/**
 * Undertow è‡ªå®šä¹‰é…ç½®
@@ -24,6 +26,12 @@
            WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
            deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
            // ä½¿ç”¨è™šæ‹Ÿçº¿ç¨‹
            if (SpringUtils.isVirtual()) {
                VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
                deploymentInfo.setExecutor(executor);
                deploymentInfo.setAsyncExecutor(executor);
            }
        });
    }
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
ÎļþÃû´Ó ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java ÐÞ¸Ä
@@ -1,8 +1,5 @@
package org.dromara.common.security.handler;
package org.dromara.common.web.handler;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
@@ -21,6 +18,7 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
/**
 * å…¨å±€å¼‚常处理器
@@ -32,36 +30,6 @@
public class GlobalExceptionHandler {
    /**
     * æƒé™ç å¼‚常
     */
    @ExceptionHandler(NotPermissionException.class)
    public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
    /**
     * è§’色权限异常
     */
    @ExceptionHandler(NotRoleException.class)
    public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
    /**
     * è®¤è¯å¤±è´¥
     */
    @ExceptionHandler(NotLoginException.class)
    public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
        return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
    }
    /**
     * è¯·æ±‚方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@@ -69,7 +37,7 @@
                                                                HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return R.fail(e.getMessage());
        return R.fail(HttpStatus.HTTP_BAD_METHOD, e.getMessage());
    }
    /**
@@ -112,6 +80,16 @@
    }
    /**
     * æ‰¾ä¸åˆ°è·¯ç”±
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public R<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}'不存在.", requestURI);
        return R.fail(HttpStatus.HTTP_NOT_FOUND, e.getMessage());
    }
    /**
     * æ‹¦æˆªæœªçŸ¥çš„运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
@@ -2,15 +2,14 @@
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
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;
import jakarta.servlet.http.HttpServletRequest;
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;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@@ -30,7 +29,7 @@
    private final String prodProfile = "prod";
    private final TransmittableThreadLocal<StopWatch> invokeTimeTL = new TransmittableThreadLocal<>();
    private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
@@ -56,7 +55,7 @@
            }
            StopWatch stopWatch = new StopWatch();
            invokeTimeTL.set(stopWatch);
            KEY_CACHE.set(stopWatch);
            stopWatch.start();
        }
        return true;
@@ -70,10 +69,10 @@
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
            StopWatch stopWatch = invokeTimeTL.get();
            StopWatch stopWatch = KEY_CACHE.get();
            stopWatch.stop();
            log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
            invokeTimeTL.remove();
            KEY_CACHE.remove();
        }
    }
ruoyi-extend/pom.xml
@@ -13,7 +13,7 @@
    <modules>
        <module>ruoyi-monitor-admin</module>
        <module>ruoyi-powerjob-server</module>
        <module>ruoyi-snailjob-server</module>
    </modules>
</project>
ruoyi-extend/ruoyi-monitor-admin/Dockerfile
@@ -13,5 +13,6 @@
ADD ./target/ruoyi-monitor-admin.jar ./app.jar
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar app.jar \
           -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS}
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
           -jar app.jar
ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/AdminServerConfig.java
@@ -3,7 +3,7 @@
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@@ -23,7 +23,7 @@
    @Lazy
    @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    @ConditionalOnMissingBean(Executor.class)
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
    public ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
        return builder.build();
    }
ruoyi-extend/ruoyi-powerjob-server/Dockerfile
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/pom.xml
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/java/org/dromara/powerjob/PowerJobServerApplication.java
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application-dev.properties
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application-prod.properties
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/application.properties
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/banner.txt
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-powerjob-server/src/main/resources/logback-plus.xml
ÎļþÒÑɾ³ý
ruoyi-extend/ruoyi-snailjob-server/Dockerfile
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/snailjob/logs
WORKDIR /ruoyi/snailjob
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8800
EXPOSE 1788
ADD ./target/ruoyi-snailjob-server.jar ./app.jar
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
           -jar app.jar
ruoyi-extend/ruoyi-snailjob-server/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
<?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-extend</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>ruoyi-snailjob-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-server-starter</artifactId>
            <version>${snailjob.version}</version>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>${spring-boot-admin.version}</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.dromara.snailjob;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * SnailJob Server å¯åŠ¨ç¨‹åº
 *
 * @author opensnail
 * @date 2024-05-17
 */
@SpringBootApplication
public class SnailJobServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(com.aizuda.snailjob.server.SnailJobServerApplication.class, args);
    }
}
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
    hikari:
      connection-timeout: 30000
      validation-timeout: 5000
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 600000
      max-lifetime: 900000
      keepaliveTime: 30000
--- # snail-job æœåŠ¡ç«¯é…ç½®
snail-job:
  # æ‹‰å–重试数据的每批次的大小
  retry-pull-page-size: 1000
  # æ‹‰å–重试数据的每批次的大小
  job-pull-page-size: 1000
  # æœåŠ¡ç«¯netty端口
  netty-port: 1788
  # ä¸€ä¸ªå®¢æˆ·ç«¯æ¯ç§’最多接收的重试数量指令
  limiter: 1000
  # å·æ®µæ¨¡å¼ä¸‹æ­¥é•¿é…ç½®
  step: 100
  # æ—¥å¿—保存时间(单位: day)
  log-storage: 90
  # å›žè°ƒé…ç½®
  callback:
    #回调最大执行次数
    max-count: 288
    #间隔时间
    trigger-interval: 900
  retry-max-pull-count: 10
--- # ç›‘控中心配置
spring.boot.admin.client:
  # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
  enabled: true
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
  username: ruoyi
  password: 123456
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
    hikari:
      connection-timeout: 30000
      validation-timeout: 5000
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 600000
      max-lifetime: 900000
      keepaliveTime: 30000
--- # snail-job æœåŠ¡ç«¯é…ç½®
snail-job:
  # æ‹‰å–重试数据的每批次的大小
  retry-pull-page-size: 1000
  # æ‹‰å–重试数据的每批次的大小
  job-pull-page-size: 1000
  # æœåŠ¡ç«¯ netty ç«¯å£
  netty-port: 1788
  # ä¸€ä¸ªå®¢æˆ·ç«¯æ¯ç§’最多接收的重试数量指令
  limiter: 1000
  # å·æ®µæ¨¡å¼ä¸‹æ­¥é•¿é…ç½®
  step: 100
  # æ—¥å¿—保存时间(单位: day)
  log-storage: 90
  # å›žè°ƒé…ç½®
  callback:
    #回调最大执行次数
    max-count: 288
    #间隔时间
    trigger-interval: 900
  retry-max-pull-count: 10
--- # ç›‘控中心配置
spring.boot.admin.client:
  # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
  enabled: true
  url: http://localhost:9090/admin
  instance:
    service-host-type: IP
  username: ruoyi
  password: 123456
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
server:
  port: 8800
  servlet:
    context-path: /snail-job
spring:
  application:
    name: ruoyi-snailjob-server
  profiles:
    active: @profiles.active@
  web:
    resources:
      static-locations: classpath:admin/
mybatis-plus:
  typeAliasesPackage: com.aizuda.snailjob.template.datasource.persistence.po
  global-config:
    db-config:
      where-strategy: NOT_EMPTY
      capital-mode: false
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
logging:
  config: classpath:logback-plus.xml
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: ALWAYS
    logfile:
      external-file: ./logs/ruoyi-snailjob-server/console.log
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
Application Version: ${revision}
Spring Boot Version: ${spring-boot.version}
                 _ _ _       _
                (_) (_)     | |
 ___ _ __   __ _ _| |_  ___ | |__ ______ ___  ___ _ ____   _____ _ __
/ __| '_ \ / _` | | | |/ _ \| '_ \______/ __|/ _ \ '__\ \ / / _ \ '__|
\__ \ | | | (_| | | | | (_) | |_) |     \__ \  __/ |   \ V /  __/ |
|___/_| |_|\__,_|_|_| |\___/|_.__/      |___/\___|_|    \_/ \___|_|
                   _/ |
                  |__/
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="log.path" value="./logs/ruoyi-snailjob-server" />
    <property name="console.log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
    <!-- æŽ§åˆ¶å°è¾“出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${console.log.pattern}</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>
    <!-- æŽ§åˆ¶å°è¾“出 -->
    <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/console.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- æ—¥å¿—文件名格式 -->
            <fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- æ—¥å¿—最大 1天 -->
            <maxHistory>1</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- è¿‡æ»¤çš„级别 -->
            <level>INFO</level>
        </filter>
    </appender>
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
            <MaxHistory>60</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log
            </FileNamePattern>
            <MaxHistory>60</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <appender name ="async_info" class= "ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold >100</discardingThreshold>
        <queueSize>1024</queueSize>
        <appender-ref ref ="file_info"/>
    </appender>
    <appender name ="async_error" class= "ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold >100</discardingThreshold>
        <queueSize>1024</queueSize>
        <appender-ref ref ="file_error"/>
    </appender>
    <!-- SnailJob appender -->
    <appender name="snail_log_server_appender" class="com.aizuda.snailjob.server.common.appender.SnailJobServerLogbackAppender">
    </appender>
    <!-- æŽ§åˆ¶å°è¾“出日志级别 -->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="async_info" />
        <appender-ref ref="async_error" />
        <appender-ref ref="snail_log_server_appender" />
    </root>
</configuration>
ruoyi-modules/pom.xml
@@ -14,6 +14,7 @@
        <module>ruoyi-generator</module>
        <module>ruoyi-job</module>
        <module>ruoyi-system</module>
        <module>ruoyi-workflow</module>
    </modules>
    <artifactId>ruoyi-modules</artifactId>
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java
@@ -5,7 +5,6 @@
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -25,7 +24,6 @@
@RestController
@RequestMapping("/demo/sms")
public class SmsController {
    /**
     * å‘送短信Aliyun
     *
@@ -36,7 +34,7 @@
    public R<Object> sendAliyun(String phones, String templateId) {
        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
        map.put("code", "1234");
        SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
        SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
        SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
        return R.ok(smsResponse);
    }
@@ -52,9 +50,33 @@
        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
//        map.put("2", "测试测试");
        map.put("1", "1234");
        SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.TENCENT);
        SmsBlend smsBlend = SmsFactory.getSmsBlend("config2");
        SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
        return R.ok(smsResponse);
    }
    /**
     * æ·»åŠ é»‘åå•
     *
     * @param phone æ‰‹æœºå·
     */
    @GetMapping("/addBlacklist")
    public R<Object> addBlacklist(String phone){
        SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
        smsBlend.joinInBlacklist(phone);
        return R.ok();
    }
    /**
     * ç§»é™¤é»‘名单
     *
     * @param phone æ‰‹æœºå·
     */
    @GetMapping("/removeBlacklist")
    public R<Object> removeBlacklist(String phone){
        SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
        smsBlend.removeFromBlacklist(phone);
        return R.ok();
    }
}
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
@@ -42,7 +42,7 @@
    /**
     * åˆ—描述
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String columnComment;
    /**
@@ -64,43 +64,43 @@
    /**
     * æ˜¯å¦ä¸»é”®ï¼ˆ1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isPk;
    /**
     * æ˜¯å¦è‡ªå¢žï¼ˆ1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isIncrement;
    /**
     * æ˜¯å¦å¿…填(1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isRequired;
    /**
     * æ˜¯å¦ä¸ºæ’入字段(1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isInsert;
    /**
     * æ˜¯å¦ç¼–辑字段(1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isEdit;
    /**
     * æ˜¯å¦åˆ—表字段(1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isList;
    /**
     * æ˜¯å¦æŸ¥è¯¢å­—段(1是)
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.VARCHAR)
    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
    private String isQuery;
    /**
ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
@@ -19,7 +19,8 @@
            select table_name, table_comment, create_time, update_time
            from information_schema.tables
            where table_schema = (select database())
            AND table_name NOT LIKE 'pj_%' AND table_name NOT LIKE 'gen_%'
            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">
@@ -40,7 +41,8 @@
            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 'pj_%' AND dt.table_name NOT LIKE 'GEN_%'
            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">
@@ -69,7 +71,8 @@
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'pj_%' AND table_name NOT LIKE 'gen_%'
            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">
@@ -92,7 +95,8 @@
            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 'pj_%' AND D.NAME NOT LIKE 'gen_%'
                    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">
@@ -112,7 +116,9 @@
    <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_name NOT LIKE 'pj_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
            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}
@@ -124,7 +130,8 @@
            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 'pj_%' AND dt.table_name NOT LIKE 'GEN_%'
            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}
@@ -144,7 +151,8 @@
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'pj_%' and table_name NOT LIKE 'gen_%'
            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}
@@ -158,7 +166,8 @@
            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 'pj_%' AND D.NAME NOT LIKE 'gen_%'
                    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}
@@ -169,7 +178,9 @@
    <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_name NOT LIKE 'pj_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database())
            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()">
@@ -178,7 +189,8 @@
            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 'pj_%' AND dt.table_name NOT LIKE 'GEN_%'
            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>
@@ -196,7 +208,8 @@
                    AND n.nspname = 'public'::name
                    AND n.nspname <![CDATA[ <> ]]> ''::name
            ) list_table
            where table_name NOT LIKE 'pj_%' and table_name NOT LIKE 'gen_%'
            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()">
@@ -207,7 +220,8 @@
            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 'pj_%' AND D.NAME NOT LIKE 'gen_%'
                    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>
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
@@ -20,7 +20,7 @@
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.bo.${ClassName}Bo;
import ${packageName}.service.I${ClassName}Service;
#if($table.crud || $table.sub)
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
#elseif($table.tree)
#end
@@ -44,7 +44,7 @@
     */
    @SaCheckPermission("${permissionPrefix}:list")
    @GetMapping("/list")
#if($table.crud || $table.sub)
#if($table.crud)
    public TableDataInfo<${ClassName}Vo> list(${ClassName}Bo bo, PageQuery pageQuery) {
        return ${className}Service.queryPageList(bo, pageQuery);
    }
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm
@@ -1,9 +1,8 @@
package ${packageName}.service;
import ${packageName}.domain.${ClassName};
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.bo.${ClassName}Bo;
#if($table.crud || $table.sub)
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
#end
@@ -21,33 +20,53 @@
    /**
     * æŸ¥è¯¢${functionName}
     *
     * @param ${pkColumn.javaField} ä¸»é”®
     * @return ${functionName}
     */
    ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
#if($table.crud || $table.sub)
#if($table.crud)
    /**
     * æŸ¥è¯¢${functionName}列表
     * åˆ†é¡µæŸ¥è¯¢${functionName}列表
     *
     * @param bo        æŸ¥è¯¢æ¡ä»¶
     * @param pageQuery åˆ†é¡µå‚æ•°
     * @return ${functionName}分页列表
     */
    TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery);
#end
    /**
     * æŸ¥è¯¢${functionName}列表
     * æŸ¥è¯¢ç¬¦åˆæ¡ä»¶çš„${functionName}列表
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶
     * @return ${functionName}列表
     */
    List<${ClassName}Vo> queryList(${ClassName}Bo bo);
    /**
     * æ–°å¢ž${functionName}
     *
     * @param bo ${functionName}
     * @return æ˜¯å¦æ–°å¢žæˆåŠŸ
     */
    Boolean insertByBo(${ClassName}Bo bo);
    /**
     * ä¿®æ”¹${functionName}
     *
     * @param bo ${functionName}
     * @return æ˜¯å¦ä¿®æ”¹æˆåŠŸ
     */
    Boolean updateByBo(${ClassName}Bo bo);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤${functionName}信息
     *
     * @param ids     å¾…删除的主键集合
     * @param isValid æ˜¯å¦è¿›è¡Œæœ‰æ•ˆæ€§æ ¡éªŒ
     * @return æ˜¯å¦åˆ é™¤æˆåŠŸ
     */
    Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
}
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
@@ -2,7 +2,7 @@
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
#if($table.crud || $table.sub)
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -35,15 +35,22 @@
    /**
     * æŸ¥è¯¢${functionName}
     *
     * @param ${pkColumn.javaField} ä¸»é”®
     * @return ${functionName}
     */
    @Override
    public ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField}){
        return baseMapper.selectVoById(${pkColumn.javaField});
    }
#if($table.crud || $table.sub)
#if($table.crud)
    /**
     * æŸ¥è¯¢${functionName}列表
     * åˆ†é¡µæŸ¥è¯¢${functionName}列表
     *
     * @param bo        æŸ¥è¯¢æ¡ä»¶
     * @param pageQuery åˆ†é¡µå‚æ•°
     * @return ${functionName}分页列表
     */
    @Override
    public TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery) {
@@ -54,7 +61,10 @@
#end
    /**
     * æŸ¥è¯¢${functionName}列表
     * æŸ¥è¯¢ç¬¦åˆæ¡ä»¶çš„${functionName}列表
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶
     * @return ${functionName}列表
     */
    @Override
    public List<${ClassName}Vo> queryList(${ClassName}Bo bo) {
@@ -91,6 +101,9 @@
    /**
     * æ–°å¢ž${functionName}
     *
     * @param bo ${functionName}
     * @return æ˜¯å¦æ–°å¢žæˆåŠŸ
     */
    @Override
    public Boolean insertByBo(${ClassName}Bo bo) {
@@ -106,6 +119,9 @@
    /**
     * ä¿®æ”¹${functionName}
     *
     * @param bo ${functionName}
     * @return æ˜¯å¦ä¿®æ”¹æˆåŠŸ
     */
    @Override
    public Boolean updateByBo(${ClassName}Bo bo) {
@@ -122,7 +138,11 @@
    }
    /**
     * æ‰¹é‡åˆ é™¤${functionName}
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤${functionName}信息
     *
     * @param ids     å¾…删除的主键集合
     * @param isValid æ˜¯å¦è¿›è¡Œæœ‰æ•ˆæ€§æ ¡éªŒ
     * @return æ˜¯å¦åˆ é™¤æˆåŠŸ
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid) {
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -1,8 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
@@ -14,38 +15,33 @@
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input" || $column.htmlType == "textarea")
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
            </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
              <el-option
                  v-for="dict in ${dictType}"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
              />
            </el-select>
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
              </el-select>
            </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
              <el-option label="请选择字典生成" value="" />
            </el-select>
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
                <el-option label="请选择字典生成" value="" />
              </el-select>
            </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-date-picker clearable
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-date-picker clearable
                v-model="queryParams.${column.javaField}"
                type="date"
                value-format="YYYY-MM-DD"
                placeholder="选择${comment}"
            />
          </el-form-item>
              />
            </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
          <el-form-item label="${comment}" style="width: 308px">
            <el-date-picker
            <el-form-item label="${comment}" style="width: 308px">
              <el-date-picker
                v-model="dateRange${AttrName}"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
@@ -53,16 +49,17 @@
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            />
          </el-form-item>
              />
            </el-form-item>
#end
#end
#end
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </div>
    </transition>
@@ -79,12 +76,12 @@
        </el-row>
      </template>
      <el-table
        ref="${businessName}TableRef"
        v-loading="loading"
        :data="${businessName}List"
        row-key="${treeCode}"
        :default-expand-all="isExpandAll"
        :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
        ref="${businessName}TableRef"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
#foreach($column in $columns)
#set($javaField=$column.javaField)
@@ -225,9 +222,9 @@
              v-for="dict in ${dictType}"
              :key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
              :label="parseInt(dict.value)"
              :value="parseInt(dict.value)"
#else
              :label="dict.value"
              :value="dict.value"
#end
            >{{dict.label}}</el-radio>
          </el-radio-group>
@@ -235,7 +232,7 @@
#elseif($column.htmlType == "radio" && $dictType)
        <el-form-item label="${comment}" prop="${field}">
          <el-radio-group v-model="form.${field}">
            <el-radio label="1">请选择字典生成</el-radio>
            <el-radio value="1">请选择字典生成</el-radio>
          </el-radio-group>
        </el-form-item>
#elseif($column.htmlType == "datetime")
@@ -424,9 +421,9 @@
  reset();
  getTreeselect();
  if (row != null && row.${treeCode}) {
      form.value.${treeParentCode} = row.${treeCode};
    form.value.${treeParentCode} = row.${treeCode};
  } else {
      form.value.${treeParentCode} = 0;
    form.value.${treeParentCode} = 0;
  }
  dialog.visible = true;
  dialog.title = "添加${functionName}";
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -1,8 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
@@ -14,38 +15,33 @@
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input" || $column.htmlType == "textarea")
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
            </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
              <el-option
                v-for="dict in ${dictType}"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
                <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
              </el-select>
            </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
              <el-option label="请选择字典生成" value="" />
            </el-select>
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
                <el-option label="请选择字典生成" value="" />
              </el-select>
            </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
          <el-form-item label="${comment}" prop="${column.javaField}">
            <el-date-picker clearable
              v-model="queryParams.${column.javaField}"
              type="date"
              value-format="YYYY-MM-DD"
              placeholder="请选择${comment}"
            />
          </el-form-item>
            <el-form-item label="${comment}" prop="${column.javaField}">
              <el-date-picker clearable
                v-model="queryParams.${column.javaField}"
                type="date"
                value-format="YYYY-MM-DD"
                placeholder="请选择${comment}"
              />
            </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
          <el-form-item label="${comment}" style="width: 308px">
            <el-date-picker
            <el-form-item label="${comment}" style="width: 308px">
              <el-date-picker
                v-model="dateRange${AttrName}"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
@@ -53,16 +49,17 @@
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            />
          </el-form-item>
              />
            </el-form-item>
#end
#end
#end
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </div>
    </transition>
@@ -135,13 +132,7 @@
        </el-table-column>
      </el-table>
      <pagination
          v-show="total>0"
          :total="total"
          v-model:page="queryParams.pageNum"
          v-model:limit="queryParams.pageSize"
          @pagination="getList"
      />
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹${functionName}对话框 -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
@@ -214,12 +205,12 @@
        <el-form-item label="${comment}" prop="${field}">
          <el-radio-group v-model="form.${field}">
            <el-radio
                v-for="dict in ${dictType}"
                :key="dict.value"
              v-for="dict in ${dictType}"
              :key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
                :label="parseInt(dict.value)"
              :value="parseInt(dict.value)"
#else
                :label="dict.value"
              :value="dict.value"
#end
            >{{dict.label}}</el-radio>
          </el-radio-group>
@@ -227,7 +218,7 @@
#elseif($column.htmlType == "radio" && $dictType)
        <el-form-item label="${comment}" prop="${field}">
          <el-radio-group v-model="form.${field}">
                <el-radio label="1">请选择字典生成</el-radio>
            <el-radio value="1">请选择字典生成</el-radio>
          </el-radio-group>
        </el-form-item>
#elseif($column.htmlType == "datetime")
@@ -439,7 +430,7 @@
      } else {
        await add${BusinessName}(form.value).finally(() =>  buttonLoading.value = false);
      }
      proxy?.#[[$modal]]#.msgSuccess("修改成功");
      proxy?.#[[$modal]]#.msgSuccess("操作成功");
      dialog.visible = false;
      await getList();
    }
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java
@@ -1,6 +1 @@
/**
 * æ¼”示用例
 * copy from https://github.com/PowerJob/PowerJob/tree/master/powerjob-worker-samples/src/main/java/tech/powerjob/samples
 */
package org.dromara.job;
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/BroadcastProcessorDemo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/LogTestProcessor.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/MapProcessorDemo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/MapReduceProcessorDemo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/SimpleProcessor.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/StandaloneProcessorDemo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/processors/TimeoutProcessor.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package org.dromara.job.snailjob;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;
/**
 * @author opensnail
 * @date 2024-05-17
 */
@Component
@JobExecutor(name = "testJobExecutor")
public class TestAnnoJobExecutor {
    public ExecuteResult jobExecute(JobArgs jobArgs) {
        SnailJobLog.LOCAL.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
        SnailJobLog.REMOTE.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
        return ExecuteResult.success("测试成功");
    }
}
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.dromara.job.snailjob;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor;
import com.aizuda.snailjob.client.model.ExecuteResult;
import org.springframework.stereotype.Component;
/**
 * @author opensnail
 * @date 2024-05-17
 */
@Component
public class TestClassJobExecutor extends AbstractJobExecutor {
    @Override
    protected ExecuteResult doJobExecute(JobArgs jobArgs) {
        return ExecuteResult.success("TestJobExecutor测试成功");
    }
}
ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/workflow/WorkflowStandaloneProcessor.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
 * åœ¨çº¿ç”¨æˆ·ç›‘控
@@ -87,4 +88,43 @@
        }
        return R.ok();
    }
    /**
     * èŽ·å–å½“å‰ç”¨æˆ·ç™»å½•åœ¨çº¿è®¾å¤‡
     */
    @GetMapping()
    public TableDataInfo<SysUserOnline> getInfo() {
        // èŽ·å–æŒ‡å®šè´¦å· id çš„ token é›†åˆ
        List<String> tokenIds = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
        List<UserOnlineDTO> userOnlineDTOList = tokenIds.stream()
            .filter(token -> StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) >= -1)
            .map(token -> (UserOnlineDTO) RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token))
            .collect(Collectors.toList());
        //复制和处理 SysUserOnline å¯¹è±¡åˆ—表
        Collections.reverse(userOnlineDTOList);
        userOnlineDTOList.removeAll(Collections.singleton(null));
        List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
        return TableDataInfo.build(userOnlineList);
    }
    /**
     * å¼ºé€€å½“前在线设备
     *
     * @param tokenId token值
     */
    @Log(title = "在线设备", businessType = BusinessType.FORCE)
    @PostMapping("/{tokenId}")
    public R<Void> remove(@PathVariable("tokenId") String tokenId) {
        try {
            // èŽ·å–æŒ‡å®šè´¦å· id çš„ token é›†åˆ
            List<String> keys = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
            keys.stream()
                .filter(key -> key.equals(tokenId))
                .findFirst()
                .ifPresent(key -> StpUtil.kickoutByTokenValue(tokenId));
        } catch (NotLoginException ignored) {
        }
        return R.ok();
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java
@@ -97,7 +97,7 @@
    @Log(title = "客户端管理", businessType = BusinessType.UPDATE)
    @PutMapping("/changeStatus")
    public R<Void> changeStatus(@RequestBody SysClientBo bo) {
        return toAjax(sysClientService.updateUserStatus(bo.getId(), bo.getStatus()));
        return toAjax(sysClientService.updateUserStatus(bo.getClientId(), bo.getStatus()));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
@@ -2,6 +2,7 @@
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
@@ -11,7 +12,6 @@
import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.service.ISysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -75,6 +75,8 @@
    public R<Void> add(@Validated @RequestBody SysDeptBo dept) {
        if (!deptService.checkDeptNameUnique(dept)) {
            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
        } else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
        }
        return toAjax(deptService.insertDept(dept));
    }
@@ -90,6 +92,8 @@
        deptService.checkDeptDataScope(deptId);
        if (!deptService.checkDeptNameUnique(dept)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
        } else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
        } else if (dept.getParentId().equals(deptId)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
        } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())) {
@@ -120,4 +124,16 @@
        deptService.checkDeptDataScope(deptId);
        return toAjax(deptService.deleteDeptById(deptId));
    }
    /**
     * èŽ·å–éƒ¨é—¨é€‰æ‹©æ¡†åˆ—è¡¨
     *
     * @param deptIds éƒ¨é—¨ID串
     */
    @SaCheckPermission("system:dept:query")
    @GetMapping("/optionselect")
    public R<List<SysDeptVo>> optionselect(@RequestParam(required = false) Long[] deptIds) {
        return R.ok(deptService.selectDeptByIds(deptIds == null ? null : List.of(deptIds)));
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java
@@ -87,6 +87,9 @@
    @Log(title = "字典数据", businessType = BusinessType.INSERT)
    @PostMapping
    public R<Void> add(@Validated @RequestBody SysDictDataBo dict) {
        if (!dictDataService.checkDictDataUnique(dict)) {
            return R.fail("新增字典数据'" + dict.getDictValue() + "'失败,字典键值已存在");
        }
        dictDataService.insertDictData(dict);
        return R.ok();
    }
@@ -98,6 +101,9 @@
    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
    @PutMapping
    public R<Void> edit(@Validated @RequestBody SysDictDataBo dict) {
        if (!dictDataService.checkDictDataUnique(dict)) {
            return R.fail("修改字典数据'" + dict.getDictValue() + "'失败,字典键值已存在");
        }
        dictDataService.updateDictData(dict);
        return R.ok();
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java
@@ -1,6 +1,9 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
@@ -12,11 +15,10 @@
import org.dromara.system.domain.bo.SysPostBo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.service.ISysPostService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
@@ -110,12 +112,22 @@
    /**
     * èŽ·å–å²—ä½é€‰æ‹©æ¡†åˆ—è¡¨
     *
     * @param postIds å²—位ID串
     * @param deptId  éƒ¨é—¨id
     */
    @SaCheckPermission("system:post:query")
    @GetMapping("/optionselect")
    public R<List<SysPostVo>> optionselect() {
        SysPostBo postBo = new SysPostBo();
        postBo.setStatus(UserConstants.POST_NORMAL);
        List<SysPostVo> posts = postService.selectPostList(postBo);
        return R.ok(posts);
    public R<List<SysPostVo>> optionselect(@RequestParam(required = false) Long[] postIds, @RequestParam(required = false) Long deptId) {
        List<SysPostVo> list = new ArrayList<>();
        if (ObjectUtil.isNotNull(deptId)) {
            SysPostBo post = new SysPostBo();
            post.setDeptId(deptId);
            list = postService.selectPostList(post);
        } else if (postIds != null) {
            list = postService.selectPostByIds(List.of(postIds));
        }
        return R.ok(list);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
@@ -19,6 +19,7 @@
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysRoleService;
import org.dromara.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
@@ -50,8 +51,8 @@
        SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
        ProfileVo profileVo = new ProfileVo();
        profileVo.setUser(user);
        profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserName()));
        profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserName()));
        profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserId()));
        profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserId()));
        return R.ok(profileVo);
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java
@@ -149,11 +149,13 @@
    /**
     * èŽ·å–è§’è‰²é€‰æ‹©æ¡†åˆ—è¡¨
     *
     * @param roleIds è§’色ID串
     */
    @SaCheckPermission("system:role:query")
    @GetMapping("/optionselect")
    public R<List<SysRoleVo>> optionselect() {
        return R.ok(roleService.selectRoleAll());
    public R<List<SysRoleVo>> optionselect(@RequestParam(required = false) Long[] roleIds) {
        return R.ok(roleService.selectRoleByIds(roleIds == null ? null : List.of(roleIds)));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
@@ -11,7 +11,6 @@
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
@@ -72,9 +71,8 @@
    @SaCheckPermission("system:user:export")
    @PostMapping("/export")
    public void export(SysUserBo user, HttpServletResponse response) {
        List<SysUserVo> list = userService.selectUserList(user);
        List<SysUserExportVo> listVo = MapstructUtils.convert(list, SysUserExportVo.class);
        ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response);
        List<SysUserExportVo> list = userService.selectUserExportList(user);
        ExcelUtil.exportExcel(list, "用户数据", SysUserExportVo.class, response);
    }
    /**
@@ -134,16 +132,19 @@
        SysUserInfoVo userInfoVo = new SysUserInfoVo();
        SysRoleBo roleBo = new SysRoleBo();
        roleBo.setStatus(UserConstants.ROLE_NORMAL);
        SysPostBo postBo = new SysPostBo();
        postBo.setStatus(UserConstants.POST_NORMAL);
        List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
        userInfoVo.setPosts(postService.selectPostList(postBo));
        if (ObjectUtil.isNotNull(userId)) {
            SysUserVo sysUser = userService.selectUserById(userId);
            userInfoVo.setUser(sysUser);
            userInfoVo.setRoleIds(StreamUtils.toList(sysUser.getRoles(), SysRoleVo::getRoleId));
            userInfoVo.setPostIds(postService.selectPostListByUserId(userId));
            userInfoVo.setRoleIds(roleService.selectRoleListByUserId(userId));
            Long deptId = sysUser.getDeptId();
            if (ObjectUtil.isNotNull(deptId)) {
                SysPostBo postBo = new SysPostBo();
                postBo.setDeptId(deptId);
                userInfoVo.setPosts(postService.selectPostList(postBo));
                userInfoVo.setPostIds(postService.selectPostListByUserId(userId));
            }
        }
        return R.ok(userInfoVo);
    }
@@ -208,6 +209,19 @@
    }
    /**
     * æ ¹æ®ç”¨æˆ·ID串批量获取用户基础信息
     *
     * @param userIds ç”¨æˆ·ID串
     * @param deptId  éƒ¨é—¨ID
     */
    @SaCheckPermission("system:user:query")
    @GetMapping("/optionselect")
    public R<List<SysUserVo>> optionselect(@RequestParam(required = false) Long[] userIds,
                                           @RequestParam(required = false) Long deptId) {
        return R.ok(userService.selectUserByIds(userIds == null ? null : List.of(userIds), deptId));
    }
    /**
     * é‡ç½®å¯†ç 
     */
    @ApiEncrypt
@@ -241,8 +255,9 @@
    @SaCheckPermission("system:user:query")
    @GetMapping("/authRole/{userId}")
    public R<SysUserInfoVo> authRole(@PathVariable Long userId) {
        userService.checkUserDataScope(userId);
        SysUserVo user = userService.selectUserById(userId);
        List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
        List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
        SysUserInfoVo userInfoVo = new SysUserInfoVo();
        userInfoVo.setUser(user);
        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
@@ -281,4 +296,5 @@
    public R<List<SysUserVo>> listByDept(@PathVariable @NotNull Long deptId) {
        return R.ok(userService.selectUserListByDept(deptId));
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java
@@ -3,9 +3,9 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
@@ -40,6 +40,11 @@
    private String deptName;
    /**
     * éƒ¨é—¨ç±»åˆ«ç¼–码
     */
    private String deptCategory;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    private Integer orderNum;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java
@@ -17,7 +17,7 @@
public class SysOssConfig extends BaseEntity {
    /**
     * ä¸»å»º
     * ä¸»é”®
     */
    @TableId(value = "oss_config_id")
    private Long ossConfigId;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java
@@ -24,6 +24,11 @@
    private Long postId;
    /**
     * éƒ¨é—¨id
     */
    private Long deptId;
    /**
     * å²—位编码
     */
    private String postCode;
@@ -34,6 +39,11 @@
    private String postName;
    /**
     * å²—位类别编码
     */
    private String postCategory;
    /**
     * å²—位排序
     */
    private Integer postSort;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java
@@ -7,7 +7,6 @@
 *
 * @author Lion Li
 */
@Data
public class SysUserOnline {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java
@@ -39,6 +39,12 @@
    private String deptName;
    /**
     * éƒ¨é—¨ç±»åˆ«ç¼–码
     */
    @Size(min = 0, max = 100, message = "部门类别编码长度不能超过{max}个字符")
    private String deptCategory;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    @NotNull(message = "显示顺序不能为空")
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java
@@ -6,6 +6,7 @@
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.constant.RegexConstants;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysDictType;
@@ -37,7 +38,7 @@
     */
    @NotBlank(message = "字典类型不能为空")
    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过{max}个字符")
    @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
    @Pattern(regexp = RegexConstants.DICTIONARY_TYPE, message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
    private String dictType;
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java
@@ -4,9 +4,11 @@
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.constant.RegexConstants;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysMenu;
@@ -92,6 +94,7 @@
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Size(min = 0, max = 100, message = "权限标识长度不能超过{max}个字符")
    @Pattern(regexp = RegexConstants.PERMISSION_STRING, message = "权限标识必须符合 tool:build:list æ ¼å¼")
    private String perms;
    /**
@@ -103,6 +106,5 @@
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java
@@ -25,9 +25,9 @@
public class SysOssConfigBo extends BaseEntity {
    /**
     * ä¸»å»º
     * ä¸»é”®
     */
    @NotNull(message = "主建不能为空", groups = {EditGroup.class})
    @NotNull(message = "主键不能为空", groups = {EditGroup.class})
    private Long ossConfigId;
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java
@@ -26,6 +26,17 @@
    private Long postId;
    /**
     * éƒ¨é—¨id(单部门)
     */
    @NotNull(message = "部门id不能为空")
    private Long deptId;
    /**
     * å½’属部门id(部门树)
     */
    private Long belongDeptId;
    /**
     * å²—位编码
     */
    @NotBlank(message = "岗位编码不能为空")
@@ -38,6 +49,12 @@
    @NotBlank(message = "岗位名称不能为空")
    @Size(min = 0, max = 50, message = "岗位名称长度不能超过{max}个字符")
    private String postName;
    /**
     * å²—位类别编码
     */
    @Size(min = 0, max = 100, message = "类别编码长度不能超过{max}个字符")
    private String postCategory;
    /**
     * æ˜¾ç¤ºé¡ºåº
@@ -54,6 +71,5 @@
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java
@@ -29,9 +29,9 @@
    private Long id;
    /**
     * çš„唯一ID
     * è®¤è¯å”¯ä¸€ID
     */
    @NotBlank(message = "的唯一ID不能为空", groups = { AddGroup.class, EditGroup.class })
    @NotBlank(message = "认证唯一ID不能为空", groups = { AddGroup.class, EditGroup.class })
    private String authId;
    /**
@@ -64,7 +64,7 @@
    /**
     * ç”¨æˆ·çš„ ID
     */
    @NotBlank(message = "用户的 ID不能为空", groups = { AddGroup.class, EditGroup.class })
    @NotBlank(message = "用户的ID不能为空", groups = { AddGroup.class, EditGroup.class })
    private Long userId;
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
@@ -103,6 +103,11 @@
     */
    private Long roleId;
    /**
     * æŽ’除不查询的用户(工作流用)
     */
    private String excludeUserIds;
    public SysUserBo(Long userId) {
        this.userId = userId;
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java
@@ -53,6 +53,12 @@
    private String deptName;
    /**
     * éƒ¨é—¨ç±»åˆ«ç¼–码
     */
    @ExcelProperty(value = "部门类别编码")
    private String deptCategory;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    private Integer orderNum;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java
@@ -25,7 +25,7 @@
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»å»º
     * ä¸»é”®
     */
    private Long ossConfigId;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java
@@ -2,17 +2,17 @@
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.system.domain.SysPost;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.system.domain.SysPost;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * å²—位信息视图对象 sys_post
@@ -34,6 +34,12 @@
    private Long postId;
    /**
     * éƒ¨é—¨id
     */
    @ExcelProperty(value = "部门id")
    private Long deptId;
    /**
     * å²—位编码
     */
    @ExcelProperty(value = "岗位编码")
@@ -44,6 +50,12 @@
     */
    @ExcelProperty(value = "岗位名称")
    private String postName;
    /**
     * å²—位类别编码
     */
    @ExcelProperty(value = "类别编码")
    private String postCategory;
    /**
     * æ˜¾ç¤ºé¡ºåº
@@ -70,4 +82,10 @@
    @ExcelProperty(value = "创建时间")
    private Date createTime;
    /**
     * éƒ¨é—¨å
     */
    @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
    private String deptName;
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java
@@ -20,7 +20,6 @@
@Data
@NoArgsConstructor
@AutoMapper(target = SysUserVo.class, convertGenerate = false)
public class SysUserExportVo implements Serializable {
    @Serial
@@ -85,14 +84,12 @@
    /**
     * éƒ¨é—¨åç§°
     */
    @ReverseAutoMapping(target = "deptName", source = "dept.deptName")
    @ExcelProperty(value = "部门名称")
    private String deptName;
    /**
     * è´Ÿè´£äºº
     */
    @ReverseAutoMapping(target = "leaderName", source = "dept.leaderName")
    @ExcelProperty(value = "部门负责人")
    private String leaderName;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
@@ -114,9 +114,10 @@
    private Date createTime;
    /**
     * éƒ¨é—¨å¯¹è±¡
     * éƒ¨é—¨å
     */
    private SysDeptVo dept;
    @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
    private String deptName;
    /**
     * è§’色对象
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java
@@ -32,7 +32,7 @@
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id")
    })
    SysDeptVo selectDeptById(Long deptId);
    long countDeptById(Long deptId);
    /**
     * æ ¹æ®è§’色ID查询部门树信息
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java
@@ -1,5 +1,11 @@
package org.dromara.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.system.domain.SysPost;
import org.dromara.system.domain.vo.SysPostVo;
@@ -13,20 +19,18 @@
 */
public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
    /**
     * æ ¹æ®ç”¨æˆ·ID获取岗位选择框列表
     *
     * @param userId ç”¨æˆ·ID
     * @return é€‰ä¸­å²—位ID列表
     */
    List<Long> selectPostListByUserId(Long userId);
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "create_by")
    })
    Page<SysPostVo> selectPagePostList(@Param("page") Page<SysPostVo> page, @Param(Constants.WRAPPER) Wrapper<SysPost> queryWrapper);
    /**
     * æŸ¥è¯¢ç”¨æˆ·æ‰€å±žå²—位组
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return ç»“æžœ
     */
    List<SysPostVo> selectPostsByUserName(String userName);
    List<SysPostVo> selectPostsByUserId(Long userId);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java
@@ -3,12 +3,12 @@
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.system.domain.SysRole;
import org.dromara.system.domain.vo.SysRoleVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -51,21 +51,12 @@
     */
    List<SysRoleVo> selectRolePermissionByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID获取角色选择框列表
     *
     * @param userId ç”¨æˆ·ID
     * @return é€‰ä¸­è§’色ID列表
     */
    List<Long> selectRoleListByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return è§’色列表
     */
    List<SysRoleVo> selectRolesByUserName(String userName);
    List<SysRoleVo> selectRolesByUserId(Long userId);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java
@@ -8,6 +8,7 @@
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import java.util.List;
@@ -20,10 +21,16 @@
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
    @DataPermission({
        @DataColumn(key = "deptName", value = "d.dept_id"),
        @DataColumn(key = "deptName", value = "u.dept_id"),
        @DataColumn(key = "userName", value = "u.user_id")
    })
    Page<SysUserVo> selectPageUserList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢ç”¨æˆ·åˆ—表
@@ -35,7 +42,7 @@
        @DataColumn(key = "deptName", value = "d.dept_id"),
        @DataColumn(key = "userName", value = "u.user_id")
    })
    List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    List<SysUserExportVo> selectUserExportList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢å·²é…ç”¨æˆ·è§’色列表
@@ -61,41 +68,11 @@
    })
    Page<SysUserVo> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    /**
     * é€šè¿‡ç”¨æˆ·åæŸ¥è¯¢ç”¨æˆ·
     *
     * @param userName ç”¨æˆ·å
     * @return ç”¨æˆ·å¯¹è±¡ä¿¡æ¯
     */
    SysUserVo selectUserByUserName(String userName);
    /**
     * é€šè¿‡æ‰‹æœºå·æŸ¥è¯¢ç”¨æˆ·
     *
     * @param phonenumber æ‰‹æœºå·
     * @return ç”¨æˆ·å¯¹è±¡ä¿¡æ¯
     */
    SysUserVo selectUserByPhonenumber(String phonenumber);
    /**
     * é€šè¿‡é‚®ç®±æŸ¥è¯¢ç”¨æˆ·
     *
     * @param email é‚®ç®±
     * @return ç”¨æˆ·å¯¹è±¡ä¿¡æ¯
     */
    SysUserVo selectUserByEmail(String email);
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户
     *
     * @param userId ç”¨æˆ·ID
     * @return ç”¨æˆ·å¯¹è±¡ä¿¡æ¯
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "d.dept_id"),
        @DataColumn(key = "userName", value = "u.user_id")
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    SysUserVo selectUserById(Long userId);
    long countUserById(Long userId);
    @Override
    @DataPermission({
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
@@ -25,7 +25,7 @@
    /**
     * æŸ¥è¯¢å®¢æˆ·ç«¯ä¿¡æ¯åŸºäºŽå®¢æˆ·ç«¯id
     */
    SysClient queryByClientId(String clientId);
    SysClientVo queryByClientId(String clientId);
    /**
     * æŸ¥è¯¢å®¢æˆ·ç«¯ç®¡ç†åˆ—表
@@ -50,7 +50,7 @@
    /**
     * ä¿®æ”¹çŠ¶æ€
     */
    int updateUserStatus(Long id, String status);
    int updateUserStatus(String clientId, String status);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤å®¢æˆ·ç«¯ç®¡ç†ä¿¡æ¯
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java
@@ -53,6 +53,14 @@
    SysDeptVo selectDeptById(Long deptId);
    /**
     * é€šè¿‡éƒ¨é—¨ID串查询部门
     *
     * @param deptIds éƒ¨é—¨id串
     * @return éƒ¨é—¨åˆ—表信息
     */
    List<SysDeptVo> selectDeptByIds(List<Long> deptIds);
    /**
     * æ ¹æ®ID查询所有子部门数(正常状态)
     *
     * @param deptId éƒ¨é—¨ID
@@ -85,6 +93,14 @@
    boolean checkDeptNameUnique(SysDeptBo dept);
    /**
     * æ ¡éªŒéƒ¨é—¨ç±»åˆ«ç¼–码是否唯一
     *
     * @param dept éƒ¨é—¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    boolean checkDeptCategoryUnique(SysDeptBo dept);
    /**
     * æ ¡éªŒéƒ¨é—¨æ˜¯å¦æœ‰æ•°æ®æƒé™
     *
     * @param deptId éƒ¨é—¨id
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java
@@ -64,4 +64,13 @@
     * @return ç»“æžœ
     */
    List<SysDictDataVo> updateDictData(SysDictDataBo bo);
    /**
     * æ ¡éªŒå­—典键值是否唯一
     *
     * @param dict å­—典数据
     * @return ç»“æžœ
     */
    boolean checkDictDataUnique(SysDictDataBo dict);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java
@@ -31,12 +31,11 @@
     */
    TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery);
    /**
     * æ ¹æ®æ–°å¢žä¸šåŠ¡å¯¹è±¡æ’å…¥å¯¹è±¡å­˜å‚¨é…ç½®
     *
     * @param bo å¯¹è±¡å­˜å‚¨é…ç½®æ–°å¢žä¸šåŠ¡å¯¹è±¡
     * @return
     * @return ç»“æžœ
     */
    Boolean insertByBo(SysOssConfigBo bo);
@@ -44,7 +43,7 @@
     * æ ¹æ®ç¼–辑业务对象修改对象存储配置
     *
     * @param bo å¯¹è±¡å­˜å‚¨é…ç½®ç¼–辑业务对象
     * @return
     * @return ç»“æžœ
     */
    Boolean updateByBo(SysOssConfigBo bo);
@@ -53,7 +52,7 @@
     *
     * @param ids     ä¸»é”®é›†åˆ
     * @param isValid æ˜¯å¦æ ¡éªŒ,true-删除前校验,false-不校验
     * @return
     * @return ç»“æžœ
     */
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java
@@ -19,18 +19,62 @@
 */
public interface ISysOssService {
    /**
     * æŸ¥è¯¢OSS对象存储列表
     *
     * @param sysOss    OSS对象存储分页查询对象
     * @param pageQuery åˆ†é¡µæŸ¥è¯¢å®žä½“ç±»
     * @return ç»“æžœ
     */
    TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
    /**
     * æ ¹æ®ä¸€ç»„ ossIds èŽ·å–å¯¹åº”çš„ SysOssVo åˆ—表
     *
     * @param ossIds ä¸€ç»„文件在数据库中的唯一标识集合
     * @return åŒ…含 SysOssVo å¯¹è±¡çš„列表
     */
    List<SysOssVo> listByIds(Collection<Long> ossIds);
    /**
     * æ ¹æ® ossId ä»Žç¼“存或数据库中获取 SysOssVo å¯¹è±¡
     *
     * @param ossId æ–‡ä»¶åœ¨æ•°æ®åº“中的唯一标识
     * @return SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     */
    SysOssVo getById(Long ossId);
    /**
     * ä¸Šä¼  MultipartFile åˆ°å¯¹è±¡å­˜å‚¨æœåŠ¡ï¼Œå¹¶ä¿å­˜æ–‡ä»¶ä¿¡æ¯åˆ°æ•°æ®åº“
     *
     * @param file è¦ä¸Šä¼ çš„ MultipartFile å¯¹è±¡
     * @return ä¸Šä¼ æˆåŠŸåŽçš„ SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     */
    SysOssVo upload(MultipartFile file);
    /**
     * ä¸Šä¼ æ–‡ä»¶åˆ°å¯¹è±¡å­˜å‚¨æœåŠ¡ï¼Œå¹¶ä¿å­˜æ–‡ä»¶ä¿¡æ¯åˆ°æ•°æ®åº“
     *
     * @param file è¦ä¸Šä¼ çš„æ–‡ä»¶å¯¹è±¡
     * @return ä¸Šä¼ æˆåŠŸåŽçš„ SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     */
    SysOssVo upload(File file);
    /**
     * æ–‡ä»¶ä¸‹è½½æ–¹æ³•,支持一次性下载完整文件
     *
     * @param ossId    OSS对象ID
     * @param response HttpServletResponse对象,用于设置响应头和向客户端发送文件内容
     */
    void download(Long ossId, HttpServletResponse response) throws IOException;
    /**
     * åˆ é™¤OSS对象存储
     *
     * @param ids     OSS对象ID串
     * @param isValid åˆ¤æ–­æ˜¯å¦éœ€è¦æ ¡éªŒ
     * @return ç»“æžœ
     */
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java
@@ -49,6 +49,14 @@
    List<Long> selectPostListByUserId(Long userId);
    /**
     * é€šè¿‡å²—位ID串查询岗位
     *
     * @param postIds å²—位id串
     * @return å²—位列表信息
     */
    List<SysPostVo> selectPostByIds(List<Long> postIds);
    /**
     * æ ¡éªŒå²—位名称
     *
     * @param post å²—位信息
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java
@@ -36,6 +36,14 @@
    List<SysRoleVo> selectRolesByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色列表(包含被授权状态)
     *
     * @param userId ç”¨æˆ·ID
     * @return è§’色列表
     */
    List<SysRoleVo> selectRolesAuthByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色权限
     *
     * @param userId ç”¨æˆ·ID
@@ -65,6 +73,14 @@
     * @return è§’色对象信息
     */
    SysRoleVo selectRoleById(Long roleId);
    /**
     * é€šè¿‡è§’色ID串查询角色
     *
     * @param roleIds è§’色ID串
     * @return è§’色列表信息
     */
    List<SysRoleVo> selectRoleByIds(List<Long> roleIds);
    /**
     * æ ¡éªŒè§’色名称是否唯一
@@ -180,4 +196,5 @@
    int insertAuthUsers(Long roleId, Long[] userIds);
    void cleanOnlineUserByRole(Long roleId);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
@@ -21,7 +21,7 @@
    /**
     * æŸ¥è¯¢ç¤¾ä¼šåŒ–关系列表
     */
    List<SysSocialVo> queryList();
    List<SysSocialVo> queryList(SysSocialBo bo);
    /**
     * æŸ¥è¯¢ç¤¾ä¼šåŒ–关系列表
@@ -45,9 +45,7 @@
    /**
     * æ ¹æ® authId æŸ¥è¯¢ SysSocial è¡¨å’Œ SysUser è¡¨ï¼Œè¿”回 SysSocialAuthResult æ˜ å°„的对象
     * @param authId è®¤è¯ID
     * @return SysSocial
     * æ ¹æ® authId æŸ¥è¯¢
     */
    List<SysSocialVo> selectByAuthId(String authId);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
@@ -3,6 +3,7 @@
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import java.util.List;
@@ -23,7 +24,7 @@
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    List<SysUserVo> selectUserList(SysUserBo user);
    List<SysUserExportVo> selectUserExportList(SysUserBo user);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢å·²åˆ†é…ç”¨æˆ·è§’色列表
@@ -66,20 +67,29 @@
    SysUserVo selectUserById(Long userId);
    /**
     * é€šè¿‡ç”¨æˆ·ID串查询用户
     *
     * @param userIds ç”¨æˆ·ID串
     * @param deptId  éƒ¨é—¨id
     * @return ç”¨æˆ·åˆ—表信息
     */
    List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询用户所属角色组
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return ç»“æžœ
     */
    String selectUserRoleGroup(String userName);
    String selectUserRoleGroup(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询用户所属岗位组
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return ç»“æžœ
     */
    String selectUserPostGroup(String userName);
    String selectUserPostGroup(Long userId);
    /**
     * æ ¡éªŒç”¨æˆ·åç§°æ˜¯å¦å”¯ä¸€
@@ -205,8 +215,8 @@
    /**
     * é€šè¿‡éƒ¨é—¨id查询当前部门所有用户
     *
     * @param deptId
     * @return
     * @param deptId éƒ¨é—¨id
     * @return ç»“æžœ
     */
    List<SysUserVo> selectUserListByDept(Long deptId);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -16,6 +17,8 @@
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.mapper.SysClientMapper;
import org.dromara.system.service.ISysClientService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Collection;
@@ -48,9 +51,10 @@
    /**
     * æŸ¥è¯¢å®¢æˆ·ç«¯ç®¡ç†
     */
    @Cacheable(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
    @Override
    public SysClient queryByClientId(String clientId) {
        return baseMapper.selectOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
    public SysClientVo queryByClientId(String clientId) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
    }
    /**
@@ -105,6 +109,7 @@
    /**
     * ä¿®æ”¹å®¢æˆ·ç«¯ç®¡ç†
     */
    @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, key = "#bo.clientId")
    @Override
    public Boolean updateByBo(SysClientBo bo) {
        SysClient update = MapstructUtils.convert(bo, SysClient.class);
@@ -116,12 +121,13 @@
    /**
     * ä¿®æ”¹çŠ¶æ€
     */
    @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
    @Override
    public int updateUserStatus(Long id, String status) {
    public int updateUserStatus(String clientId, String status) {
        return baseMapper.update(null,
            new LambdaUpdateWrapper<SysClient>()
                .set(SysClient::getStatus, status)
                .eq(SysClient::getId, id));
                .eq(SysClient::getClientId, clientId));
    }
    /**
@@ -134,6 +140,7 @@
    /**
     * æ‰¹é‡åˆ é™¤å®¢æˆ·ç«¯ç®¡ç†
     */
    @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, allEntries = true)
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
@@ -154,6 +154,7 @@
            }
            row = baseMapper.updateById(config);
        } else {
            CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
            row = baseMapper.update(config, new LambdaQueryWrapper<SysConfig>()
                .eq(SysConfig::getConfigKey, config.getConfigKey()));
        }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.exception.ServiceException;
@@ -27,7 +28,6 @@
import org.dromara.system.mapper.SysRoleMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@@ -82,7 +82,9 @@
        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());
        lqw.like(StringUtils.isNotBlank(bo.getDeptCategory()), SysDept::getDeptCategory, bo.getDeptCategory());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDept::getStatus, bo.getStatus());
        lqw.orderByAsc(SysDept::getAncestors);
        lqw.orderByAsc(SysDept::getParentId);
        lqw.orderByAsc(SysDept::getOrderNum);
        lqw.orderByAsc(SysDept::getDeptId);
@@ -136,6 +138,14 @@
            .select(SysDept::getDeptName).eq(SysDept::getDeptId, dept.getParentId()));
        dept.setParentName(ObjectUtil.isNotNull(parentDept) ? parentDept.getDeptName() : null);
        return dept;
    }
    @Override
    public List<SysDeptVo> selectDeptByIds(List<Long> deptIds) {
        return baseMapper.selectDeptList(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptId, SysDept::getDeptName, SysDept::getLeader)
            .eq(SysDept::getStatus, UserConstants.DEPT_NORMAL)
            .in(CollUtil.isNotEmpty(deptIds), SysDept::getDeptId, deptIds));
    }
    /**
@@ -209,6 +219,20 @@
    }
    /**
     * æ ¡éªŒéƒ¨é—¨ç±»åˆ«ç¼–码是否唯一
     *
     * @param dept éƒ¨é—¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public boolean checkDeptCategoryUnique(SysDeptBo dept) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDept>()
            .eq(SysDept::getDeptCategory, dept.getDeptCategory())
            .ne(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId()));
        return !exist;
    }
    /**
     * æ ¡éªŒéƒ¨é—¨æ˜¯å¦æœ‰æ•°æ®æƒé™
     *
     * @param deptId éƒ¨é—¨id
@@ -221,8 +245,7 @@
        if (LoginHelper.isSuperAdmin()) {
            return;
        }
        SysDeptVo dept = baseMapper.selectDeptById(deptId);
        if (ObjectUtil.isNull(dept)) {
        if (baseMapper.countDeptById(deptId) == 0) {
            throw new ServiceException("没有权限访问部门数据!");
        }
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java
@@ -1,5 +1,6 @@
package org.dromara.system.service.impl;
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;
@@ -135,4 +136,21 @@
        throw new ServiceException("操作失败");
    }
    /**
     * æ ¡éªŒå­—典键值是否唯一
     *
     * @param dict å­—典数据
     * @return ç»“æžœ
     */
    @Override
    public boolean checkDictDataUnique(SysDictDataBo dict) {
        Long dictCode = ObjectUtil.isNull(dict.getDictCode()) ? -1L : dict.getDictCode();
        SysDictData entity = baseMapper.selectOne(new LambdaQueryWrapper<SysDictData>()
            .eq(SysDictData::getDictType, dict.getDictType()).eq(SysDictData::getDictValue, dict.getDictValue()));
        if (ObjectUtil.isNotNull(entity) && !dictCode.equals(entity.getDictCode())) {
            return false;
        }
        return true;
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
@@ -1,13 +1,12 @@
package org.dromara.system.service.impl;
import cn.dev33.satoken.context.SaHolder;
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.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.core.constant.CacheConstants;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DictService;
@@ -26,7 +25,6 @@
import org.dromara.system.mapper.SysDictDataMapper;
import org.dromara.system.mapper.SysDictTypeMapper;
import org.dromara.system.service.ISysDictTypeService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@@ -217,16 +215,9 @@
     * @param separator åˆ†éš”符
     * @return å­—典标签
     */
    @SuppressWarnings("unchecked cast")
    @Override
    public String getDictLabel(String dictType, String dictValue, String separator) {
        // ä¼˜å…ˆä»Žæœ¬åœ°ç¼“存获取
        List<SysDictDataVo> datas = (List<SysDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
        if (ObjectUtil.isNull(datas)) {
            datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
            SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
        }
        List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
        Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
        if (StringUtils.containsAny(dictValue, separator)) {
            return Arrays.stream(dictValue.split(separator))
@@ -245,16 +236,9 @@
     * @param separator åˆ†éš”符
     * @return å­—典值
     */
    @SuppressWarnings("unchecked cast")
    @Override
    public String getDictValue(String dictType, String dictLabel, String separator) {
        // ä¼˜å…ˆä»Žæœ¬åœ°ç¼“存获取
        List<SysDictDataVo> datas = (List<SysDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
        if (ObjectUtil.isNull(datas)) {
            datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
            SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
        }
        List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
        Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
        if (StringUtils.containsAny(dictLabel, separator)) {
            return Arrays.stream(dictLabel.split(separator))
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java
@@ -5,30 +5,29 @@
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysLogininfor;
import org.dromara.system.domain.bo.SysLogininforBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysLogininforVo;
import org.dromara.system.mapper.SysClientMapper;
import org.dromara.system.mapper.SysLogininforMapper;
import org.dromara.system.service.ISysClientService;
import org.dromara.system.service.ISysLogininforService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -60,10 +59,10 @@
        final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
        final String ip = ServletUtils.getClientIP(request);
        // å®¢æˆ·ç«¯ä¿¡æ¯
        String clientid = request.getHeader(LoginHelper.CLIENT_KEY);
        SysClient client = null;
        if (StringUtils.isNotBlank(clientid)) {
            client = clientService.queryByClientId(clientid);
        String clientId = request.getHeader(LoginHelper.CLIENT_KEY);
        SysClientVo client = null;
        if (StringUtils.isNotBlank(clientId)) {
            client = clientService.queryByClientId(clientId);
        }
        String address = AddressUtils.getRealAddressByIP(ip);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java
@@ -9,6 +9,7 @@
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysNotice;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysNoticeBo;
import org.dromara.system.domain.vo.SysNoticeVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -68,7 +69,7 @@
        lqw.like(StringUtils.isNotBlank(bo.getNoticeTitle()), SysNotice::getNoticeTitle, bo.getNoticeTitle());
        lqw.eq(StringUtils.isNotBlank(bo.getNoticeType()), SysNotice::getNoticeType, bo.getNoticeType());
        if (StringUtils.isNotBlank(bo.getCreateByName())) {
            SysUserVo sysUser = userMapper.selectUserByUserName(bo.getCreateByName());
            SysUserVo sysUser = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, bo.getCreateByName()));
            lqw.eq(SysNotice::getCreateBy, ObjectUtil.isNotNull(sysUser) ? sysUser.getUserId() : null);
        }
        lqw.orderByAsc(SysNotice::getNoticeId);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -1,12 +1,15 @@
package org.dromara.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
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;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.domain.dto.OssDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.OssService;
import org.dromara.common.core.utils.MapstructUtils;
@@ -25,8 +28,6 @@
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.mapper.SysOssMapper;
import org.dromara.system.service.ISysOssService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
@@ -35,8 +36,10 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * æ–‡ä»¶ä¸Šä¼  æœåŠ¡å±‚å®žçŽ°
@@ -49,6 +52,13 @@
    private final SysOssMapper baseMapper;
    /**
     * æŸ¥è¯¢OSS对象存储列表
     *
     * @param bo        OSS对象存储分页查询对象
     * @param pageQuery åˆ†é¡µæŸ¥è¯¢å®žä½“ç±»
     * @return ç»“æžœ
     */
    @Override
    public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
@@ -58,6 +68,12 @@
        return TableDataInfo.build(result);
    }
    /**
     * æ ¹æ®ä¸€ç»„ ossIds èŽ·å–å¯¹åº”çš„ SysOssVo åˆ—表
     *
     * @param ossIds ä¸€ç»„文件在数据库中的唯一标识集合
     * @return åŒ…含 SysOssVo å¯¹è±¡çš„列表
     */
    @Override
    public List<SysOssVo> listByIds(Collection<Long> ossIds) {
        List<SysOssVo> list = new ArrayList<>();
@@ -75,6 +91,12 @@
        return list;
    }
    /**
     * æ ¹æ®ä¸€ç»„ ossIds èŽ·å–å¯¹åº”æ–‡ä»¶çš„ URL åˆ—表
     *
     * @param ossIds ä»¥é€—号分隔的 ossId å­—符串
     * @return ä»¥é€—号分隔的文件 URL å­—符串
     */
    @Override
    public String selectUrlByIds(String ossIds) {
        List<String> list = new ArrayList<>();
@@ -92,6 +114,24 @@
        return String.join(StringUtils.SEPARATOR, list);
    }
    @Override
    public List<OssDTO> selectByIds(String ossIds) {
        List<OssDTO> list = new ArrayList<>();
        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    vo.setUrl(this.matchingUrl(vo).getUrl());
                    list.add(BeanUtil.toBean(vo, OssDTO.class));
                } catch (Exception ignored) {
                    // å¦‚æžœoss异常无法连接则将数据直接返回
                    list.add(BeanUtil.toBean(vo, OssDTO.class));
                }
            }
        }
        return list;
    }
    private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
@@ -107,12 +147,25 @@
        return lqw;
    }
    /**
     * æ ¹æ® ossId ä»Žç¼“存或数据库中获取 SysOssVo å¯¹è±¡
     *
     * @param ossId æ–‡ä»¶åœ¨æ•°æ®åº“中的唯一标识
     * @return SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     */
    @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
    @Override
    public SysOssVo getById(Long ossId) {
        return baseMapper.selectVoById(ossId);
    }
    /**
     * æ–‡ä»¶ä¸‹è½½æ–¹æ³•,支持一次性下载完整文件
     *
     * @param ossId    OSS对象ID
     * @param response HttpServletResponse对象,用于设置响应头和向客户端发送文件内容
     */
    @Override
    public void download(Long ossId, HttpServletResponse response) throws IOException {
        SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
@@ -122,15 +175,17 @@
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        OssClient storage = OssFactory.instance(sysOss.getService());
        try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
            int available = inputStream.available();
            IoUtil.copy(inputStream, response.getOutputStream(), available);
            response.setContentLength(available);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
        long contentLength = storage.download(sysOss.getFileName(), response.getOutputStream());
        response.setContentLengthLong(contentLength);
    }
    /**
     * ä¸Šä¼  MultipartFile åˆ°å¯¹è±¡å­˜å‚¨æœåŠ¡ï¼Œå¹¶ä¿å­˜æ–‡ä»¶ä¿¡æ¯åˆ°æ•°æ®åº“
     *
     * @param file è¦ä¸Šä¼ çš„ MultipartFile å¯¹è±¡
     * @return ä¸Šä¼ æˆåŠŸåŽçš„ SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     * @throws ServiceException å¦‚果上传过程中发生异常,则抛出 ServiceException å¼‚常
     */
    @Override
    public SysOssVo upload(MultipartFile file) {
        String originalfileName = file.getOriginalFilename();
@@ -138,7 +193,7 @@
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        try {
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix);
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
@@ -146,6 +201,12 @@
        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
    }
    /**
     * ä¸Šä¼ æ–‡ä»¶åˆ°å¯¹è±¡å­˜å‚¨æœåŠ¡ï¼Œå¹¶ä¿å­˜æ–‡ä»¶ä¿¡æ¯åˆ°æ•°æ®åº“
     *
     * @param file è¦ä¸Šä¼ çš„æ–‡ä»¶å¯¹è±¡
     * @return ä¸Šä¼ æˆåŠŸåŽçš„ SysOssVo å¯¹è±¡ï¼ŒåŒ…含文件信息
     */
    @Override
    public SysOssVo upload(File file) {
        String originalfileName = file.getName();
@@ -169,6 +230,13 @@
        return this.matchingUrl(sysOssVo);
    }
    /**
     * åˆ é™¤OSS对象存储
     *
     * @param ids     OSS对象ID串
     * @param isValid åˆ¤æ–­æ˜¯å¦éœ€è¦æ ¡éªŒ
     * @return ç»“æžœ
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
@@ -183,7 +251,7 @@
    }
    /**
     * åŒ¹é…Url
     * æ¡¶ç±»åž‹ä¸º private çš„URL ä¿®æ”¹ä¸ºä¸´æ—¶URL时长为120s
     *
     * @param oss OSS对象
     * @return oss åŒ¹é…Url的OSS对象
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
@@ -1,27 +1,33 @@
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.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysPost;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.bo.SysPostBo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysPostMapper;
import org.dromara.system.mapper.SysUserPostMapper;
import org.dromara.system.service.ISysPostService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
 * å²—位信息 æœåŠ¡å±‚å¤„ç†
@@ -33,12 +39,12 @@
public class SysPostServiceImpl implements ISysPostService {
    private final SysPostMapper baseMapper;
    private final SysDeptMapper deptMapper;
    private final SysUserPostMapper userPostMapper;
    @Override
    public TableDataInfo<SysPostVo> selectPagePostList(SysPostBo post, PageQuery pageQuery) {
        LambdaQueryWrapper<SysPost> lqw = buildQueryWrapper(post);
        Page<SysPostVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
        Page<SysPostVo> page = baseMapper.selectPagePostList(pageQuery.build(), buildQueryWrapper(post));
        return TableDataInfo.build(page);
    }
@@ -50,17 +56,39 @@
     */
    @Override
    public List<SysPostVo> selectPostList(SysPostBo post) {
        LambdaQueryWrapper<SysPost> lqw = buildQueryWrapper(post);
        return baseMapper.selectVoList(lqw);
        return baseMapper.selectVoList(buildQueryWrapper(post));
    }
    /**
     * æ ¹æ®æŸ¥è¯¢æ¡ä»¶æž„建查询包装器
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶å¯¹è±¡
     * @return æž„建好的查询包装器
     */
    private LambdaQueryWrapper<SysPost> buildQueryWrapper(SysPostBo bo) {
        LambdaQueryWrapper<SysPost> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getPostCode()), SysPost::getPostCode, bo.getPostCode());
        lqw.like(StringUtils.isNotBlank(bo.getPostName()), SysPost::getPostName, bo.getPostName());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysPost::getStatus, bo.getStatus());
        lqw.orderByAsc(SysPost::getPostSort);
        return lqw;
        LambdaQueryWrapper<SysPost> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(bo.getPostCode()), SysPost::getPostCode, bo.getPostCode())
            .like(StringUtils.isNotBlank(bo.getPostCategory()), SysPost::getPostCategory, bo.getPostCategory())
            .like(StringUtils.isNotBlank(bo.getPostName()), SysPost::getPostName, bo.getPostName())
            .eq(StringUtils.isNotBlank(bo.getStatus()), SysPost::getStatus, bo.getStatus())
            .orderByAsc(SysPost::getPostSort);
        if (ObjectUtil.isNotNull(bo.getDeptId())) {
            //优先单部门搜索
            wrapper.eq(SysPost::getDeptId, bo.getDeptId());
        } else if (ObjectUtil.isNotNull(bo.getBelongDeptId())) {
            //部门树搜索
            wrapper.and(x -> {
                List<Long> deptIds = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
                        .select(SysDept::getDeptId)
                        .apply(DataBaseHelper.findInSet(bo.getBelongDeptId(), "ancestors")))
                    .stream()
                    .map(SysDept::getDeptId)
                    .collect(Collectors.toList());
                deptIds.add(bo.getBelongDeptId());
                x.in(SysPost::getDeptId, deptIds);
            });
        }
        return wrapper;
    }
    /**
@@ -92,7 +120,22 @@
     */
    @Override
    public List<Long> selectPostListByUserId(Long userId) {
        return baseMapper.selectPostListByUserId(userId);
        List<SysPostVo> list = baseMapper.selectPostsByUserId(userId);
        return StreamUtils.toList(list, SysPostVo::getPostId);
    }
    /**
     * é€šè¿‡å²—位ID串查询岗位
     *
     * @param postIds å²—位id串
     * @return å²—位列表信息
     */
    @Override
    public List<SysPostVo> selectPostByIds(List<Long> postIds) {
        return baseMapper.selectVoList(new LambdaQueryWrapper<SysPost>()
            .select(SysPost::getPostId, SysPost::getPostName, SysPost::getPostCode)
            .eq(SysPost::getStatus, UserConstants.POST_NORMAL)
            .in(CollUtil.isNotEmpty(postIds), SysPost::getPostId, postIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -80,7 +80,6 @@
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                "r.create_time", params.get("beginTime"), params.get("endTime"))
            .orderByAsc("r.role_sort").orderByAsc("r.create_time");
        ;
        return wrapper;
    }
@@ -92,6 +91,17 @@
     */
    @Override
    public List<SysRoleVo> selectRolesByUserId(Long userId) {
        return baseMapper.selectRolesByUserId(userId);
    }
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色列表(包含被授权状态)
     *
     * @param userId ç”¨æˆ·ID
     * @return è§’色列表
     */
    @Override
    public List<SysRoleVo> selectRolesAuthByUserId(Long userId) {
        List<SysRoleVo> userRoles = baseMapper.selectRolePermissionByUserId(userId);
        List<SysRoleVo> roles = selectRoleAll();
        for (SysRoleVo role : roles) {
@@ -141,7 +151,8 @@
     */
    @Override
    public List<Long> selectRoleListByUserId(Long userId) {
        return baseMapper.selectRoleListByUserId(userId);
        List<SysRoleVo> list = baseMapper.selectRolesByUserId(userId);
        return StreamUtils.toList(list, SysRoleVo::getRoleId);
    }
    /**
@@ -156,6 +167,19 @@
    }
    /**
     * é€šè¿‡è§’色ID串查询角色
     *
     * @param roleIds è§’色ID串
     * @return è§’色列表信息
     */
    @Override
    public List<SysRoleVo> selectRoleByIds(List<Long> roleIds) {
        return baseMapper.selectRoleList(new QueryWrapper<SysRole>()
            .eq("r.status", UserConstants.ROLE_NORMAL)
            .in(CollUtil.isNotEmpty(roleIds), "r.role_id", roleIds));
    }
    /**
     * æ ¡éªŒè§’色名称是否唯一
     *
     * @param role è§’色信息
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
@@ -1,8 +1,10 @@
package org.dromara.system.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.domain.SysSocial;
import org.dromara.system.domain.bo.SysSocialBo;
import org.dromara.system.domain.vo.SysSocialVo;
@@ -37,8 +39,12 @@
     * æŽˆæƒåˆ—表
     */
    @Override
    public List<SysSocialVo> queryList() {
        return baseMapper.selectVoList();
    public List<SysSocialVo> queryList(SysSocialBo bo) {
        LambdaQueryWrapper<SysSocial> lqw = new LambdaQueryWrapper<SysSocial>()
            .eq(ObjectUtil.isNotNull(bo.getUserId()), SysSocial::getUserId, bo.getUserId())
            .eq(StringUtils.isNotBlank(bo.getAuthId()), SysSocial::getAuthId, bo.getAuthId())
            .eq(StringUtils.isNotBlank(bo.getSource()), SysSocial::getSource, bo.getSource());
        return baseMapper.selectVoList(lqw);
    }
    @Override
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -1,6 +1,8 @@
package org.dromara.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
@@ -13,9 +15,11 @@
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.MapstructUtils;
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.mybatis.core.page.PageQuery;
@@ -29,6 +33,7 @@
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.*;
import org.dromara.system.service.ISysUserService;
@@ -36,6 +41,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -69,28 +75,31 @@
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    @Override
    public List<SysUserVo> selectUserList(SysUserBo user) {
        return baseMapper.selectUserList(this.buildQueryWrapper(user));
    public List<SysUserExportVo> selectUserExportList(SysUserBo user) {
        return baseMapper.selectUserExportList(this.buildQueryWrapper(user));
    }
    private Wrapper<SysUser> buildQueryWrapper(SysUserBo user) {
        Map<String, Object> params = user.getParams();
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
                .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
                .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
                .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
                .between(params.get("beginTime") != null && params.get("endTime") != null,
                        "u.create_time", params.get("beginTime"), params.get("endTime"))
                .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
                    List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
                            .select(SysDept::getDeptId)
                            .apply(DataBaseHelper.findInSet(user.getDeptId(), "ancestors")));
                    List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
                    ids.add(user.getDeptId());
                    w.in("u.dept_id", ids);
                }).orderByAsc("u.user_id");
            .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
            .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                "u.create_time", params.get("beginTime"), params.get("endTime"))
            .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
                List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
                    .select(SysDept::getDeptId)
                    .apply(DataBaseHelper.findInSet(user.getDeptId(), "ancestors")));
                List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
                ids.add(user.getDeptId());
                w.in("u.dept_id", ids);
            }).orderByAsc("u.user_id");
        if (StringUtils.isNotBlank(user.getExcludeUserIds())) {
            wrapper.notIn("u.user_id", StringUtils.splitList(user.getExcludeUserIds()));
        }
        return wrapper;
    }
@@ -104,11 +113,11 @@
    public TableDataInfo<SysUserVo> selectAllocatedList(SysUserBo user, PageQuery pageQuery) {
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(user.getRoleId()), "r.role_id", user.getRoleId())
                .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
                .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
                .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
                .orderByAsc("u.user_id");
            .eq(ObjectUtil.isNotNull(user.getRoleId()), "r.role_id", user.getRoleId())
            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
            .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
            .orderByAsc("u.user_id");
        Page<SysUserVo> page = baseMapper.selectAllocatedList(pageQuery.build(), wrapper);
        return TableDataInfo.build(page);
    }
@@ -124,11 +133,11 @@
        List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(user.getRoleId());
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
                .and(w -> w.ne("r.role_id", user.getRoleId()).or().isNull("r.role_id"))
                .notIn(CollUtil.isNotEmpty(userIds), "u.user_id", userIds)
                .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
                .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
                .orderByAsc("u.user_id");
            .and(w -> w.ne("r.role_id", user.getRoleId()).or().isNull("r.role_id"))
            .notIn(CollUtil.isNotEmpty(userIds), "u.user_id", userIds)
            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
            .orderByAsc("u.user_id");
        Page<SysUserVo> page = baseMapper.selectUnallocatedList(pageQuery.build(), wrapper);
        return TableDataInfo.build(page);
    }
@@ -141,7 +150,7 @@
     */
    @Override
    public SysUserVo selectUserByUserName(String userName) {
        return baseMapper.selectUserByUserName(userName);
        return baseMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, userName));
    }
    /**
@@ -152,7 +161,7 @@
     */
    @Override
    public SysUserVo selectUserByPhonenumber(String phonenumber) {
        return baseMapper.selectUserByPhonenumber(phonenumber);
        return baseMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
    }
    /**
@@ -163,18 +172,39 @@
     */
    @Override
    public SysUserVo selectUserById(Long userId) {
        return baseMapper.selectUserById(userId);
        SysUserVo user = baseMapper.selectVoById(userId);
        if (ObjectUtil.isNull(user)) {
            return user;
        }
        user.setRoles(roleMapper.selectRolesByUserId(user.getUserId()));
        return user;
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID串查询用户
     *
     * @param userIds ç”¨æˆ·ID串
     * @param deptId  éƒ¨é—¨id
     * @return ç”¨æˆ·åˆ—表信息
     */
    @Override
    public List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId) {
        return baseMapper.selectUserList(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName)
            .eq(SysUser::getStatus, UserConstants.USER_NORMAL)
            .eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId)
            .in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
    }
    /**
     * æŸ¥è¯¢ç”¨æˆ·æ‰€å±žè§’色组
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return ç»“æžœ
     */
    @Override
    public String selectUserRoleGroup(String userName) {
        List<SysRoleVo> list = roleMapper.selectRolesByUserName(userName);
    public String selectUserRoleGroup(Long userId) {
        List<SysRoleVo> list = roleMapper.selectRolesByUserId(userId);
        if (CollUtil.isEmpty(list)) {
            return StringUtils.EMPTY;
        }
@@ -184,12 +214,12 @@
    /**
     * æŸ¥è¯¢ç”¨æˆ·æ‰€å±žå²—位组
     *
     * @param userName ç”¨æˆ·å
     * @param userId ç”¨æˆ·ID
     * @return ç»“æžœ
     */
    @Override
    public String selectUserPostGroup(String userName) {
        List<SysPostVo> list = postMapper.selectPostsByUserName(userName);
    public String selectUserPostGroup(Long userId) {
        List<SysPostVo> list = postMapper.selectPostsByUserId(userId);
        if (CollUtil.isEmpty(list)) {
            return StringUtils.EMPTY;
        }
@@ -205,8 +235,8 @@
    @Override
    public boolean checkUserNameUnique(SysUserBo user) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserName, user.getUserName())
                .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
            .eq(SysUser::getUserName, user.getUserName())
            .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
        return !exist;
    }
@@ -218,8 +248,8 @@
    @Override
    public boolean checkPhoneUnique(SysUserBo user) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getPhonenumber, user.getPhonenumber())
                .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
            .eq(SysUser::getPhonenumber, user.getPhonenumber())
            .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
        return !exist;
    }
@@ -231,8 +261,8 @@
    @Override
    public boolean checkEmailUnique(SysUserBo user) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getEmail, user.getEmail())
                .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
            .eq(SysUser::getEmail, user.getEmail())
            .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
        return !exist;
    }
@@ -261,7 +291,7 @@
        if (LoginHelper.isSuperAdmin()) {
            return;
        }
        if (ObjectUtil.isNull(baseMapper.selectUserById(userId))) {
        if (baseMapper.countUserById(userId) == 0) {
            throw new ServiceException("没有权限访问用户数据!");
        }
    }
@@ -294,8 +324,8 @@
     */
    @Override
    public boolean registerUser(SysUserBo user, String tenantId) {
        user.setCreateBy(user.getUserId());
        user.setUpdateBy(user.getUserId());
        user.setCreateBy(0L);
        user.setUpdateBy(0L);
        SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
        sysUser.setTenantId(tenantId);
        return baseMapper.insert(sysUser) > 0;
@@ -345,9 +375,9 @@
    @Override
    public int updateUserStatus(Long userId, String status) {
        return baseMapper.update(null,
                new LambdaUpdateWrapper<SysUser>()
                        .set(SysUser::getStatus, status)
                        .eq(SysUser::getUserId, userId));
            new LambdaUpdateWrapper<SysUser>()
                .set(SysUser::getStatus, status)
                .eq(SysUser::getUserId, userId));
    }
    /**
@@ -359,12 +389,12 @@
    @Override
    public int updateUserProfile(SysUserBo user) {
        return baseMapper.update(null,
                new LambdaUpdateWrapper<SysUser>()
                        .set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName())
                        .set(SysUser::getPhonenumber, user.getPhonenumber())
                        .set(SysUser::getEmail, user.getEmail())
                        .set(SysUser::getSex, user.getSex())
                        .eq(SysUser::getUserId, user.getUserId()));
            new LambdaUpdateWrapper<SysUser>()
                .set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName())
                .set(SysUser::getPhonenumber, user.getPhonenumber())
                .set(SysUser::getEmail, user.getEmail())
                .set(SysUser::getSex, user.getSex())
                .eq(SysUser::getUserId, user.getUserId()));
    }
    /**
@@ -377,9 +407,9 @@
    @Override
    public boolean updateUserAvatar(Long userId, Long avatar) {
        return baseMapper.update(null,
                new LambdaUpdateWrapper<SysUser>()
                        .set(SysUser::getAvatar, avatar)
                        .eq(SysUser::getUserId, userId)) > 0;
            new LambdaUpdateWrapper<SysUser>()
                .set(SysUser::getAvatar, avatar)
                .eq(SysUser::getUserId, userId)) > 0;
    }
    /**
@@ -392,9 +422,9 @@
    @Override
    public int resetUserPwd(Long userId, String password) {
        return baseMapper.update(null,
                new LambdaUpdateWrapper<SysUser>()
                        .set(SysUser::getPassword, password)
                        .eq(SysUser::getUserId, userId));
            new LambdaUpdateWrapper<SysUser>()
                .set(SysUser::getPassword, password)
                .eq(SysUser::getUserId, userId));
    }
    /**
@@ -518,8 +548,8 @@
    /**
     * é€šè¿‡éƒ¨é—¨id查询当前部门所有用户
     *
     * @param deptId
     * @return
     * @param deptId éƒ¨é—¨ID
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    @Override
    public List<SysUserVo> selectUserListByDept(Long deptId) {
@@ -529,14 +559,26 @@
        return baseMapper.selectVoList(lqw);
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户账户
     *
     * @param userId ç”¨æˆ·ID
     * @return ç”¨æˆ·è´¦æˆ·
     */
    @Cacheable(cacheNames = CacheNames.SYS_USER_NAME, key = "#userId")
    @Override
    public String selectUserNameById(Long userId) {
        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getUserName).eq(SysUser::getUserId, userId));
            .select(SysUser::getUserName).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserName();
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户账户
     *
     * @param userId ç”¨æˆ·ID
     * @return ç”¨æˆ·è´¦æˆ·
     */
    @Override
    @Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
    public String selectNicknameById(Long userId) {
@@ -544,4 +586,65 @@
            .select(SysUser::getNickName).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getNickName();
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户账户
     *
     * @param userIds ç”¨æˆ·ID å¤šä¸ªç”¨é€—号隔开
     * @return ç”¨æˆ·è´¦æˆ·
     */
    @Override
    public String selectNicknameByIds(String userIds) {
        List<String> list = new ArrayList<>();
        for (Long id : StringUtils.splitTo(userIds, Convert::toLong)) {
            String nickname = SpringUtils.getAopProxy(this).selectNicknameById(id);
            if (StringUtils.isNotBlank(nickname)) {
                list.add(nickname);
            }
        }
        return String.join(StringUtils.SEPARATOR, list);
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户手机号
     *
     * @param userId ç”¨æˆ·id
     * @return ç”¨æˆ·æ‰‹æœºå·
     */
    @Override
    public String selectPhonenumberById(Long userId) {
        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getPhonenumber).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getPhonenumber();
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户邮箱
     *
     * @param userId ç”¨æˆ·id
     * @return ç”¨æˆ·é‚®ç®±
     */
    @Override
    public String selectEmailById(Long userId) {
        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getEmail).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getEmail();
    }
    @Override
    public List<UserDTO> selectListByIds(List<Long> userIds) {
        if (CollUtil.isEmpty(userIds)) {
            return List.of();
        }
        List<SysUserVo> list = this.selectUserByIds(userIds, null);
        return BeanUtil.copyToList(list, UserDTO.class);
    }
    @Override
    public List<Long> selectUserIdsByRoleIds(List<Long> roleIds) {
        List<SysUserRole> userRoles = userRoleMapper.selectList(
            new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
        return StreamUtils.toList(userRoles, SysUserRole::getUserId);
    }
}
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -8,11 +8,18 @@
    </resultMap>
    <select id="selectDeptList" resultMap="SysDeptResult">
        select * from sys_dept ${ew.getCustomSqlSegment}
        select
        <if test="ew.getSqlSelect != null">
            ${ew.getSqlSelect}
        </if>
        <if test="ew.getSqlSelect == null">
            *
        </if>
        from sys_dept ${ew.getCustomSqlSegment}
    </select>
    <select id="selectDeptById" resultMap="SysDeptResult">
        select * from sys_dept where del_flag = '0' and dept_id = #{deptId}
    <select id="countDeptById" resultType="Long">
        select count(*) from sys_dept where del_flag = '0' and dept_id = #{deptId}
    </select>
    <select id="selectDeptListByRoleId" resultType="Long">
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml
@@ -7,20 +7,23 @@
    <resultMap type="org.dromara.system.domain.vo.SysPostVo" id="SysPostResult">
    </resultMap>
    <select id="selectPostListByUserId" parameterType="Long" resultType="Long">
        select p.post_id
    <select id="selectPagePostList" resultMap="SysPostResult">
        select
        <if test="ew.getSqlSelect != null">
            ${ew.getSqlSelect}
        </if>
        <if test="ew.getSqlSelect == null">
            *
        </if>
        from sys_post ${ew.getCustomSqlSegment}
    </select>
    <select id="selectPostsByUserId" parameterType="Long" resultMap="SysPostResult">
        select p.post_id, p.dept_id, p.post_name, p.post_code, p.post_category
        from sys_post p
                 left join sys_user_post up on up.post_id = p.post_id
                 left join sys_user u on u.user_id = up.user_id
        where u.user_id = #{userId}
    </select>
    <select id="selectPostsByUserName" parameterType="String" resultMap="SysPostResult">
        select p.post_id, p.post_name, p.post_code
        from sys_post p
                 left join sys_user_post up on up.post_id = p.post_id
                 left join sys_user u on u.user_id = up.user_id
        where u.user_name = #{userName}
    </select>
</mapper>
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
@@ -40,23 +40,17 @@
        WHERE r.del_flag = '0' and sur.user_id = #{userId}
    </select>
    <select id="selectRoleListByUserId" parameterType="Long" resultType="Long">
        select r.role_id
        from sys_role r
                 left join sys_user_role sur on sur.role_id = r.role_id
                 left join sys_user u on u.user_id = sur.user_id
        where u.user_id = #{userId}
    </select>
    <select id="selectRolesByUserName" parameterType="String" resultMap="SysRoleResult">
    <select id="selectRolesByUserId" parameterType="Long" resultMap="SysRoleResult">
        select r.role_id,
               r.role_name,
               r.role_key,
               r.role_sort
               r.role_sort,
               r.data_scope,
               r.status
        from sys_role r
                 left join sys_user_role sur on sur.role_id = r.role_id
                 left join sys_user u on u.user_id = sur.user_id
        WHERE r.del_flag = '0' and u.user_name = #{userName}
        WHERE r.del_flag = '0' and sur.user_id = #{userId}
    </select>
    <select id="selectRoleById" resultMap="SysRoleResult">
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -4,78 +4,40 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.SysUserMapper">
    <!-- å¤šç»“构嵌套自动映射需带上每个实体的主键id å¦åˆ™æ˜ å°„会失败 -->
    <resultMap type="org.dromara.system.domain.vo.SysUserVo" id="SysUserResult">
        <id property="userId" column="user_id"/>
        <result property="deptId" column="dept_id"/>
        <association property="dept" column="dept_id" resultMap="deptResult"/>
        <collection property="roles" javaType="java.util.List" resultMap="RoleResult"/>
    </resultMap>
    <resultMap id="deptResult" type="org.dromara.system.domain.vo.SysDeptVo">
        <id property="deptId" column="dept_id"/>
        <result property="email" column="dept_email"/>
        <result property="status" column="dept_status"/>
        <result property="createTime" column="dept_create_time"/>
    <resultMap type="org.dromara.system.domain.vo.SysUserExportVo" id="SysUserExportResult">
        <id property="userId" column="user_id"/>
    </resultMap>
    <resultMap id="RoleResult" type="org.dromara.system.domain.vo.SysRoleVo">
        <id property="roleId" column="role_id"/>
        <result property="status" column="role_status"/>
        <result property="createTime" column="role_create_time"/>
    </resultMap>
    <sql id="selectUserVo">
        select u.user_id,
               u.tenant_id,
               u.dept_id,
               u.user_name,
               u.nick_name,
               u.user_type,
               u.email,
               u.avatar,
               u.phonenumber,
               u.password,
               u.sex,
               u.status,
               u.del_flag,
               u.login_ip,
               u.login_date,
               u.create_by,
               u.create_time,
               u.remark,
               d.dept_id,
               d.parent_id,
               d.ancestors,
               d.dept_name,
               d.order_num,
               d.leader,
               d.status as dept_status,
               d.email as dept_email,
               d.create_time as dept_cteate_time,
               r.role_id,
               r.role_name,
               r.role_key,
               r.role_sort,
               r.data_scope,
               r.status as role_status
        from sys_user u
            left join sys_dept d on u.dept_id = d.dept_id
            left join sys_user_role sur on u.user_id = sur.user_id
            left join sys_role r on r.role_id = sur.role_id
    </sql>
    <select id="selectPageUserList" resultMap="SysUserResult">
        select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
            d.dept_name, d.leader, u1.user_name as leaderName
        select
        <if test="ew.getSqlSelect != null">
            ${ew.getSqlSelect}
        </if>
        <if test="ew.getSqlSelect == null">
            u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark
        </if>
        from sys_user u
            left join sys_dept d on u.dept_id = d.dept_id
            left join sys_user u1 on u1.user_id = d.leader
        ${ew.getCustomSqlSegment}
    </select>
    <select id="selectUserList" resultMap="SysUserResult">
        select
        <if test="ew.getSqlSelect != null">
            ${ew.getSqlSelect}
        </if>
        <if test="ew.getSqlSelect == null">
            u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark
        </if>
        from sys_user u
        ${ew.getCustomSqlSegment}
    </select>
    <select id="selectUserExportList" resultMap="SysUserExportResult">
        select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
            d.dept_name, d.leader, u1.user_name as leaderName
@@ -103,24 +65,8 @@
        ${ew.getCustomSqlSegment}
    </select>
    <select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.user_name = #{userName}
    </select>
    <select id="selectUserByPhonenumber" parameterType="String" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.phonenumber = #{phonenumber}
    </select>
    <select id="selectUserByEmail" parameterType="String" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.email = #{email}
    </select>
    <select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.user_id = #{userId}
    <select id="countUserById" resultType="Long">
        select count(*) from sys_user where del_flag = '0' and user_id = #{userId}
    </select>
ruoyi-modules/ruoyi-workflow/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
<?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-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>ruoyi-workflow</artifactId>
    <description>
        å·¥ä½œæµæ¨¡å—
    </description>
    <dependencies>
        <!--引入flowable依赖-->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-autoconfigure</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.flowable</groupId>
                    <artifactId>flowable-spring-security</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-configurator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- ç»˜åˆ¶flowable流程图 -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-image-generator</artifactId>
        </dependency>
        <!-- flowable json è½¬æ¢ -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-json-converter</artifactId>
            <version>6.8.0</version>
        </dependency>
        <!-- svg转png图片工具-->
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-all</artifactId>
            <version>1.10</version>
            <exclusions>
                <exclusion>
                    <groupId>xalan</groupId>
                    <artifactId>xalan</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-sms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-log</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-idempotent</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-excel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-translation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-tenant</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.dromara.workflow.annotation;
import java.lang.annotation.*;
/**
 * æµç¨‹ä»»åŠ¡ç›‘å¬æ³¨è§£
 *
 * @author may
 * @date 2023-12-27
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FlowListenerAnnotation {
    /**
     * æµç¨‹å®šä¹‰key
     */
    String processDefinitionKey();
    /**
     * èŠ‚ç‚¹id
     */
    String taskDefId() default "";
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,137 @@
package org.dromara.workflow.common.constant;
/**
 * å·¥ä½œæµå¸¸é‡
 *
 * @author may
 */
public interface FlowConstant {
    String MESSAGE_CURRENT_TASK_IS_NULL = "当前任务不存在或你不是任务办理人!";
    String MESSAGE_SUSPENDED = "当前任务已挂起不可审批!";
    /**
     * è¿žçº¿
     */
    String SEQUENCE_FLOW = "sequenceFlow";
    /**
     * å¹¶è¡Œç½‘å…³
     */
    String PARALLEL_GATEWAY = "parallelGateway";
    /**
     * æŽ’它网关
     */
    String EXCLUSIVE_GATEWAY = "exclusiveGateway";
    /**
     * åŒ…含网关
     */
    String INCLUSIVE_GATEWAY = "inclusiveGateway";
    /**
     * ç»“束节点
     */
    String END_EVENT = "endEvent";
    /**
     * æµç¨‹å§”派标识
     */
    String PENDING = "PENDING";
    /**
     * å€™é€‰äººæ ‡è¯†
     */
    String CANDIDATE = "candidate";
    /**
     * ä¼šç­¾ä»»åŠ¡æ€»æ•°
     */
    String NUMBER_OF_INSTANCES = "nrOfInstances";
    /**
     * æ­£åœ¨æ‰§è¡Œçš„会签总数
     */
    String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
    /**
     * å·²å®Œæˆçš„会签任务总数
     */
    String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";
    /**
     * å¾ªçŽ¯çš„ç´¢å¼•å€¼ï¼Œå¯ä»¥ä½¿ç”¨elementIndexVariable属性修改loopCounter的变量名
     */
    String LOOP_COUNTER = "loopCounter";
    String ZIP = "ZIP";
    /**
     * æµç¨‹å®žä¾‹å¯¹è±¡
     */
    String PROCESS_INSTANCE_VO = "processInstanceVo";
    /**
     * æµç¨‹å®šä¹‰é…ç½®
     */
    String WF_DEFINITION_CONFIG_VO = "wfDefinitionConfigVo";
    /**
     * èŠ‚ç‚¹é…ç½®
     */
    String WF_NODE_CONFIG_VO = "wfNodeConfigVo";
    /**
     * æµç¨‹å‘起人
     */
    String INITIATOR = "initiator";
    /**
     * æµç¨‹å®žä¾‹id
     */
    String PROCESS_INSTANCE_ID = "processInstanceId";
    /**
     * ä¸šåŠ¡id
     */
    String BUSINESS_KEY = "businessKey";
    /**
     * æµç¨‹å®šä¹‰id
     */
    String PROCESS_DEFINITION_ID = "processDefinitionId";
    /**
     * å¼€å¯è·³è¿‡è¡¨è¾¾å¼å˜é‡
     */
    String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
    /**
     * æ¨¡åž‹æ ‡è¯†key命名规范正则表达式
     */
    String MODEL_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9_]{0,254}$";
    /**
     * ç”¨æˆ·ä»»åŠ¡
     */
    String USER_TASK = "userTask";
    /**
     * ä¼šç­¾
     */
    String MULTI_INSTANCE = "multiInstance";
    /**
     * æ˜¯
     */
    String TRUE = "0";
    /**
     * å¦
     */
    String FALSE = "1";
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,152 @@
package org.dromara.workflow.common.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import java.util.Arrays;
/**
 * ä¸šåŠ¡çŠ¶æ€æžšä¸¾
 *
 * @author may
 */
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
    /**
     * å·²æ’¤é”€
     */
    CANCEL("cancel", "已撤销"),
    /**
     * è‰ç¨¿
     */
    DRAFT("draft", "草稿"),
    /**
     * å¾…审核
     */
    WAITING("waiting", "待审核"),
    /**
     * å·²å®Œæˆ
     */
    FINISH("finish", "已完成"),
    /**
     * å·²ä½œåºŸ
     */
    INVALID("invalid", "已作废"),
    /**
     * å·²é€€å›ž
     */
    BACK("back", "已退回"),
    /**
     * å·²ç»ˆæ­¢
     */
    TERMINATION("termination", "已终止");
    /**
     * çŠ¶æ€
     */
    private final String status;
    /**
     * æè¿°
     */
    private final String desc;
    /**
     * èŽ·å–ä¸šåŠ¡çŠ¶æ€
     *
     * @param status çŠ¶æ€
     */
    public static String findByStatus(String status) {
        if (StringUtils.isBlank(status)) {
            return StrUtil.EMPTY;
        }
        return Arrays.stream(BusinessStatusEnum.values())
            .filter(statusEnum -> statusEnum.getStatus().equals(status))
            .findFirst()
            .map(BusinessStatusEnum::getDesc)
            .orElse(StrUtil.EMPTY);
    }
    /**
     * å¯åŠ¨æµç¨‹æ ¡éªŒ
     *
     * @param status çŠ¶æ€
     */
    public static void checkStartStatus(String status) {
        if (WAITING.getStatus().equals(status)) {
            throw new ServiceException("该单据已提交过申请,正在审批中!");
        } else if (FINISH.getStatus().equals(status)) {
            throw new ServiceException("该单据已完成申请!");
        } else if (INVALID.getStatus().equals(status)) {
            throw new ServiceException("该单据已作废!");
        } else if (TERMINATION.getStatus().equals(status)) {
            throw new ServiceException("该单据已终止!");
        } else if (StringUtils.isBlank(status)) {
            throw new ServiceException("流程状态为空!");
        }
    }
    /**
     * æ’¤é”€æµç¨‹æ ¡éªŒ
     *
     * @param status çŠ¶æ€
     */
    public static void checkCancelStatus(String status) {
        if (CANCEL.getStatus().equals(status)) {
            throw new ServiceException("该单据已撤销!");
        } else if (FINISH.getStatus().equals(status)) {
            throw new ServiceException("该单据已完成申请!");
        } else if (INVALID.getStatus().equals(status)) {
            throw new ServiceException("该单据已作废!");
        } else if (TERMINATION.getStatus().equals(status)) {
            throw new ServiceException("该单据已终止!");
        } else if (BACK.getStatus().equals(status)) {
            throw new ServiceException("该单据已退回!");
        } else if (StringUtils.isBlank(status)) {
            throw new ServiceException("流程状态为空!");
        }
    }
    /**
     * é©³å›žæµç¨‹æ ¡éªŒ
     *
     * @param status çŠ¶æ€
     */
    public static void checkBackStatus(String status) {
        if (BACK.getStatus().equals(status)) {
            throw new ServiceException("该单据已退回!");
        } else if (FINISH.getStatus().equals(status)) {
            throw new ServiceException("该单据已完成申请!");
        } else if (INVALID.getStatus().equals(status)) {
            throw new ServiceException("该单据已作废!");
        } else if (TERMINATION.getStatus().equals(status)) {
            throw new ServiceException("该单据已终止!");
        } else if (CANCEL.getStatus().equals(status)) {
            throw new ServiceException("该单据已撤销!");
        } else if (StringUtils.isBlank(status)) {
            throw new ServiceException("流程状态为空!");
        }
    }
    /**
     * ä½œåºŸ,终止流程校验
     *
     * @param status çŠ¶æ€
     */
    public static void checkInvalidStatus(String status) {
        if (FINISH.getStatus().equals(status)) {
            throw new ServiceException("该单据已完成申请!");
        } else if (INVALID.getStatus().equals(status)) {
            throw new ServiceException("该单据已作废!");
        } else if (TERMINATION.getStatus().equals(status)) {
            throw new ServiceException("该单据已终止!");
        } else if (StringUtils.isBlank(status)) {
            throw new ServiceException("流程状态为空!");
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/FormTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package org.dromara.workflow.common.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
/**
 * ä»»åŠ¡çŠ¶æ€æžšä¸¾
 *
 * @author may
 */
@Getter
@AllArgsConstructor
public enum FormTypeEnum {
    /**
     * è‡ªå®šä¹‰è¡¨å•
     */
    STATIC("static", "自定义表单"),
    /**
     * åŠ¨æ€è¡¨å•
     */
    DYNAMIC("dynamic", "动态表单");
    /**
     * ç±»åž‹
     */
    private final String type;
    /**
     * æè¿°
     */
    private final String desc;
    /**
     * è¡¨å•类型
     *
     * @param formType è¡¨å•类型
     */
    public static String findByType(String formType) {
        if (StringUtils.isBlank(formType)) {
            return StrUtil.EMPTY;
        }
        return Arrays.stream(FormTypeEnum.values())
            .filter(statusEnum -> statusEnum.getType().equals(formType))
            .findFirst()
            .map(FormTypeEnum::getDesc)
            .orElse(StrUtil.EMPTY);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * æ¶ˆæ¯ç±»åž‹æžšä¸¾
 *
 * @author may
 */
@Getter
@AllArgsConstructor
public enum MessageTypeEnum {
    /**
     * ç«™å†…ä¿¡
     */
    SYSTEM_MESSAGE("1", "站内信"),
    /**
     * é‚®ç®±
     */
    EMAIL_MESSAGE("2", "邮箱"),
    /**
     * çŸ­ä¿¡
     */
    SMS_MESSAGE("3", "短信");
    private final String code;
    private final String desc;
    private final static Map<String, MessageTypeEnum> MESSAGE_TYPE_ENUM_MAP = new ConcurrentHashMap<>(MessageTypeEnum.values().length);
    static {
        for (MessageTypeEnum messageType : MessageTypeEnum.values()) {
            MESSAGE_TYPE_ENUM_MAP.put(messageType.code, messageType);
        }
    }
    /**
     * æ ¹æ®æ¶ˆæ¯ç±»åž‹ code èŽ·å– MessageTypeEnum
     * @param code æ¶ˆæ¯ç±»åž‹code
     * @return MessageTypeEnum
     */
    public static MessageTypeEnum getByCode(String code) {
        return MESSAGE_TYPE_ENUM_MAP.get(code);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package org.dromara.workflow.common.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
/**
 * ä»»åŠ¡çŠ¶æ€æžšä¸¾
 *
 * @author may
 */
@Getter
@AllArgsConstructor
public enum TaskStatusEnum {
    /**
     * æ’¤é”€
     */
    CANCEL("cancel", "撤销"),
    /**
     * é€šè¿‡
     */
    PASS("pass", "通过"),
    /**
     * å¾…审核
     */
    WAITING("waiting", "待审核"),
    /**
     * ä½œåºŸ
     */
    INVALID("invalid", "作废"),
    /**
     * é€€å›ž
     */
    BACK("back", "退回"),
    /**
     * ç»ˆæ­¢
     */
    TERMINATION("termination", "终止"),
    /**
     * è½¬åŠž
     */
    TRANSFER("transfer", "转办"),
    /**
     * å§”托
     */
    PENDING("pending", "委托"),
    /**
     * æŠ„送
     */
    COPY("copy", "抄送"),
    /**
     * åŠ ç­¾
     */
    SIGN("sign", "加签"),
    /**
     * å‡ç­¾
     */
    SIGN_OFF("sign_off", "减签"),
    /**
     * è¶…æ—¶
     */
    TIMEOUT("timeout", "超时");
    /**
     * çŠ¶æ€
     */
    private final String status;
    /**
     * æè¿°
     */
    private final String desc;
    /**
     * ä»»åŠ¡ä¸šåŠ¡çŠ¶æ€
     *
     * @param status çŠ¶æ€
     */
    public static String findByStatus(String status) {
        if (StringUtils.isBlank(status)) {
            return StrUtil.EMPTY;
        }
        return Arrays.stream(TaskStatusEnum.values())
            .filter(statusEnum -> statusEnum.getStatus().equals(status))
            .findFirst()
            .map(TaskStatusEnum::getDesc)
            .orElse(StrUtil.EMPTY);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
package org.dromara.workflow.controller;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.bo.ModelBo;
import org.dromara.workflow.domain.vo.ModelVo;
import org.dromara.workflow.service.IActModelService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
 * æ¨¡åž‹ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/model")
public class ActModelController extends BaseController {
    private final RepositoryService repositoryService;
    private final IActModelService actModelService;
    /**
     * åˆ†é¡µæŸ¥è¯¢æ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹å‚æ•°
     */
    @GetMapping("/list")
    public TableDataInfo<Model> page(ModelBo modelBo, PageQuery pageQuery) {
        return actModelService.page(modelBo, pageQuery);
    }
    /**
     * æ–°å¢žæ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹è¯·æ±‚对象
     */
    @Log(title = "模型管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/save")
    public R<Void> saveNewModel(@Validated(AddGroup.class) @RequestBody ModelBo modelBo) {
        return toAjax(actModelService.saveNewModel(modelBo));
    }
    /**
     * æŸ¥è¯¢æ¨¡åž‹
     *
     * @param id æ¨¡åž‹id
     */
    @GetMapping("/getInfo/{id}")
    public R<ModelVo> getInfo(@NotBlank(message = "模型id不能为空") @PathVariable String id) {
        return R.ok(actModelService.getInfo(id));
    }
    /**
     * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     */
    @Log(title = "模型管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping(value = "/update")
    public R<Void> update(@RequestBody ModelBo modelBo) {
        return toAjax(actModelService.update(modelBo));
    }
    /**
     * ç¼–辑XMl模型
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     */
    @Log(title = "模型管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping(value = "/editModelXml")
    public R<Void> editModel(@Validated(EditGroup.class) @RequestBody ModelBo modelBo) {
        return toAjax(actModelService.editModelXml(modelBo));
    }
    /**
     * åˆ é™¤æµç¨‹æ¨¡åž‹
     *
     * @param ids æ¨¡åž‹id
     */
    @Log(title = "模型管理", businessType = BusinessType.DELETE)
    @RepeatSubmit()
    @DeleteMapping("/{ids}")
    @Transactional(rollbackFor = Exception.class)
    public R<Void> delete(@NotEmpty(message = "主键不能为空") @PathVariable String[] ids) {
        Arrays.stream(ids).parallel().forEachOrdered(repositoryService::deleteModel);
        return R.ok();
    }
    /**
     * æ¨¡åž‹éƒ¨ç½²
     *
     * @param id æ¨¡åž‹id
     */
    @Log(title = "模型管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/modelDeploy/{id}")
    public R<Void> deploy(@NotBlank(message = "模型id不能为空") @PathVariable("id") String id) {
        return toAjax(actModelService.modelDeploy(id));
    }
    /**
     * å¯¼å‡ºæ¨¡åž‹zip压缩包
     *
     * @param modelIds æ¨¡åž‹id
     * @param response ç›¸åº”
     */
    @GetMapping("/export/zip/{modelIds}")
    public void exportZip(@NotEmpty(message = "模型id不能为空") @PathVariable List<String> modelIds,
                          HttpServletResponse response) {
        actModelService.exportZip(modelIds, response);
    }
    /**
     * å¤åˆ¶æ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     */
    @PostMapping("/copyModel")
    public R<Void> copyModel(@RequestBody ModelBo modelBo) {
        return toAjax(actModelService.copyModel(modelBo));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
package org.dromara.workflow.controller;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
import org.dromara.workflow.service.IActProcessDefinitionService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹å®šä¹‰ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/processDefinition")
public class ActProcessDefinitionController extends BaseController {
    private final IActProcessDefinitionService actProcessDefinitionService;
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param bo å‚æ•°
     */
    @GetMapping("/list")
    public TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo bo, PageQuery pageQuery) {
        return actProcessDefinitionService.page(bo, pageQuery);
    }
    /**
     * æŸ¥è¯¢åŽ†å²æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @param key æµç¨‹å®šä¹‰key
     */
    @GetMapping("/getListByKey/{key}")
    public R<List<ProcessDefinitionVo>> getListByKey(@NotEmpty(message = "流程定义key不能为空") @PathVariable String key) {
        return R.ok("操作成功", actProcessDefinitionService.getListByKey(key));
    }
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰å›¾ç‰‡
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @GetMapping("/definitionImage/{processDefinitionId}")
    public R<String> definitionImage(@PathVariable String processDefinitionId) {
        return R.ok("操作成功", actProcessDefinitionService.definitionImage(processDefinitionId));
    }
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰xml文件
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @GetMapping("/definitionXml/{processDefinitionId}")
    public R<Map<String, Object>> definitionXml(@NotBlank(message = "流程定义id不能为空") @PathVariable String processDefinitionId) {
        Map<String, Object> map = new HashMap<>();
        String xmlStr = actProcessDefinitionService.definitionXml(processDefinitionId);
        map.put("xml", Arrays.asList(xmlStr.split("\n")));
        map.put("xmlStr", xmlStr);
        return R.ok(map);
    }
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     *
     * @param deploymentIds        éƒ¨ç½²id
     * @param processDefinitionIds æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{deploymentIds}/{processDefinitionIds}")
    public R<Void> deleteDeployment(@NotNull(message = "流程部署id不能为空") @PathVariable List<String> deploymentIds,
                                    @NotNull(message = "流程定义id不能为空") @PathVariable List<String> processDefinitionIds) {
        return toAjax(actProcessDefinitionService.deleteDeployment(deploymentIds, processDefinitionIds));
    }
    /**
     * æ¿€æ´»æˆ–者挂起流程定义
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/updateDefinitionState/{processDefinitionId}")
    public R<Void> updateDefinitionState(@NotBlank(message = "流程定义id不能为空") @PathVariable String processDefinitionId) {
        return toAjax(actProcessDefinitionService.updateDefinitionState(processDefinitionId));
    }
    /**
     * è¿ç§»æµç¨‹å®šä¹‰
     *
     * @param currentProcessDefinitionId å½“前流程定义id
     * @param fromProcessDefinitionId    éœ€è¦è¿ç§»åˆ°çš„æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/migrationDefinition/{currentProcessDefinitionId}/{fromProcessDefinitionId}")
    public R<Void> migrationDefinition(@NotBlank(message = "当前流程定义id") @PathVariable String currentProcessDefinitionId,
                                       @NotBlank(message = "需要迁移到的流程定义id") @PathVariable String fromProcessDefinitionId) {
        return toAjax(actProcessDefinitionService.migrationDefinition(currentProcessDefinitionId, fromProcessDefinitionId));
    }
    /**
     * æµç¨‹å®šä¹‰è½¬æ¢ä¸ºæ¨¡åž‹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/convertToModel/{processDefinitionId}")
    public R<Void> convertToModel(@NotEmpty(message = "流程定义id不能为空") @PathVariable String processDefinitionId) {
        return toAjax(actProcessDefinitionService.convertToModel(processDefinitionId));
    }
    /**
     * é€šè¿‡zip或xml部署流程定义
     *
     * @param file         æ–‡ä»¶
     * @param categoryCode åˆ†ç±»
     */
    @Log(title = "流程定义管理", businessType = BusinessType.INSERT)
    @PostMapping("/deployByFile")
    public void deployByFile(@RequestParam("file") MultipartFile file, @RequestParam("categoryCode") String categoryCode) {
        actProcessDefinitionService.deployByFile(file, categoryCode);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
package org.dromara.workflow.controller;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.bo.ProcessInstanceBo;
import org.dromara.workflow.domain.bo.ProcessInvalidBo;
import org.dromara.workflow.domain.bo.TaskUrgingBo;
import org.dromara.workflow.domain.vo.ActHistoryInfoVo;
import org.dromara.workflow.domain.vo.ProcessInstanceVo;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹å®žä¾‹ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/processInstance")
public class ActProcessInstanceController extends BaseController {
    private final IActProcessInstanceService actProcessInstanceService;
    /**
     * åˆ†é¡µæŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹
     *
     * @param bo å‚æ•°
     */
    @GetMapping("/getPageByRunning")
    public TableDataInfo<ProcessInstanceVo> getPageByRunning(ProcessInstanceBo bo, PageQuery pageQuery) {
        return actProcessInstanceService.getPageByRunning(bo, pageQuery);
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å·²ç»“束的流程实例
     *
     * @param bo å‚æ•°
     */
    @GetMapping("/getPageByFinish")
    public TableDataInfo<ProcessInstanceVo> getPageByFinish(ProcessInstanceBo bo, PageQuery pageQuery) {
        return actProcessInstanceService.getPageByFinish(bo, pageQuery);
    }
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @GetMapping("/getHistoryImage/{processInstanceId}")
    public R<String> getHistoryImage(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) {
        return R.ok("操作成功", actProcessInstanceService.getHistoryImage(processInstanceId));
    }
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图运行中,历史等节点
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @GetMapping("/getHistoryList/{processInstanceId}")
    public R<Map<String, Object>> getHistoryList(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) {
        return R.ok("操作成功", actProcessInstanceService.getHistoryList(processInstanceId));
    }
    /**
     * èŽ·å–å®¡æ‰¹è®°å½•
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @GetMapping("/getHistoryRecord/{processInstanceId}")
    public R<List<ActHistoryInfoVo>> getHistoryRecord(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) {
        return R.ok(actProcessInstanceService.getHistoryRecord(processInstanceId));
    }
    /**
     * ä½œåºŸæµç¨‹å®žä¾‹ï¼Œä¸ä¼šåˆ é™¤åŽ†å²è®°å½•(删除运行中的实例)
     *
     * @param processInvalidBo å‚æ•°
     */
    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
    @RepeatSubmit()
    @PostMapping("/deleteRunInstance")
    public R<Void> deleteRunInstance(@Validated(AddGroup.class) @RequestBody ProcessInvalidBo processInvalidBo) {
        return toAjax(actProcessInstanceService.deleteRunInstance(processInvalidBo));
    }
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     */
    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
    @RepeatSubmit()
    @DeleteMapping("/deleteRunAndHisInstance/{processInstanceIds}")
    public R<Void> deleteRunAndHisInstance(@NotNull(message = "流程实例id不能为空") @PathVariable String[] processInstanceIds) {
        return toAjax(actProcessInstanceService.deleteRunAndHisInstance(Arrays.asList(processInstanceIds)));
    }
    /**
     * å·²å®Œæˆçš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     */
    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
    @RepeatSubmit()
    @DeleteMapping("/deleteFinishAndHisInstance/{processInstanceIds}")
    public R<Void> deleteFinishAndHisInstance(@NotNull(message = "流程实例id不能为空") @PathVariable String[] processInstanceIds) {
        return toAjax(actProcessInstanceService.deleteFinishAndHisInstance(Arrays.asList(processInstanceIds)));
    }
    /**
     * æ’¤é”€æµç¨‹ç”³è¯·
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @Log(title = "流程实例管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/cancelProcessApply/{processInstanceId}")
    public R<Void> cancelProcessApply(@NotBlank(message = "流程实例id不能为空") @PathVariable String processInstanceId) {
        return toAjax(actProcessInstanceService.cancelProcessApply(processInstanceId));
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å½“前登录人单据
     *
     * @param bo å‚æ•°
     */
    @GetMapping("/getPageByCurrent")
    public TableDataInfo<ProcessInstanceVo> getPageByCurrent(ProcessInstanceBo bo, PageQuery pageQuery) {
        return actProcessInstanceService.getPageByCurrent(bo, pageQuery);
    }
    /**
     * ä»»åŠ¡å‚¬åŠž(给当前任务办理人发送站内信,邮件,短信等)
     *
     * @param taskUrgingBo ä»»åŠ¡å‚¬åŠž
     */
    @Log(title = "流程实例管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/taskUrging")
    public R<Void> taskUrging(@RequestBody TaskUrgingBo taskUrgingBo) {
        return toAjax(actProcessInstanceService.taskUrging(taskUrgingBo));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,295 @@
package org.dromara.workflow.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.WfTaskBackNode;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.TaskVo;
import org.dromara.workflow.domain.vo.VariableVo;
import org.dromara.workflow.service.IActTaskService;
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.engine.TaskService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
 * ä»»åŠ¡ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/task")
public class ActTaskController extends BaseController {
    private final IActTaskService actTaskService;
    private final TaskService taskService;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/startWorkFlow")
    public R<Map<String, Object>> startWorkFlow(@Validated(AddGroup.class) @RequestBody StartProcessBo startProcessBo) {
        Map<String, Object> map = actTaskService.startWorkFlow(startProcessBo);
        return R.ok("提交成功", map);
    }
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/completeTask")
    public R<Void> completeTask(@Validated(AddGroup.class) @RequestBody CompleteTaskBo completeTaskBo) {
        return toAjax(actTaskService.completeTask(completeTaskBo));
    }
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param taskBo å‚æ•°
     */
    @GetMapping("/getPageByTaskWait")
    public TableDataInfo<TaskVo> getPageByTaskWait(TaskBo taskBo, PageQuery pageQuery) {
        return actTaskService.getPageByTaskWait(taskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前租户所有待办任务
     *
     * @param taskBo å‚æ•°
     */
    @GetMapping("/getPageByAllTaskWait")
    public TableDataInfo<TaskVo> getPageByAllTaskWait(TaskBo taskBo, PageQuery pageQuery) {
        return actTaskService.getPageByAllTaskWait(taskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前用户的已办任务
     *
     * @param taskBo å‚æ•°
     */
    @GetMapping("/getPageByTaskFinish")
    public TableDataInfo<TaskVo> getPageByTaskFinish(TaskBo taskBo, PageQuery pageQuery) {
        return actTaskService.getPageByTaskFinish(taskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param taskBo å‚æ•°
     */
    @GetMapping("/getPageByTaskCopy")
    public TableDataInfo<TaskVo> getPageByTaskCopy(TaskBo taskBo, PageQuery pageQuery) {
        return actTaskService.getPageByTaskCopy(taskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前租户所有已办任务
     *
     * @param taskBo å‚æ•°
     */
    @GetMapping("/getPageByAllTaskFinish")
    public TableDataInfo<TaskVo> getPageByAllTaskFinish(TaskBo taskBo, PageQuery pageQuery) {
        return actTaskService.getPageByAllTaskFinish(taskBo, pageQuery);
    }
    /**
     * ç­¾æ”¶ï¼ˆæ‹¾å–)任务
     *
     * @param taskId ä»»åŠ¡id
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/claim/{taskId}")
    public R<Void> claimTask(@NotBlank(message = "任务id不能为空") @PathVariable String taskId) {
        try {
            taskService.claim(taskId, Convert.toStr(LoginHelper.getUserId()));
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("签收任务失败:" + e.getMessage());
        }
    }
    /**
     * å½’还(拾取的)任务
     *
     * @param taskId ä»»åŠ¡id
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/returnTask/{taskId}")
    public R<Void> returnTask(@NotBlank(message = "任务id不能为空") @PathVariable String taskId) {
        try {
            taskService.setAssignee(taskId, null);
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("归还任务失败:" + e.getMessage());
        }
    }
    /**
     * å§”派任务
     *
     * @param delegateBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/delegateTask")
    public R<Void> delegateTask(@Validated({AddGroup.class}) @RequestBody DelegateBo delegateBo) {
        return toAjax(actTaskService.delegateTask(delegateBo));
    }
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param terminationBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.DELETE)
    @RepeatSubmit()
    @PostMapping("/terminationTask")
    public R<Void> terminationTask(@RequestBody TerminationBo terminationBo) {
        return toAjax(actTaskService.terminationTask(terminationBo));
    }
    /**
     * è½¬åŠžä»»åŠ¡
     *
     * @param transmitBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/transferTask")
    public R<Void> transferTask(@Validated({AddGroup.class}) @RequestBody TransmitBo transmitBo) {
        return toAjax(actTaskService.transferTask(transmitBo));
    }
    /**
     * ä¼šç­¾ä»»åŠ¡åŠ ç­¾
     *
     * @param addMultiBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/addMultiInstanceExecution")
    public R<Void> addMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody AddMultiBo addMultiBo) {
        return toAjax(actTaskService.addMultiInstanceExecution(addMultiBo));
    }
    /**
     * ä¼šç­¾ä»»åŠ¡å‡ç­¾
     *
     * @param deleteMultiBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/deleteMultiInstanceExecution")
    public R<Void> deleteMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody DeleteMultiBo deleteMultiBo) {
        return toAjax(actTaskService.deleteMultiInstanceExecution(deleteMultiBo));
    }
    /**
     * é©³å›žå®¡æ‰¹
     *
     * @param backProcessBo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/backProcess")
    public R<String> backProcess(@Validated({AddGroup.class}) @RequestBody BackProcessBo backProcessBo) {
        return R.ok(actTaskService.backProcess(backProcessBo));
    }
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/getTaskById/{taskId}")
    public R<TaskVo> getTaskById(@PathVariable String taskId) {
        return R.ok(QueryUtils.getTask(taskId));
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
     *
     * @param taskIds ä»»åŠ¡id
     * @param userId  åŠžç†äººid
     */
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/updateAssignee/{taskIds}/{userId}")
    public R<Void> updateAssignee(@PathVariable String[] taskIds, @PathVariable String userId) {
        return toAjax(actTaskService.updateAssignee(taskIds, userId));
    }
    /**
     * æŸ¥è¯¢æµç¨‹å˜é‡
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/getInstanceVariable/{taskId}")
    public R<List<VariableVo>> getProcessInstVariable(@PathVariable String taskId) {
        return R.ok(actTaskService.getInstanceVariable(taskId));
    }
    /**
     * èŽ·å–å¯é©³å›žå¾—ä»»åŠ¡èŠ‚ç‚¹
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @GetMapping("/getTaskNodeList/{processInstanceId}")
    public R<List<WfTaskBackNode>> getNodeList(@PathVariable String processInstanceId) {
        return R.ok(CollUtil.reverse(wfTaskBackNodeService.getListByInstanceId(processInstanceId)));
    }
    /**
     * æŸ¥è¯¢å·¥ä½œæµä»»åŠ¡ç”¨æˆ·é€‰æ‹©åŠ ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/getTaskUserIdsByAddMultiInstance/{taskId}")
    public R<String> getTaskUserIdsByAddMultiInstance(@PathVariable String taskId) {
        return R.ok(actTaskService.getTaskUserIdsByAddMultiInstance(taskId));
    }
    /**
     * æŸ¥è¯¢å·¥ä½œæµé€‰æ‹©å‡ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/getListByDeleteMultiInstance/{taskId}")
    public R<List<TaskVo>> getListByDeleteMultiInstance(@PathVariable String taskId) {
        return R.ok(actTaskService.getListByDeleteMultiInstance(taskId));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package org.dromara.workflow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import org.dromara.workflow.service.ITestLeaveService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * è¯·å‡
 *
 * @author may
 * @date 2023-07-21
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/leave")
public class TestLeaveController extends BaseController {
    private final ITestLeaveService testLeaveService;
    /**
     * æŸ¥è¯¢è¯·å‡åˆ—表
     */
    @SaCheckPermission("demo:leave:list")
    @GetMapping("/list")
    public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
        return testLeaveService.queryPageList(bo, pageQuery);
    }
    /**
     * å¯¼å‡ºè¯·å‡åˆ—表
     */
    @SaCheckPermission("demo:leave:export")
    @Log(title = "请假", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(TestLeaveBo bo, HttpServletResponse response) {
        List<TestLeaveVo> list = testLeaveService.queryList(bo);
        ExcelUtil.exportExcel(list, "请假", TestLeaveVo.class, response);
    }
    /**
     * èŽ·å–è¯·å‡è¯¦ç»†ä¿¡æ¯
     *
     * @param id ä¸»é”®
     */
    @SaCheckPermission("demo:leave:query")
    @GetMapping("/{id}")
    public R<TestLeaveVo> getInfo(@NotNull(message = "主键不能为空")
                                  @PathVariable Long id) {
        return R.ok(testLeaveService.queryById(id));
    }
    /**
     * æ–°å¢žè¯·å‡
     */
    @SaCheckPermission("demo:leave:add")
    @Log(title = "请假", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping()
    public R<TestLeaveVo> add(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) {
        return R.ok(testLeaveService.insertByBo(bo));
    }
    /**
     * ä¿®æ”¹è¯·å‡
     */
    @SaCheckPermission("demo:leave:edit")
    @Log(title = "请假", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<TestLeaveVo> edit(@Validated(EditGroup.class) @RequestBody TestLeaveBo bo) {
        return R.ok(testLeaveService.updateByBo(bo));
    }
    /**
     * åˆ é™¤è¯·å‡
     *
     * @param ids ä¸»é”®ä¸²
     */
    @SaCheckPermission("demo:leave:remove")
    @Log(title = "请假", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        return toAjax(testLeaveService.deleteWithValidByIds(List.of(ids)));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package org.dromara.workflow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
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.web.core.BaseController;
import org.dromara.workflow.domain.bo.WfCategoryBo;
import org.dromara.workflow.domain.vo.WfCategoryVo;
import org.dromara.workflow.service.IWfCategoryService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»
 *
 * @author may
 * @date 2023-06-28
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/category")
public class WfCategoryController extends BaseController {
    private final IWfCategoryService wfCategoryService;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
     */
    @SaCheckPermission("workflow:category:list")
    @GetMapping("/list")
    public R<List<WfCategoryVo>> list(WfCategoryBo bo) {
        List<WfCategoryVo> list = wfCategoryService.queryList(bo);
        return R.ok(list);
    }
    /**
     * å¯¼å‡ºæµç¨‹åˆ†ç±»åˆ—表
     */
    @SaCheckPermission("workflow:category:export")
    @Log(title = "流程分类", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(WfCategoryBo bo, HttpServletResponse response) {
        List<WfCategoryVo> list = wfCategoryService.queryList(bo);
        ExcelUtil.exportExcel(list, "流程分类", WfCategoryVo.class, response);
    }
    /**
     * èŽ·å–æµç¨‹åˆ†ç±»è¯¦ç»†ä¿¡æ¯
     *
     * @param id ä¸»é”®
     */
    @SaCheckPermission("workflow:category:query")
    @GetMapping("/{id}")
    public R<WfCategoryVo> getInfo(@NotNull(message = "主键不能为空")
                                   @PathVariable Long id) {
        return R.ok(wfCategoryService.queryById(id));
    }
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     */
    @SaCheckPermission("workflow:category:add")
    @Log(title = "流程分类", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody WfCategoryBo bo) {
        return toAjax(wfCategoryService.insertByBo(bo));
    }
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     */
    @SaCheckPermission("workflow:category:edit")
    @Log(title = "流程分类", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody WfCategoryBo bo) {
        return toAjax(wfCategoryService.updateByBo(bo));
    }
    /**
     * åˆ é™¤æµç¨‹åˆ†ç±»
     *
     * @param ids ä¸»é”®ä¸²
     */
    @SaCheckPermission("workflow:category:remove")
    @Log(title = "流程分类", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        return toAjax(wfCategoryService.deleteWithValidByIds(List.of(ids), true));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfDefinitionConfigController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package org.dromara.workflow.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.validation.constraints.*;
import org.dromara.workflow.domain.bo.WfDefinitionConfigBo;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.workflow.service.IWfDefinitionConfigService;
/**
 * æµç¨‹å®šä¹‰é…ç½®
 *
 * @author may
 * @date 2024-03-18
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/definitionConfig")
public class WfDefinitionConfigController extends BaseController {
    private final IWfDefinitionConfigService wfDefinitionConfigService;
    /**
     * èŽ·å–æµç¨‹å®šä¹‰é…ç½®è¯¦ç»†ä¿¡æ¯
     *
     * @param definitionId ä¸»é”®
     */
    @GetMapping("/getByDefId/{definitionId}")
    public R<WfDefinitionConfigVo> getByDefId(@NotBlank(message = "流程定义ID不能为空")
                                              @PathVariable String definitionId) {
        return R.ok(wfDefinitionConfigService.getByDefId(definitionId));
    }
    /**
     * æ–°å¢žæµç¨‹å®šä¹‰é…ç½®
     */
    @Log(title = "流程定义配置", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/saveOrUpdate")
    public R<Void> saveOrUpdate(@Validated(AddGroup.class) @RequestBody WfDefinitionConfigBo bo) {
        return toAjax(wfDefinitionConfigService.saveOrUpdate(bo));
    }
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰é…ç½®
     *
     * @param ids ä¸»é”®ä¸²
     */
    @Log(title = "流程定义配置", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        return toAjax(wfDefinitionConfigService.deleteByIds(List.of(ids)));
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®æŽ’除当前查询的流程定义
     *
     * @param tableName    è¡¨å
     * @param definitionId æµç¨‹å®šä¹‰id
     */
    @GetMapping("/getByTableNameNotDefId/{tableName}/{definitionId}")
    public R<List<WfDefinitionConfigVo>> getByTableNameNotDefId(@NotBlank(message = "表名不能为空") @PathVariable String tableName,
                                                                @NotBlank(message = "流程定义ID不能为空") @PathVariable String definitionId) {
        return R.ok(wfDefinitionConfigService.getByTableNameNotDefId(tableName, definitionId));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfFormManageController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
package org.dromara.workflow.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.workflow.domain.vo.WfFormManageVo;
import org.dromara.workflow.domain.bo.WfFormManageBo;
import org.dromara.workflow.service.IWfFormManageService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
 * è¡¨å•管理
 *
 * @author may
 * @date 2024-03-29
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/formManage")
public class WfFormManageController extends BaseController {
    private final IWfFormManageService wfFormManageService;
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     */
    @SaCheckPermission("workflow:formManage:list")
    @GetMapping("/list")
    public TableDataInfo<WfFormManageVo> list(WfFormManageBo bo, PageQuery pageQuery) {
        return wfFormManageService.queryPageList(bo, pageQuery);
    }
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     */
    @SaCheckPermission("workflow:formManage:list")
    @GetMapping("/list/selectList")
    public R<List<WfFormManageVo>> selectList() {
        return R.ok(wfFormManageService.selectList());
    }
    /**
     * å¯¼å‡ºè¡¨å•管理列表
     */
    @SaCheckPermission("workflow:formManage:export")
    @Log(title = "表单管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(WfFormManageBo bo, HttpServletResponse response) {
        List<WfFormManageVo> list = wfFormManageService.queryList(bo);
        ExcelUtil.exportExcel(list, "表单管理", WfFormManageVo.class, response);
    }
    /**
     * èŽ·å–è¡¨å•ç®¡ç†è¯¦ç»†ä¿¡æ¯
     *
     * @param id ä¸»é”®
     */
    @SaCheckPermission("workflow:formManage:query")
    @GetMapping("/{id}")
    public R<WfFormManageVo> getInfo(@NotNull(message = "主键不能为空")
                                     @PathVariable Long id) {
        return R.ok(wfFormManageService.queryById(id));
    }
    /**
     * æ–°å¢žè¡¨å•管理
     */
    @SaCheckPermission("workflow:formManage:add")
    @Log(title = "表单管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody WfFormManageBo bo) {
        return toAjax(wfFormManageService.insertByBo(bo));
    }
    /**
     * ä¿®æ”¹è¡¨å•管理
     */
    @SaCheckPermission("workflow:formManage:edit")
    @Log(title = "表单管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody WfFormManageBo bo) {
        return toAjax(wfFormManageService.updateByBo(bo));
    }
    /**
     * åˆ é™¤è¡¨å•管理
     *
     * @param ids ä¸»é”®ä¸²
     */
    @SaCheckPermission("workflow:formManage:remove")
    @Log(title = "表单管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        return toAjax(wfFormManageService.deleteByIds(List.of(ids)));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,152 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * æµç¨‹å®žä¾‹å¯¹è±¡ act_hi_procinst
 *
 * @author may
 * @date 2023-07-22
 */
@Data
@TableName("act_hi_procinst")
public class ActHiProcinst implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId(value = "ID_")
    private String id;
    /**
     *
     */
    @TableField(value = "REV_")
    private Long rev;
    /**
     *
     */
    @TableField(value = "PROC_INST_ID_")
    private String procInstId;
    /**
     *
     */
    @TableField(value = "BUSINESS_KEY_")
    private String businessKey;
    /**
     *
     */
    @TableField(value = "PROC_DEF_ID_")
    private String procDefId;
    /**
     *
     */
    @TableField(value = "START_TIME_")
    private Date startTime;
    /**
     *
     */
    @TableField(value = "END_TIME_")
    private Date endTime;
    /**
     *
     */
    @TableField(value = "DURATION_")
    private Long duration;
    /**
     *
     */
    @TableField(value = "START_USER_ID_")
    private String startUserId;
    /**
     *
     */
    @TableField(value = "START_ACT_ID_")
    private String startActId;
    /**
     *
     */
    @TableField(value = "END_ACT_ID_")
    private String endActId;
    /**
     *
     */
    @TableField(value = "SUPER_PROCESS_INSTANCE_ID_")
    private String superProcessInstanceId;
    /**
     *
     */
    @TableField(value = "DELETE_REASON_")
    private String deleteReason;
    /**
     *
     */
    @TableField(value = "TENANT_ID_")
    private String tenantId;
    /**
     *
     */
    @TableField(value = "NAME_")
    private String name;
    /**
     *
     */
    @TableField(value = "CALLBACK_ID_")
    private String callbackId;
    /**
     *
     */
    @TableField(value = "CALLBACK_TYPE_")
    private String callbackType;
    /**
     *
     */
    @TableField(value = "REFERENCE_ID_")
    private String referenceId;
    /**
     *
     */
    @TableField(value = "REFERENCE_TYPE_")
    private String referenceType;
    /**
     *
     */
    @TableField(value = "PROPAGATED_STAGE_INST_ID_")
    private String propagatedStageInstId;
    /**
     *
     */
    @TableField(value = "BUSINESS_STATUS_")
    private String businessStatus;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.io.Serial;
/**
 * æµç¨‹åŽ†å²ä»»åŠ¡å¯¹è±¡ act_hi_taskinst
 *
 * @author may
 * @date 2024-03-02
 */
@Data
@TableName("act_hi_taskinst")
public class ActHiTaskinst implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId(value = "ID_")
    private String id;
    /**
     * ç‰ˆæœ¬
     */
    @TableField(value = "REV_")
    private Long rev;
    /**
     * æµç¨‹å®šä¹‰id
     */
    @TableField(value = "PROC_DEF_ID_")
    private String procDefId;
    /**
     *
     */
    @TableField(value = "TASK_DEF_ID_")
    private String taskDefId;
    /**
     * ä»»åŠ¡èŠ‚ç‚¹id
     */
    @TableField(value = "TASK_DEF_KEY_")
    private String taskDefKey;
    /**
     * æµç¨‹å®žä¾‹id
     */
    @TableField(value = "PROC_INST_ID_")
    private String procInstId;
    /**
     * æµç¨‹æ‰§è¡Œid
     */
    @TableField(value = "EXECUTION_ID")
    private String executionId;
    /**
     *
     */
    @TableField(value = "SCOPE_ID_")
    private String scopeId;
    /**
     *
     */
    @TableField(value = "SUB_SCOPE_ID_")
    private String subScopeId;
    /**
     * å…ˆç”¨å½“前字段标识抄送类型
     */
    @TableField(value = "SCOPE_TYPE_")
    private String scopeType;
    /**
     *
     */
    @TableField(value = "SCOPE_DEFINITION_ID_")
    private String scopeDefinitionId;
    /**
     *
     */
    @TableField(value = "PROPAGATED_STAGE_INST_ID_")
    private String propagatedStageInstId;
    /**
     * ä»»åŠ¡åç§°
     */
    @TableField(value = "NAME_")
    private String name;
    /**
     * çˆ¶çº§id
     */
    @TableField(value = "PARENT_TASK_ID_")
    private String parentTaskId;
    /**
     * æè¿°
     */
    @TableField(value = "DESCRIPTION_")
    private String description;
    /**
     * åŠžç†äºº
     */
    @TableField(value = "OWNER_")
    private String owner;
    /**
     * åŠžç†äºº
     */
    @TableField(value = "ASSIGNEE_")
    private String assignee;
    /**
     * å¼€å§‹äº‹ä»¶
     */
    @TableField(value = "START_TIME_")
    private Date startTime;
    /**
     * è®¤é¢†æ—¶é—´
     */
    @TableField(value = "CLAIM_TIME_")
    private Date claimTime;
    /**
     * ç»“束时间
     */
    @TableField(value = "END_TIME_")
    private Date endTime;
    /**
     * æŒç»­æ—¶é—´
     */
    @TableField(value = "DURATION_")
    private Long duration;
    /**
     * åˆ é™¤åŽŸå› 
     */
    @TableField(value = "DELETE_REASON_")
    private String deleteReason;
    /**
     * ä¼˜å…ˆçº§
     */
    @TableField(value = "PRIORITY_")
    private Long priority;
    /**
     * åˆ°æœŸæ—¶é—´
     */
    @TableField(value = "DUE_DATE_")
    private Date dueDate;
    /**
     *
     */
    @TableField(value = "FORM_KEY_")
    private String formKey;
    /**
     * åˆ†ç±»
     */
    @TableField(value = "CATEGORY_")
    private String category;
    /**
     * æœ€åŽä¿®æ”¹æ—¶é—´
     */
    @TableField(value = "LAST_UPDATED_TIME_")
    private Date lastUpdatedTime;
    /**
     * ç§Ÿæˆ·id
     */
    @TableField(value = "TENANT_ID_")
    private String tenantId;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
import java.util.Date;
/**
 * è¯·å‡å¯¹è±¡ test_leave
 *
 * @author may
 * @date 2023-07-21
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("test_leave")
public class TestLeave extends BaseEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * è¯·å‡ç±»åž‹
     */
    private String leaveType;
    /**
     * å¼€å§‹æ—¶é—´
     */
    private Date startDate;
    /**
     * ç»“束时间
     */
    private Date endDate;
    /**
     * è¯·å‡å¤©æ•°
     */
    private Integer leaveDays;
    /**
     * è¯·å‡åŽŸå› 
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
/**
 * æµç¨‹åˆ†ç±»å¯¹è±¡ wf_category
 *
 * @author may
 * @date 2023-06-27
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_category")
public class WfCategory extends TenantEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * åˆ†ç±»åç§°
     */
    private String categoryName;
    /**
     * åˆ†ç±»ç¼–码
     */
    private String categoryCode;
    /**
     * çˆ¶çº§id
     */
    private Long parentId;
    /**
     * æŽ’序
     */
    private Long sortNum;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfDefinitionConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package org.dromara.workflow.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
 * æµç¨‹å®šä¹‰é…ç½®å¯¹è±¡ wf_definition_config
 *
 * @author may
 * @date 2024-03-18
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_definition_config")
public class WfDefinitionConfig extends BaseEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * è¡¨å
     */
    private String tableName;
    /**
     * æµç¨‹å®šä¹‰ID
     */
    private String definitionId;
    /**
     * æµç¨‹KEY
     */
    private String processKey;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    private Integer version;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfFormManage.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.workflow.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
 * è¡¨å•管理对象 wf_form_manage
 *
 * @author may
 * @date 2024-03-29
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_form_manage")
public class WfFormManage extends TenantEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * è¡¨å•名称
     */
    private String formName;
    /**
     * è¡¨å•类型
     */
    private String formType;
    /**
     * è·¯ç”±åœ°å€/表单ID
     */
    private String router;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfNodeConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package org.dromara.workflow.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
 * èŠ‚ç‚¹é…ç½®å¯¹è±¡ wf_node_config
 *
 * @author may
 * @date 2024-03-30
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_node_config")
public class WfNodeConfig extends TenantEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * è¡¨å•id
     */
    private Long formId;
    /**
     * è¡¨å•类型
     */
    private String formType;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * èŠ‚ç‚¹id
     */
    private String nodeId;
    /**
     * æµç¨‹å®šä¹‰id
     */
    private String definitionId;
    /**
     * æ˜¯å¦ä¸ºç”³è¯·äººèŠ‚ç‚¹ ï¼ˆ0是 1否)
     */
    private String applyUserTask;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfTaskBackNode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
/**
 * èŠ‚ç‚¹é©³å›žè®°å½• wf_task_back_node
 *
 * @author may
 * @date 2024-03-13
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_task_back_node")
public class WfTaskBackNode extends TenantEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * å®žä¾‹id
     */
    private String instanceId;
    /**
     * èŠ‚ç‚¹id
     */
    private String nodeId;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * æŽ’序
     */
    private Integer orderNo;
    /**
     * èŠ‚ç‚¹ç±»åž‹
     */
    private String taskType;
    /**
     * åŠžç†äºº
     */
    private String assignee;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * åŠ ç­¾å‚æ•°è¯·æ±‚
 *
 * @author may
 */
@Data
public class AddMultiBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ID
     */
    @NotBlank(message = "任务ID不能为空", groups = AddGroup.class)
    private String taskId;
    /**
     * åŠ ç­¾äººå‘˜id
     */
    @NotEmpty(message = "加签人员不能为空", groups = AddGroup.class)
    private List<Long> assignees;
    /**
     * åŠ ç­¾äººå‘˜åç§°
     */
    @NotEmpty(message = "加签人员不能为空", groups = AddGroup.class)
    private List<String> assigneeNames;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * é©³å›žå‚数请求
 *
 * @author may
 */
@Data
public class BackProcessBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ID
     */
    @NotBlank(message = "任务ID不能为空", groups = AddGroup.class)
    private String taskId;
    /**
     * æ¶ˆæ¯ç±»åž‹
     */
    private List<String> messageType;
    /**
     * é©³å›žçš„节点id(目前未使用,直接驳回到申请人)
     */
    @NotBlank(message = "驳回的节点不能为空", groups = AddGroup.class)
    private String targetActivityId;
    /**
     * åŠžç†æ„è§
     */
    private String message;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.workflow.domain.vo.WfCopy;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
 * åŠžç†ä»»åŠ¡è¯·æ±‚å¯¹è±¡
 *
 * @author may
 */
@Data
public class CompleteTaskBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id不能为空", groups = {AddGroup.class})
    private String taskId;
    /**
     * é™„ä»¶id
     */
    private String fileId;
    /**
     * æŠ„送人员
     */
    private List<WfCopy> wfCopyList;
    /**
     * æ¶ˆæ¯ç±»åž‹
     */
    private List<String> messageType;
    /**
     * åŠžç†æ„è§
     */
    private String message;
    /**
     * æµç¨‹å˜é‡
     */
    private Map<String, Object> variables;
    public Map<String, Object> getVariables() {
        if (variables == null) {
            return new HashMap<>(16);
        }
        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        return variables;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
/**
 * å§”派任务请求对象
 *
 * @author may
 */
@Data
public class DelegateBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * å§”派人id
     */
    @NotBlank(message = "委派人id不能为空", groups = {AddGroup.class})
    private String userId;
    /**
     * å§”派人名称
     */
    @NotBlank(message = "委派人名称不能为空", groups = {AddGroup.class})
    private String nickName;
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id不能为空", groups = {AddGroup.class})
    private String taskId;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * å‡ç­¾å‚数请求
 *
 * @author may
 */
@Data
public class DeleteMultiBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ID
     */
    @NotBlank(message = "任务ID不能为空", groups = AddGroup.class)
    private String taskId;
    /**
     * å‡ç­¾äººå‘˜
     */
    @NotEmpty(message = "减签人员不能为空", groups = AddGroup.class)
    private List<String> taskIds;
    /**
     * æ‰§è¡Œid
     */
    @NotEmpty(message = "执行id不能为空", groups = AddGroup.class)
    private List<String> executionIds;
    /**
     * äººå‘˜id
     */
    @NotEmpty(message = "减签人员id不能为空", groups = AddGroup.class)
    private List<Long> assigneeIds;
    /**
     * äººå‘˜åç§°
     */
    @NotEmpty(message = "减签人员不能为空", groups = AddGroup.class)
    private List<String> assigneeNames;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.workflow.common.constant.FlowConstant;
import java.io.Serial;
import java.io.Serializable;
/**
 * æ¨¡åž‹è¯·æ±‚对象
 *
 * @author may
 */
@Data
public class ModelBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æ¨¡åž‹id
     */
    @NotBlank(message = "模型ID不能为空", groups = {EditGroup.class})
    private String id;
    /**
     * æ¨¡åž‹åç§°
     */
    @NotBlank(message = "模型名称不能为空", groups = {AddGroup.class})
    private String name;
    /**
     * æ¨¡åž‹æ ‡è¯†key
     */
    @NotBlank(message = "模型标识key不能为空", groups = {AddGroup.class})
    @Pattern(regexp = FlowConstant.MODEL_KEY_PATTERN, message = "模型标识key只能字符或者下划线开头", groups = {AddGroup.class})
    private String key;
    /**
     * æ¨¡åž‹åˆ†ç±»
     */
    @NotBlank(message = "模型分类不能为空", groups = {AddGroup.class})
    private String categoryCode;
    /**
     * æ¨¡åž‹XML
     */
    @NotBlank(message = "模型XML不能为空", groups = {AddGroup.class})
    private String xml;
    /**
     * æ¨¡åž‹SVG图片
     */
    @NotBlank(message = "模型SVG不能为空", groups = {EditGroup.class})
    private String svg;
    /**
     * å¤‡æ³¨
     */
    private String description;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹å®šä¹‰è¯·æ±‚对象
 *
 * @author may
 */
@Data
public class ProcessDefinitionBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰åç§°key
     */
    private String key;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String name;
    /**
     * æ¨¡åž‹åˆ†ç±»
     */
    private String categoryCode;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹å®žä¾‹è¯·æ±‚对象
 *
 * @author may
 */
@Data
public class ProcessInstanceBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹åç§°
     */
    private String name;
    /**
     * æµç¨‹key
     */
    private String key;
    /**
     * ä»»åŠ¡å‘èµ·äºº
     */
    private String startUserId;
    /**
     * ä¸šåŠ¡id
     */
    private String businessKey;
    /**
     * æ¨¡åž‹åˆ†ç±»
     */
    private String categoryCode;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹å®žä¾‹ä½œåºŸè¯·æ±‚对象
 *
 * @author may
 */
@Data
public class ProcessInvalidBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹id
     */
    @NotBlank(message = "流程实例id不能为空", groups = {AddGroup.class})
    private String processInstanceId;
    /**
     * ä½œåºŸåŽŸå› 
     */
    private String deleteReason;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
 * å¯åŠ¨æµç¨‹å¯¹è±¡
 *
 * @author may
 */
@Data
public class StartProcessBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸šåС唝䏀值id
     */
    @NotBlank(message = "业务ID不能为空", groups = {AddGroup.class})
    private String businessKey;
    /**
     * è¡¨å
     */
    @NotBlank(message = "表名不能为空", groups = {AddGroup.class})
    private String tableName;
    /**
     * æµç¨‹å˜é‡ï¼Œå‰ç«¯ä¼šæäº¤ä¸€ä¸ªå…ƒç´ {'entity': {业务详情数据对象}}
     */
    private Map<String, Object> variables;
    public Map<String, Object> getVariables() {
        if (variables == null) {
            return new HashMap<>(16);
        }
        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        return variables;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * ç”¨æˆ·åŠ ç­¾æŸ¥è¯¢
 *
 * @author may
 */
@Data
public class SysUserMultiBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * äººå‘˜åç§°
     */
    private String userName;
    /**
     * äººå‘˜åç§°
     */
    private String nickName;
    /**
     * éƒ¨é—¨id
     */
    private String deptId;
    /**
     * ä»»åŠ¡id
     */
    private String taskId;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * ä»»åŠ¡è¯·æ±‚å¯¹è±¡
 *
 * @author may
 */
@Data
public class TaskBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡åç§°
     */
    private String name;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String processDefinitionName;
    /**
     * æµç¨‹å®šä¹‰key
     */
    private String processDefinitionKey;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * ä»»åŠ¡å‚¬åŠž
 *
 * @author may
 */
@Data
public class TaskUrgingBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private String processInstanceId;
    /**
     * æ¶ˆæ¯ç±»åž‹
     */
    private List<String> messageType;
    /**
     * å‚¬åŠžå†…å®¹ï¼ˆä¸ºç©ºé»˜è®¤ç³»ç»Ÿå†…ç½®ä¿¡æ¯ï¼‰
     */
    private String message;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
/**
 * ç»ˆæ­¢ä»»åŠ¡è¯·æ±‚å¯¹è±¡
 *
 * @author may
 */
@Data
public class TerminationBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id为空", groups = AddGroup.class)
    private String taskId;
    /**
     * å®¡æ‰¹æ„è§
     */
    private String comment;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package org.dromara.workflow.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.workflow.domain.TestLeave;
import java.util.Date;
/**
 * è¯·å‡ä¸šåŠ¡å¯¹è±¡ test_leave
 *
 * @author may
 * @date 2023-07-21
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = TestLeave.class, reverseConvertGenerate = false)
public class TestLeaveBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = {EditGroup.class})
    private Long id;
    /**
     * è¯·å‡ç±»åž‹
     */
    @NotBlank(message = "请假类型不能为空", groups = {AddGroup.class, EditGroup.class})
    private String leaveType;
    /**
     * å¼€å§‹æ—¶é—´
     */
    @NotNull(message = "开始时间不能为空", groups = {AddGroup.class, EditGroup.class})
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    /**
     * ç»“束时间
     */
    @NotNull(message = "结束时间不能为空", groups = {AddGroup.class, EditGroup.class})
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    /**
     * è¯·å‡å¤©æ•°
     */
    @NotNull(message = "请假天数不能为空", groups = {AddGroup.class, EditGroup.class})
    private Integer leaveDays;
    /**
     * å¼€å§‹æ—¶é—´
     */
    private Integer startLeaveDays;
    /**
     * ç»“束时间
     */
    private Integer endLeaveDays;
    /**
     * è¯·å‡åŽŸå› 
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
/**
 * ç»ˆè½¬åŠžåŠ¡è¯·æ±‚å¯¹è±¡
 *
 * @author may
 */
@Data
public class TransmitBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id为空", groups = AddGroup.class)
    private String taskId;
    /**
     * è½¬åŠžäººid
     */
    @NotBlank(message = "转办人不能为空", groups = AddGroup.class)
    private String userId;
    /**
     * å®¡æ‰¹æ„è§
     */
    private String comment;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package org.dromara.workflow.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.workflow.domain.WfCategory;
/**
 * æµç¨‹åˆ†ç±»ä¸šåŠ¡å¯¹è±¡ wf_category
 *
 * @author may
 * @date 2023-06-27
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = WfCategory.class, reverseConvertGenerate = false)
public class WfCategoryBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = {EditGroup.class})
    private Long id;
    /**
     * åˆ†ç±»åç§°
     */
    @NotBlank(message = "分类名称不能为空", groups = {AddGroup.class, EditGroup.class})
    private String categoryName;
    /**
     * åˆ†ç±»ç¼–码
     */
    @NotBlank(message = "分类编码不能为空", groups = {AddGroup.class, EditGroup.class})
    private String categoryCode;
    /**
     * çˆ¶çº§id
     */
    @NotNull(message = "父级id不能为空", groups = {AddGroup.class, EditGroup.class})
    private Long parentId;
    /**
     * æŽ’序
     */
    private Long sortNum;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfDefinitionConfigBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package org.dromara.workflow.domain.bo;
import org.dromara.workflow.domain.WfDefinitionConfig;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
 * æµç¨‹å®šä¹‰é…ç½®ä¸šåŠ¡å¯¹è±¡ wf_form_definition
 *
 * @author may
 * @date 2024-03-18
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = WfDefinitionConfig.class, reverseConvertGenerate = false)
public class WfDefinitionConfigBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = {EditGroup.class})
    private Long id;
    /**
     * è¡¨å
     */
    @NotBlank(message = "表名不能为空", groups = {AddGroup.class})
    private String tableName;
    /**
     * æµç¨‹å®šä¹‰ID
     */
    @NotBlank(message = "流程定义ID不能为空", groups = {AddGroup.class})
    private String definitionId;
    /**
     * æµç¨‹KEY
     */
    @NotBlank(message = "流程KEY不能为空", groups = {AddGroup.class})
    private String processKey;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @NotNull(message = "流程版本不能为空", groups = {AddGroup.class})
    private Integer version;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfFormManageBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package org.dromara.workflow.domain.bo;
import org.dromara.workflow.domain.WfFormManage;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
 * è¡¨å•管理业务对象 wf_form_manage
 *
 * @author may
 * @date 2024-03-29
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = WfFormManage.class, reverseConvertGenerate = false)
public class WfFormManageBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = { EditGroup.class })
    private Long id;
    /**
     * è¡¨å•名称
     */
    @NotBlank(message = "表单名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String formName;
    /**
     * è¡¨å•类型
     */
    @NotBlank(message = "表单类型不能为空", groups = { AddGroup.class, EditGroup.class })
    private String formType;
    /**
     * è·¯ç”±åœ°å€/表单ID
     */
    @NotBlank(message = "路由地址/表单ID不能为空", groups = { AddGroup.class, EditGroup.class })
    private String router;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfNodeConfigBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package org.dromara.workflow.domain.bo;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
 * èŠ‚ç‚¹é…ç½®ä¸šåŠ¡å¯¹è±¡ wf_node_config
 *
 * @author may
 * @date 2024-03-30
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = WfNodeConfig.class, reverseConvertGenerate = false)
public class WfNodeConfigBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = {EditGroup.class})
    private Long id;
    /**
     * è¡¨å•id
     */
    private Long formId;
    /**
     * è¡¨å•类型
     */
    private String formType;
    /**
     * èŠ‚ç‚¹åç§°
     */
    @NotBlank(message = "节点名称不能为空", groups = {AddGroup.class, EditGroup.class})
    private String nodeName;
    /**
     * èŠ‚ç‚¹id
     */
    @NotBlank(message = "节点id不能为空", groups = {AddGroup.class, EditGroup.class})
    private String nodeId;
    /**
     * æµç¨‹å®šä¹‰id
     */
    @NotBlank(message = "流程定义id不能为空", groups = {AddGroup.class, EditGroup.class})
    private String definitionId;
    /**
     * æ˜¯å¦ä¸ºç”³è¯·äººèŠ‚ç‚¹ ï¼ˆ0是 1否)
     */
    @NotBlank(message = "是否为申请人节点不能为空", groups = {AddGroup.class, EditGroup.class})
    private String applyUserTask;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.flowable.engine.task.Attachment;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
 * æµç¨‹å®¡æ‰¹è®°å½•视图
 *
 * @author may
 */
@Data
public class ActHistoryInfoVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    private String id;
    /**
     * èŠ‚ç‚¹id
     */
    private String taskDefinitionKey;
    /**
     * ä»»åŠ¡åç§°
     */
    private String name;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private String processInstanceId;
    /**
     * å¼€å§‹æ—¶é—´
     */
    private Date startTime;
    /**
     * ç»“束时间
     */
    private Date endTime;
    /**
     * è¿è¡Œæ—¶é•¿
     */
    private String runDuration;
    /**
     * çŠ¶æ€
     */
    private String status;
    /**
     * çŠ¶æ€
     */
    private String statusName;
    /**
     * åŠžç†äººid
     */
    private String assignee;
    /**
     * åŠžç†äººåç§°
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee")
    private String nickName;
    /**
     * åŠžç†äººid
     */
    private String owner;
    /**
     * å®¡æ‰¹ä¿¡æ¯id
     */
    private String commentId;
    /**
     * å®¡æ‰¹ä¿¡æ¯
     */
    private String comment;
    /**
     * å®¡æ‰¹é™„ä»¶
     */
    private List<Attachment> attachmentList;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * èŠ‚ç‚¹å›¾å½¢ä¿¡æ¯
 *
 * @author may
 */
@Data
public class GraphicInfoVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * x坐标
     */
    private double x;
    /**
     * y坐标
     */
    private double y;
    /**
     * èŠ‚ç‚¹é«˜åº¦
     */
    private double height;
    /**
     * èŠ‚ç‚¹å®½åº¦
     */
    private double width;
    /**
     * èŠ‚ç‚¹id
     */
    private String nodeId;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æ¨¡åž‹è§†å›¾å¯¹è±¡
 *
 * @author may
 */
@Data
public class ModelVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æ¨¡åž‹id
     */
    private String id;
    /**
     * æ¨¡åž‹åç§°
     */
    private String name;
    /**
     * æ¨¡åž‹æ ‡è¯†key
     */
    private String key;
    /**
     * æ¨¡åž‹åˆ†ç±»
     */
    private String categoryCode;
    /**
     * æ¨¡åž‹XML
     */
    private String xml;
    /**
     * å¤‡æ³¨
     */
    private String description;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * å¤šå®žä¾‹ä¿¡æ¯
 *
 * @author may
 */
@Data
public class MultiInstanceVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¼šç­¾ç±»åž‹ï¼ˆä¸²è¡Œï¼Œå¹¶è¡Œï¼‰
     */
    private Object type;
    /**
     * ä¼šç­¾äººå‘˜KEY
     */
    private String assignee;
    /**
     * ä¼šç­¾äººå‘˜é›†åˆKEY
     */
    private String assigneeList;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * å‚与者
 *
 * @author may
 */
@Data
public class ParticipantVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ç»„id(角色id)
     */
    private List<Long> groupIds;
    /**
     * å€™é€‰äººid(用户id) å½“组id不为空时,将组内人员查出放入candidate
     */
    private List<Long> candidate;
    /**
     * å€™é€‰äººåç§°ï¼ˆç”¨æˆ·åç§°ï¼‰ å½“组id不为空时,将组内人员查出放入candidateName
     */
    private List<String> candidateName;
    /**
     * æ˜¯å¦è®¤é¢†æ ‡è¯†
     * å½“为空时默认当前任务不需要认领
     * å½“为true时当前任务说明为候选模式并且有人已经认领了任务可以归还,
     * å½“为false时当前任务说明为候选模式该任务未认领,
     */
    private Boolean claim;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * æµç¨‹å®šä¹‰è§†å›¾
 *
 * @author may
 */
@Data
public class ProcessDefinitionVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰id
     */
    private String id;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String name;
    /**
     * æµç¨‹å®šä¹‰æ ‡è¯†key
     */
    private String key;
    /**
     * æµç¨‹å®šä¹‰ç‰ˆæœ¬
     */
    private int version;
    /**
     * æµç¨‹å®šä¹‰æŒ‚起或激活 1激活 2挂起
     */
    private int suspensionState;
    /**
     * æµç¨‹xml名称
     */
    private String resourceName;
    /**
     * æµç¨‹å›¾ç‰‡åç§°
     */
    private String diagramResourceName;
    /**
     * æµç¨‹éƒ¨ç½²id
     */
    private String deploymentId;
    /**
     * æµç¨‹éƒ¨ç½²æ—¶é—´
     */
    private Date deploymentTime;
    /**
     * æµç¨‹å®šä¹‰é…ç½®
     */
    private WfDefinitionConfigVo wfDefinitionConfigVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
 * æµç¨‹å®žä¾‹è§†å›¾
 *
 * @author may
 */
@Data
public class ProcessInstanceVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private String id;
    /**
     * æµç¨‹å®šä¹‰id
     */
    private String processDefinitionId;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String processDefinitionName;
    /**
     * æµç¨‹å®šä¹‰key
     */
    private String processDefinitionKey;
    /**
     * æµç¨‹å®šä¹‰ç‰ˆæœ¬
     */
    private Integer processDefinitionVersion;
    /**
     * éƒ¨ç½²id
     */
    private String deploymentId;
    /**
     * ä¸šåŠ¡id
     */
    private String businessKey;
    /**
     * æ˜¯å¦æŒ‚èµ·
     */
    private Boolean isSuspended;
    /**
     * ç§Ÿæˆ·id
     */
    private String tenantId;
    /**
     * å¯åŠ¨æ—¶é—´
     */
    private Date startTime;
    /**
     * ç»“束时间
     */
    private Date endTime;
    /**
     * å¯åŠ¨äººid
     */
    private String startUserId;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String businessStatus;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String businessStatusName;
    /**
     * å¾…办任务集合
     */
    private List<TaskVo> taskVoList;
    /**
     * èŠ‚ç‚¹é…ç½®
     */
    private WfNodeConfigVo wfNodeConfigVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,173 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * ä»»åŠ¡è§†å›¾
 *
 * @author may
 */
@Data
public class TaskVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    private String id;
    /**
     * ä»»åŠ¡åç§°
     */
    private String name;
    /**
     * æè¿°
     */
    private String description;
    /**
     * ä¼˜å…ˆçº§
     */
    private Integer priority;
    /**
     * è´Ÿè´£æ­¤ä»»åŠ¡çš„äººå‘˜çš„ç”¨æˆ·id
     */
    private String owner;
    /**
     * åŠžç†äººid
     */
    private Long assignee;
    /**
     * åŠžç†äºº
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee")
    private String assigneeName;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private String processInstanceId;
    /**
     * æ‰§è¡Œid
     */
    private String executionId;
    /**
     * æ— ç”¨
     */
    private String taskDefinitionId;
    /**
     * æµç¨‹å®šä¹‰id
     */
    private String processDefinitionId;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * å·²åŠžä»»åŠ¡-创建时间
     */
    private Date startTime;
    /**
     * ç»“束时间
     */
    private Date endTime;
    /**
     * èŠ‚ç‚¹id
     */
    private String taskDefinitionKey;
    /**
     * ä»»åŠ¡æˆªæ­¢æ—¥æœŸ
     */
    private Date dueDate;
    /**
     * æµç¨‹ç±»åˆ«
     */
    private String category;
    /**
     * çˆ¶çº§ä»»åŠ¡id
     */
    private String parentTaskId;
    /**
     * ç§Ÿæˆ·id
     */
    private String tenantId;
    /**
     * è®¤é¢†æ—¶é—´
     */
    private Date claimTime;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String businessStatus;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String businessStatusName;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String processDefinitionName;
    /**
     * æµç¨‹å®šä¹‰key
     */
    private String processDefinitionKey;
    /**
     * æµç¨‹å®šä¹‰ç‰ˆæœ¬
     */
    private Integer processDefinitionVersion;
    /**
     * å‚与者
     */
    private ParticipantVo participantVo;
    /**
     * æ˜¯å¦ä¼šç­¾
     */
    private Boolean multiInstance;
    /**
     * ä¸šåŠ¡id
     */
    private String businessKey;
    /**
     * æµç¨‹å®šä¹‰é…ç½®
     */
    private WfDefinitionConfigVo wfDefinitionConfigVo;
    /**
     * èŠ‚ç‚¹é…ç½®
     */
    private WfNodeConfigVo wfNodeConfigVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package org.dromara.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.workflow.domain.TestLeave;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * è¯·å‡è§†å›¾å¯¹è±¡ test_leave
 *
 * @author may
 * @date 2023-07-21
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = TestLeave.class)
public class TestLeaveVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * è¯·å‡ç±»åž‹
     */
    @ExcelProperty(value = "请假类型")
    private String leaveType;
    /**
     * å¼€å§‹æ—¶é—´
     */
    @ExcelProperty(value = "开始时间")
    private Date startDate;
    /**
     * ç»“束时间
     */
    @ExcelProperty(value = "结束时间")
    private Date endDate;
    /**
     * è¯·å‡å¤©æ•°
     */
    @ExcelProperty(value = "请假天数")
    private Integer leaveDays;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "请假原因")
    private String remark;
    /**
     * æµç¨‹å®žä¾‹å¯¹è±¡
     */
    private ProcessInstanceVo processInstanceVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/VariableVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹å˜é‡
 *
 * @author may
 */
@Data
public class VariableVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * å˜é‡key
     */
    private String key;
    /**
     * å˜é‡å€¼
     */
    private String value;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.dromara.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.workflow.domain.WfCategory;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹åˆ†ç±»è§†å›¾å¯¹è±¡ wf_category
 *
 * @author may
 * @date 2023-06-27
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = WfCategory.class)
public class WfCategoryVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * åˆ†ç±»åç§°
     */
    @ExcelProperty(value = "分类名称")
    private String categoryName;
    /**
     * åˆ†ç±»ç¼–码
     */
    @ExcelProperty(value = "分类编码")
    private String categoryCode;
    /**
     * çˆ¶çº§id
     */
    @ExcelProperty(value = "父级id")
    private Long parentId;
    /**
     * æŽ’序
     */
    @ExcelProperty(value = "排序")
    private Long sortNum;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æŠ„送
 *
 * @author may
 */
@Data
public class WfCopy implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ç”¨æˆ·id
     */
    private Long userId;
    /**
     * ç”¨æˆ·åç§°
     */
    private String userName;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfDefinitionConfigVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package org.dromara.workflow.domain.vo;
import org.dromara.workflow.domain.WfDefinitionConfig;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æµç¨‹å®šä¹‰é…ç½®è§†å›¾å¯¹è±¡ wf_definition_config
 *
 * @author may
 * @date 2024-03-18
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = WfDefinitionConfig.class)
public class WfDefinitionConfigVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * è¡¨å
     */
    @ExcelProperty(value = "表名")
    private String tableName;
    /**
     * æµç¨‹å®šä¹‰ID
     */
    @ExcelProperty(value = "流程定义ID")
    private String definitionId;
    /**
     * æµç¨‹KEY
     */
    @ExcelProperty(value = "流程KEY")
    private String processKey;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "流程版本")
    private Integer version;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
    /**
     * è¡¨å•管理
     */
    private WfFormManageVo wfFormManageVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfFormManageVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package org.dromara.workflow.domain.vo;
import org.dromara.workflow.domain.WfFormManage;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * è¡¨å•管理视图对象 wf_form_manage
 *
 * @author may
 * @date 2024-03-29
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = WfFormManage.class)
public class WfFormManageVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * è¡¨å•名称
     */
    @ExcelProperty(value = "表单名称")
    private String formName;
    /**
     * è¡¨å•类型
     */
    @ExcelProperty(value = "表单类型")
    private String formType;
    /**
     * è¡¨å•类型名称
     */
    private String formTypeName;
    /**
     * è·¯ç”±åœ°å€/表单ID
     */
    @ExcelProperty(value = "路由地址/表单ID")
    private String router;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfNodeConfigVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package org.dromara.workflow.domain.vo;
import org.dromara.workflow.domain.WfNodeConfig;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * èŠ‚ç‚¹é…ç½®è§†å›¾å¯¹è±¡ wf_node_config
 *
 * @author may
 * @date 2024-03-30
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = WfNodeConfig.class)
public class WfNodeConfigVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * è¡¨å•id
     */
    @ExcelProperty(value = "表单id")
    private Long formId;
    /**
     * è¡¨å•类型
     */
    @ExcelProperty(value = "表单类型")
    private String formType;
    /**
     * èŠ‚ç‚¹åç§°
     */
    @ExcelProperty(value = "节点名称")
    private String nodeName;
    /**
     * èŠ‚ç‚¹id
     */
    @ExcelProperty(value = "节点id")
    private String nodeId;
    /**
     * æµç¨‹å®šä¹‰id
     */
    @ExcelProperty(value = "流程定义id")
    private String definitionId;
    /**
     * æ˜¯å¦ä¸ºç”³è¯·äººèŠ‚ç‚¹ ï¼ˆ0是 1否)
     */
    @ExcelProperty(value = "是否为申请人节点 ï¼ˆ0是 1否)")
    private String applyUserTask;
    /**
     * è¡¨å•管理
     */
    private WfFormManageVo wfFormManageVo;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
package org.dromara.workflow.flowable;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
public class CustomDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
    //设置高亮线的颜色  è¿™é‡Œæˆ‘设置成绿色
    protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN;
    public CustomDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }
    /**
     * ç”»çº¿é¢œè‰²è®¾ç½®
     */
    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType,
                               AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(CONNECTION_COLOR);
        if (connectionType.equals("association")) {
            g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            //设置线的颜色
            g.setPaint(HIGHLIGHT_SEQUENCEFLOW_COLOR);
            g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }
        for (int i = 1; i < xPoints.length; i++) {
            Integer sourceX = xPoints[i - 1];
            Integer sourceY = yPoints[i - 1];
            Integer targetX = xPoints[i];
            Integer targetY = yPoints[i];
            Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
            g.draw(line);
        }
        if (isDefault) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }
        if (conditional) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }
        if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
            drawArrowHead(line, scaleFactor);
        }
        if (associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
            drawArrowHead(line, scaleFactor);
        }
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * é«˜äº®èŠ‚ç‚¹è®¾ç½®
     */
    public void drawHighLight(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        //设置高亮节点的颜色
        g.setPaint(HIGHLIGHT_COLOR);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * @description: é«˜äº®èŠ‚ç‚¹çº¢è‰²
     * @param: x
     * @param: y
     * @param: width
     * @param: height
     * @return: void
     * @author: gssong
     * @date: 2022/4/12
     */
    public void drawHighLightRed(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        //设置高亮节点的颜色
        g.setPaint(Color.green);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1120 @@
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.dromara.workflow.flowable;
import org.flowable.bpmn.model.Event;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.image.ProcessDiagramGenerator;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.List;
import java.util.*;
/**
 * Class to generate an image based the diagram interchange information in a BPMN 2.0 process.
 *
 * @author Joram Barrez
 * @author Tijs Rademakers
 * @author Zheng Ji
 */
public class CustomDefaultProcessDiagramGenerator implements ProcessDiagramGenerator {
    protected Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions = new HashMap<>();
    protected Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<>();
    public CustomDefaultProcessDiagramGenerator() {
        this(1.0);
    }
    // The instructions on how to draw a certain construct is
    // created statically and stored in a map for performance.
    public CustomDefaultProcessDiagramGenerator(final double scaleFactor) {
        // start event
        activityDrawInstructions.put(StartEvent.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                StartEvent startEvent = (StartEvent) flowNode;
                if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) {
                    EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0);
                    if (eventDefinition instanceof TimerEventDefinition) {
                        processDiagramCanvas.drawTimerStartEvent(graphicInfo, scaleFactor);
                    } else if (eventDefinition instanceof ErrorEventDefinition) {
                        processDiagramCanvas.drawErrorStartEvent(graphicInfo, scaleFactor);
                    } else if (eventDefinition instanceof EscalationEventDefinition) {
                        processDiagramCanvas.drawEscalationStartEvent(graphicInfo, scaleFactor);
                    } else if (eventDefinition instanceof ConditionalEventDefinition) {
                        processDiagramCanvas.drawConditionalStartEvent(graphicInfo, scaleFactor);
                    } else if (eventDefinition instanceof SignalEventDefinition) {
                        processDiagramCanvas.drawSignalStartEvent(graphicInfo, scaleFactor);
                    } else if (eventDefinition instanceof MessageEventDefinition) {
                        processDiagramCanvas.drawMessageStartEvent(graphicInfo, scaleFactor);
                    } else {
                        processDiagramCanvas.drawNoneStartEvent(graphicInfo);
                    }
                } else {
                    List<ExtensionElement> eventTypeElements = startEvent.getExtensionElements().get("eventType");
                    if (eventTypeElements != null && eventTypeElements.size() > 0) {
                        processDiagramCanvas.drawEventRegistryStartEvent(graphicInfo, scaleFactor);
                    } else {
                        processDiagramCanvas.drawNoneStartEvent(graphicInfo);
                    }
                }
            }
        });
        // signal catch
        activityDrawInstructions.put(IntermediateCatchEvent.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode;
                if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent.getEventDefinitions().isEmpty()) {
                    if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
                        processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
                        processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
                        processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof ConditionalEventDefinition) {
                        processDiagramCanvas.drawCatchingConditionalEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
                    }
                }
            }
        });
        // signal throw
        activityDrawInstructions.put(ThrowEvent.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                ThrowEvent throwEvent = (ThrowEvent) flowNode;
                if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) {
                    if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
                        processDiagramCanvas.drawThrowingSignalEvent(graphicInfo, scaleFactor);
                    } else if (throwEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) {
                        processDiagramCanvas.drawThrowingEscalationEvent(graphicInfo, scaleFactor);
                    } else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
                        processDiagramCanvas.drawThrowingCompensateEvent(graphicInfo, scaleFactor);
                    } else {
                        processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
                    }
                } else {
                    processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
                }
            }
        });
        // end event
        activityDrawInstructions.put(EndEvent.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                EndEvent endEvent = (EndEvent) flowNode;
                if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) {
                    if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
                        processDiagramCanvas.drawErrorEndEvent(flowNode.getName(), graphicInfo, scaleFactor);
                    } else if (endEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) {
                        processDiagramCanvas.drawEscalationEndEvent(flowNode.getName(), graphicInfo, scaleFactor);
                    } else {
                        processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
                    }
                } else {
                    processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
                }
            }
        });
        // task
        activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // user task
        activityDrawInstructions.put(UserTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawUserTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // script task
        activityDrawInstructions.put(ScriptTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawScriptTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // service task
        activityDrawInstructions.put(ServiceTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                ServiceTask serviceTask = (ServiceTask) flowNode;
                if ("camel".equalsIgnoreCase(serviceTask.getType())) {
                    processDiagramCanvas.drawCamelTask(serviceTask.getName(), graphicInfo, scaleFactor);
                }else if (ServiceTask.HTTP_TASK.equalsIgnoreCase(serviceTask.getType())) {
                    processDiagramCanvas.drawHttpTask(serviceTask.getName(), graphicInfo, scaleFactor);
                } else if (ServiceTask.DMN_TASK.equalsIgnoreCase(serviceTask.getType())) {
                    processDiagramCanvas.drawDMNTask(serviceTask.getName(), graphicInfo, scaleFactor);
                } else if (ServiceTask.SHELL_TASK.equalsIgnoreCase(serviceTask.getType())) {
                    processDiagramCanvas.drawShellTask(serviceTask.getName(), graphicInfo, scaleFactor);
                } else {
                    processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor);
                }
            }
        });
        // http service task
        activityDrawInstructions.put(HttpServiceTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawHttpTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // receive task
        activityDrawInstructions.put(ReceiveTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawReceiveTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // send task
        activityDrawInstructions.put(SendTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawSendTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // manual task
        activityDrawInstructions.put(ManualTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawManualTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // send event service task
        activityDrawInstructions.put(SendEventServiceTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawSendEventServiceTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // external worker service task
        activityDrawInstructions.put(ExternalWorkerServiceTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                ServiceTask serviceTask = (ServiceTask) flowNode;
                processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor);
            }
        });
        // case service task
        activityDrawInstructions.put(CaseServiceTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawCaseServiceTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // businessRuleTask task
        activityDrawInstructions.put(BusinessRuleTask.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawBusinessRuleTask(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // exclusive gateway
        activityDrawInstructions.put(ExclusiveGateway.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawExclusiveGateway(graphicInfo, scaleFactor);
            }
        });
        // inclusive gateway
        activityDrawInstructions.put(InclusiveGateway.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawInclusiveGateway(graphicInfo, scaleFactor);
            }
        });
        // parallel gateway
        activityDrawInstructions.put(ParallelGateway.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawParallelGateway(graphicInfo, scaleFactor);
            }
        });
        // event based gateway
        activityDrawInstructions.put(EventGateway.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawEventBasedGateway(graphicInfo, scaleFactor);
            }
        });
        // Boundary timer
        activityDrawInstructions.put(BoundaryEvent.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode;
                if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) {
                    EventDefinition eventDefinition = boundaryEvent.getEventDefinitions().get(0);
                    if (eventDefinition instanceof TimerEventDefinition) {
                        processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof ConditionalEventDefinition) {
                        processDiagramCanvas.drawCatchingConditionalEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof ErrorEventDefinition) {
                        processDiagramCanvas.drawCatchingErrorEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof EscalationEventDefinition) {
                        processDiagramCanvas.drawCatchingEscalationEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof SignalEventDefinition) {
                        processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof MessageEventDefinition) {
                        processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    } else if (eventDefinition instanceof CompensateEventDefinition) {
                        processDiagramCanvas.drawCatchingCompensateEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    }
                } else {
                    List<ExtensionElement> eventTypeElements = boundaryEvent.getExtensionElements().get("eventType");
                    if (eventTypeElements != null && eventTypeElements.size() > 0) {
                        processDiagramCanvas.drawCatchingEventRegistryEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
                    }
                }
            }
        });
        // subprocess
        activityDrawInstructions.put(SubProcess.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
                } else {
                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
                }
            }
        });
        // transaction
        activityDrawInstructions.put(Transaction.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
                } else {
                    processDiagramCanvas.drawExpandedTransaction(flowNode.getName(), graphicInfo, scaleFactor);
                }
            }
        });
        // Event subprocess
        activityDrawInstructions.put(EventSubProcess.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor);
                } else {
                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor);
                }
            }
        });
        // Adhoc subprocess
        activityDrawInstructions.put(AdhocSubProcess.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
                } else {
                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
                }
            }
        });
        // call activity
        activityDrawInstructions.put(CallActivity.class, new ActivityDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                processDiagramCanvas.drawCollapsedCallActivity(flowNode.getName(), graphicInfo, scaleFactor);
            }
        });
        // text annotation
        artifactDrawInstructions.put(TextAnnotation.class, new ArtifactDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
                TextAnnotation textAnnotation = (TextAnnotation) artifact;
                processDiagramCanvas.drawTextAnnotation(textAnnotation.getText(), graphicInfo, scaleFactor);
            }
        });
        // association
        artifactDrawInstructions.put(Association.class, new ArtifactDrawInstruction() {
            @Override
            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
                Association association = (Association) artifact;
                String sourceRef = association.getSourceRef();
                String targetRef = association.getTargetRef();
                // source and target can be instance of FlowElement or Artifact
                BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef);
                BaseElement targetElement = bpmnModel.getFlowElement(targetRef);
                if (sourceElement == null) {
                    sourceElement = bpmnModel.getArtifact(sourceRef);
                }
                if (targetElement == null) {
                    targetElement = bpmnModel.getArtifact(targetRef);
                }
                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
                int[] xPoints = new int[graphicInfoList.size()];
                int[] yPoints = new int[graphicInfoList.size()];
                for (int i = 1; i < graphicInfoList.size(); i++) {
                    GraphicInfo graphicInfo = graphicInfoList.get(i);
                    GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
                    if (i == 1) {
                        xPoints[0] = (int) previousGraphicInfo.getX();
                        yPoints[0] = (int) previousGraphicInfo.getY();
                    }
                    xPoints[i] = (int) graphicInfo.getX();
                    yPoints[i] = (int) graphicInfo.getY();
                }
                AssociationDirection associationDirection = association.getAssociationDirection();
                processDiagramCanvas.drawAssociation(xPoints, yPoints, associationDirection, false, scaleFactor);
            }
        });
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows,
                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows,
            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateImage(imageType);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, 1.0, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
                                       List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
                                       String labelFontName, String annotationFontName, ClassLoader customClassLoader, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(),
            activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
                                       String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(),
            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generatePngDiagram(BpmnModel bpmnModel, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generatePngDiagram(bpmnModel, 1.0, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generatePngDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public InputStream generateJpgDiagram(BpmnModel bpmnModel) {
        return generateJpgDiagram(bpmnModel, 1.0, false);
    }
    @Override
    public InputStream generateJpgDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateDiagram(bpmnModel, "jpg", Collections.emptyList(), Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI);
    }
    public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows,
                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows,
            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateBufferedImage(imageType);
    }
    public BufferedImage generateImage(BpmnModel bpmnModel, String imageType,
                                       List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        return generateImage(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    }
    @Override
    public BufferedImage generatePngImage(BpmnModel bpmnModel, double scaleFactor) {
        return generateImage(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, false);
    }
    protected CustomDefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,
                                                                       List<String> highLightedActivities, List<String> highLightedFlows,
                                                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        prepareBpmnModel(bpmnModel);
        CustomDefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
        // Draw pool shape, if process is participant in collaboration
        for (Pool pool : bpmnModel.getPools()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo, scaleFactor);
        }
        // Draw lanes
        for (Process process : bpmnModel.getProcesses()) {
            for (Lane lane : process.getLanes()) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
                processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo, scaleFactor);
            }
        }
        // Draw activities and their sequence-flows
        for (Process process : bpmnModel.getProcesses()) {
            for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) {
                if (!isPartOfCollapsedSubProcess(flowNode, bpmnModel)) {
                    drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
                }
            }
        }
        // Draw artifacts
        for (Process process : bpmnModel.getProcesses()) {
            for (Artifact artifact : process.getArtifacts()) {
                drawArtifact(processDiagramCanvas, bpmnModel, artifact);
            }
            List<SubProcess> subProcesses = process.findFlowElementsOfType(SubProcess.class, true);
            if (subProcesses != null) {
                for (SubProcess subProcess : subProcesses) {
                    GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(subProcess.getId());
                    if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                        continue;
                    }
                    if (!isPartOfCollapsedSubProcess(subProcess, bpmnModel)) {
                        for (Artifact subProcessArtifact : subProcess.getArtifacts()) {
                            drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
                        }
                    }
                }
            }
        }
        return processDiagramCanvas;
    }
    protected void prepareBpmnModel(BpmnModel bpmnModel) {
        // Need to make sure all elements have positive x and y.
        // Check all graphicInfo and update the elements accordingly
        List<GraphicInfo> allGraphicInfos = new ArrayList<>();
        if (bpmnModel.getLocationMap() != null) {
            allGraphicInfos.addAll(bpmnModel.getLocationMap().values());
        }
        if (bpmnModel.getLabelLocationMap() != null) {
            allGraphicInfos.addAll(bpmnModel.getLabelLocationMap().values());
        }
        if (bpmnModel.getFlowLocationMap() != null) {
            for (List<GraphicInfo> flowGraphicInfos : bpmnModel.getFlowLocationMap().values()) {
                allGraphicInfos.addAll(flowGraphicInfos);
            }
        }
        if (allGraphicInfos.size() > 0) {
            boolean needsTranslationX = false;
            boolean needsTranslationY = false;
            double lowestX = 0.0;
            double lowestY = 0.0;
            // Collect lowest x and y
            for (GraphicInfo graphicInfo : allGraphicInfos) {
                double x = graphicInfo.getX();
                double y = graphicInfo.getY();
                if (x < lowestX) {
                    needsTranslationX = true;
                    lowestX = x;
                }
                if (y < lowestY) {
                    needsTranslationY = true;
                    lowestY = y;
                }
            }
            // Update all graphicInfo objects
            if (needsTranslationX || needsTranslationY) {
                double translationX = Math.abs(lowestX);
                double translationY = Math.abs(lowestY);
                for (GraphicInfo graphicInfo : allGraphicInfos) {
                    if (needsTranslationX) {
                        graphicInfo.setX(graphicInfo.getX() + translationX);
                    }
                    if (needsTranslationY) {
                        graphicInfo.setY(graphicInfo.getY() + translationY);
                    }
                }
            }
        }
    }
    protected void drawActivity(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel,
                                FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) {
        ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass());
        if (drawInstruction != null) {
            drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);
            // Gather info on the multi instance marker
            boolean multiInstanceSequential = false;
            boolean multiInstanceParallel = false;
            boolean collapsed = false;
            if (flowNode instanceof Activity) {
                Activity activity = (Activity) flowNode;
                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
                if (multiInstanceLoopCharacteristics != null) {
                    multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
                    multiInstanceParallel = !multiInstanceSequential;
                }
            }
            // Gather info on the collapsed marker
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            if (flowNode instanceof SubProcess) {
                collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded();
            } else if (flowNode instanceof CallActivity) {
                collapsed = true;
            }
            if (scaleFactor == 1.0) {
                // Actually draw the markers
                processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(),
                    multiInstanceSequential, multiInstanceParallel, collapsed);
            }
            // Draw highlighted activities
            if (highLightedActivities.contains(flowNode.getId())) {
                drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
            } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) {
                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
            }
        } else if (flowNode instanceof Task) {
            activityDrawInstructions.get(Task.class).draw(processDiagramCanvas, bpmnModel, flowNode);
            if (highLightedActivities.contains(flowNode.getId())) {
                drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
            } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) {
                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
            }
        }
        // Outgoing transitions of activity
        for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
            boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId()));
            String defaultFlow = null;
            if (flowNode instanceof Activity) {
                defaultFlow = ((Activity) flowNode).getDefaultFlow();
            } else if (flowNode instanceof Gateway) {
                defaultFlow = ((Gateway) flowNode).getDefaultFlow();
            }
            boolean isDefault = false;
            if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
                isDefault = true;
            }
            boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && sequenceFlow.getConditionExpression().trim().length() > 0 && !(flowNode instanceof Gateway);
            String sourceRef = sequenceFlow.getSourceRef();
            String targetRef = sequenceFlow.getTargetRef();
            FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
            FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
            if (graphicInfoList != null && graphicInfoList.size() > 0) {
                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
                int[] xPoints = new int[graphicInfoList.size()];
                int[] yPoints = new int[graphicInfoList.size()];
                for (int i = 1; i < graphicInfoList.size(); i++) {
                    GraphicInfo graphicInfo = graphicInfoList.get(i);
                    GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
                    if (i == 1) {
                        xPoints[0] = (int) previousGraphicInfo.getX();
                        yPoints[0] = (int) previousGraphicInfo.getY();
                    }
                    xPoints[i] = (int) graphicInfo.getX();
                    yPoints[i] = (int) graphicInfo.getY();
                }
                processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor);
                // Draw sequenceflow label
                GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
                if (labelGraphicInfo != null) {
                    processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false);
                } else {
                    if (drawSequenceFlowNameWithNoLabelDI) {
                        GraphicInfo lineCenter = getLineCenter(graphicInfoList);
                        processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false);
                    }
                }
            }
        }
        // Nested elements
        if (flowNode instanceof FlowElementsContainer) {
            for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
                if (nestedFlowElement instanceof FlowNode && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) {
                    drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement,
                        highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
                }
            }
        }
    }
    /**
     * This method makes coordinates of connection flow better.
     *
     * @param processDiagramCanvas
     * @param bpmnModel
     * @param sourceElement
     * @param targetElement
     * @param graphicInfoList
     * @return
     */
    protected static List<GraphicInfo> connectionPerfectionizer(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, BaseElement sourceElement, BaseElement targetElement, List<GraphicInfo> graphicInfoList) {
        GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
        GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId());
        CustomDefaultProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
        CustomDefaultProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement);
        return processDiagramCanvas.connectionPerfectionizer(sourceShapeType, targetShapeType, sourceGraphicInfo, targetGraphicInfo, graphicInfoList);
    }
    /**
     * This method returns shape type of base element.<br>
     * Each element can be presented as rectangle, rhombus, or ellipse.
     *
     * @param baseElement
     * @return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE
     */
    protected static CustomDefaultProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) {
        if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) {
            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
        } else if (baseElement instanceof Gateway) {
            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
        } else if (baseElement instanceof Event) {
            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Ellipse;
        } else {
            // unknown source element, just do not correct coordinates
        }
        return null;
    }
    protected static GraphicInfo getLineCenter(List<GraphicInfo> graphicInfoList) {
        GraphicInfo gi = new GraphicInfo();
        int[] xPoints = new int[graphicInfoList.size()];
        int[] yPoints = new int[graphicInfoList.size()];
        double length = 0;
        double[] lengths = new double[graphicInfoList.size()];
        lengths[0] = 0;
        double m;
        for (int i = 1; i < graphicInfoList.size(); i++) {
            GraphicInfo graphicInfo = graphicInfoList.get(i);
            GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
            if (i == 1) {
                xPoints[0] = (int) previousGraphicInfo.getX();
                yPoints[0] = (int) previousGraphicInfo.getY();
            }
            xPoints[i] = (int) graphicInfo.getX();
            yPoints[i] = (int) graphicInfo.getY();
            length += Math.sqrt(
                Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(), 2) +
                    Math.pow((int) graphicInfo.getY() - (int) previousGraphicInfo.getY(), 2));
            lengths[i] = length;
        }
        m = length / 2;
        int p1 = 0;
        int p2 = 1;
        for (int i = 1; i < lengths.length; i++) {
            double len = lengths[i];
            p1 = i - 1;
            p2 = i;
            if (len > m) {
                break;
            }
        }
        GraphicInfo graphicInfo1 = graphicInfoList.get(p1);
        GraphicInfo graphicInfo2 = graphicInfoList.get(p2);
        double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX();
        double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY();
        double OB = lengths[p2] - lengths[p1];
        double ob = m - lengths[p1];
        double ab = AB * ob / OB;
        double oa = OA * ob / OB;
        double mx = graphicInfo1.getX() + ab;
        double my = graphicInfo1.getY() + oa;
        gi.setX(mx);
        gi.setY(my);
        return gi;
    }
    protected void drawArtifact(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
        ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass());
        if (drawInstruction != null) {
            drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact);
        }
    }
    private static void drawHighLight(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
    }
    private static void drawHighLightRed(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLightRed((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
    }
    protected static CustomDefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType,
                                                                                String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        // We need to calculate maximum values to know how big the image will be in its entirety
        double minX = Double.MAX_VALUE;
        double maxX = 0;
        double minY = Double.MAX_VALUE;
        double maxY = 0;
        for (Pool pool : bpmnModel.getPools()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            minX = graphicInfo.getX();
            maxX = graphicInfo.getX() + graphicInfo.getWidth();
            minY = graphicInfo.getY();
            maxY = graphicInfo.getY() + graphicInfo.getHeight();
        }
        List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
        for (FlowNode flowNode : flowNodes) {
            GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            // width
            if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
                maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
            }
            if (flowNodeGraphicInfo.getX() < minX) {
                minX = flowNodeGraphicInfo.getX();
            }
            // height
            if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
                maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
            }
            if (flowNodeGraphicInfo.getY() < minY) {
                minY = flowNodeGraphicInfo.getY();
            }
            for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
                if (graphicInfoList != null) {
                    for (GraphicInfo graphicInfo : graphicInfoList) {
                        // width
                        if (graphicInfo.getX() > maxX) {
                            maxX = graphicInfo.getX();
                        }
                        if (graphicInfo.getX() < minX) {
                            minX = graphicInfo.getX();
                        }
                        // height
                        if (graphicInfo.getY() > maxY) {
                            maxY = graphicInfo.getY();
                        }
                        if (graphicInfo.getY() < minY) {
                            minY = graphicInfo.getY();
                        }
                    }
                }
            }
        }
        List<Artifact> artifacts = gatherAllArtifacts(bpmnModel);
        for (Artifact artifact : artifacts) {
            GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
            if (artifactGraphicInfo != null) {
                // width
                if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) {
                    maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth();
                }
                if (artifactGraphicInfo.getX() < minX) {
                    minX = artifactGraphicInfo.getX();
                }
                // height
                if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) {
                    maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight();
                }
                if (artifactGraphicInfo.getY() < minY) {
                    minY = artifactGraphicInfo.getY();
                }
            }
            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
            if (graphicInfoList != null) {
                for (GraphicInfo graphicInfo : graphicInfoList) {
                    // width
                    if (graphicInfo.getX() > maxX) {
                        maxX = graphicInfo.getX();
                    }
                    if (graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }
                    // height
                    if (graphicInfo.getY() > maxY) {
                        maxY = graphicInfo.getY();
                    }
                    if (graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }
        int nrOfLanes = 0;
        for (Process process : bpmnModel.getProcesses()) {
            for (Lane l : process.getLanes()) {
                nrOfLanes++;
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
                // // width
                if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
                    maxX = graphicInfo.getX() + graphicInfo.getWidth();
                }
                if (graphicInfo.getX() < minX) {
                    minX = graphicInfo.getX();
                }
                // height
                if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
                    maxY = graphicInfo.getY() + graphicInfo.getHeight();
                }
                if (graphicInfo.getY() < minY) {
                    minY = graphicInfo.getY();
                }
            }
        }
        // Special case, see https://activiti.atlassian.net/browse/ACT-1431
        if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) {
            // Nothing to show
            minX = 0;
            minY = 0;
        }
        return new CustomDefaultProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY,
            imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }
    protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) {
        List<Artifact> artifacts = new ArrayList<>();
        for (Process process : bpmnModel.getProcesses()) {
            artifacts.addAll(process.getArtifacts());
        }
        return artifacts;
    }
    protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
        List<FlowNode> flowNodes = new ArrayList<>();
        for (Process process : bpmnModel.getProcesses()) {
            flowNodes.addAll(gatherAllFlowNodes(process));
        }
        return flowNodes;
    }
    protected static List<FlowNode> gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) {
        List<FlowNode> flowNodes = new ArrayList<>();
        for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
            if (flowElement instanceof FlowNode) {
                flowNodes.add((FlowNode) flowElement);
            }
            if (flowElement instanceof FlowElementsContainer) {
                flowNodes.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement));
            }
        }
        return flowNodes;
    }
    protected boolean isPartOfCollapsedSubProcess(FlowElement flowElement, BpmnModel model) {
        SubProcess subProcess = flowElement.getSubProcess();
        if (subProcess != null) {
            GraphicInfo graphicInfo = model.getGraphicInfo(subProcess.getId());
            if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                return true;
            }
            return isPartOfCollapsedSubProcess(subProcess, model);
        }
        return false;
    }
    public Map<Class<? extends BaseElement>, ActivityDrawInstruction> getActivityDrawInstructions() {
        return activityDrawInstructions;
    }
    public void setActivityDrawInstructions(
        Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions) {
        this.activityDrawInstructions = activityDrawInstructions;
    }
    public Map<Class<? extends BaseElement>, ArtifactDrawInstruction> getArtifactDrawInstructions() {
        return artifactDrawInstructions;
    }
    public void setArtifactDrawInstructions(
        Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions) {
        this.artifactDrawInstructions = artifactDrawInstructions;
    }
    protected interface ActivityDrawInstruction {
        void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode);
    }
    protected interface ArtifactDrawInstruction {
        void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package org.dromara.workflow.flowable.cmd;
import cn.hutool.core.collection.CollUtil;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES;
/**
 * ä¸²è¡ŒåŠ ç­¾
 *
 * @author may
 */
public class AddSequenceMultiInstanceCmd implements Command<Void> {
    /**
     * æ‰§è¡Œid
     */
    private final String executionId;
    /**
     * ä¼šç­¾äººå‘˜é›†åˆKEY
     */
    private final String assigneeList;
    /**
     * åŠ ç­¾äººå‘˜
     */
    private final List<Long> assignees;
    public AddSequenceMultiInstanceCmd(String executionId, String assigneeList, List<Long> assignees) {
        this.executionId = executionId;
        this.assigneeList = assigneeList;
        this.assignees = assignees;
    }
    @Override
    public Void execute(CommandContext commandContext) {
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
        ExecutionEntity entity = executionEntityManager.findById(executionId);
        // å¤šå®žä¾‹ä»»åŠ¡æ€»æ•°åŠ  assignees.size()
        if (entity.getVariable(NUMBER_OF_INSTANCES) instanceof Integer nrOfInstances) {
            entity.setVariable(NUMBER_OF_INSTANCES, nrOfInstances + assignees.size());
        }
        // è®¾ç½®æµç¨‹å˜é‡
        if (entity.getVariable(assigneeList) instanceof List<?> userIds) {
            CollUtil.addAll(userIds, assignees);
            Map<String, Object> variables = new HashMap<>(16);
            variables.put(assigneeList, userIds);
            entity.setVariables(variables);
        }
        return null;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package org.dromara.workflow.flowable.cmd;
import cn.hutool.core.collection.CollUtil;
import org.dromara.common.core.domain.dto.OssDTO;
import org.dromara.common.core.service.OssService;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.AttachmentEntity;
import org.flowable.engine.impl.persistence.entity.AttachmentEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.util.Date;
import java.util.List;
/**
 * é™„件上传
 *
 * @author may
 */
public class AttachmentCmd implements Command<Boolean> {
    private final String fileId;
    private final String taskId;
    private final String processInstanceId;
    public AttachmentCmd(String fileId, String taskId, String processInstanceId) {
        this.fileId = fileId;
        this.taskId = taskId;
        this.processInstanceId = processInstanceId;
    }
    @Override
    public Boolean execute(CommandContext commandContext) {
        try {
            if (StringUtils.isNotBlank(fileId)) {
                List<OssDTO> ossList = SpringUtils.getBean(OssService.class).selectByIds(fileId);
                if (CollUtil.isNotEmpty(ossList)) {
                    for (OssDTO oss : ossList) {
                        AttachmentEntityManager attachmentEntityManager = CommandContextUtil.getAttachmentEntityManager();
                        AttachmentEntity attachmentEntity = attachmentEntityManager.create();
                        attachmentEntity.setRevision(1);
                        attachmentEntity.setUserId(LoginHelper.getUserId().toString());
                        attachmentEntity.setName(oss.getOriginalName());
                        attachmentEntity.setDescription(oss.getOriginalName());
                        attachmentEntity.setType(oss.getFileSuffix());
                        attachmentEntity.setTaskId(taskId);
                        attachmentEntity.setProcessInstanceId(processInstanceId);
                        attachmentEntity.setContentId(oss.getOssId().toString());
                        attachmentEntity.setTime(new Date());
                        attachmentEntityManager.insert(attachmentEntity);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package org.dromara.workflow.flowable.cmd;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.io.Serializable;
/**
 * åˆ é™¤æ‰§è¡Œæ•°æ®
 *
 * @author may
 */
public class DeleteExecutionCmd implements Command<Void>, Serializable {
    /**
     * æ‰§è¡Œid
     */
    private final String executionId;
    public DeleteExecutionCmd(String executionId) {
        this.executionId = executionId;
    }
    @Override
    public Void execute(CommandContext commandContext) {
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
        ExecutionEntity entity = executionEntityManager.findById(executionId);
        if (entity != null) {
            executionEntityManager.deleteExecutionAndRelatedData(entity, "", false, false);
        }
        return null;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package org.dromara.workflow.flowable.cmd;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.dromara.workflow.common.constant.FlowConstant.LOOP_COUNTER;
import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES;
/**
 * ä¸²è¡Œå‡ç­¾
 *
 * @author may
 */
@AllArgsConstructor
public class DeleteSequenceMultiInstanceCmd implements Command<Void> {
    /**
     * å½“前节点审批人员id
     */
    private final String currentUserId;
    /**
     * æ‰§è¡Œid
     */
    private final String executionId;
    /**
     * ä¼šç­¾äººå‘˜é›†åˆKEY
     */
    private final String assigneeList;
    /**
     * å‡ç­¾äººå‘˜
     */
    private final List<Long> assignees;
    @Override
    @SuppressWarnings("unchecked")
    public Void execute(CommandContext commandContext) {
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
        ExecutionEntity entity = executionEntityManager.findById(executionId);
        // è®¾ç½®æµç¨‹å˜é‡
        List<Long> userIds = new ArrayList<>();
        List<Object> variable = (List<Object>) entity.getVariable(assigneeList);
        for (Object o : variable) {
            userIds.add(Long.valueOf(o.toString()));
        }
        List<Long> userIdList = new ArrayList<>();
        userIds.forEach(e -> {
            Long userId = assignees.stream().filter(id -> ObjectUtil.equals(id, e)).findFirst().orElse(null);
            if (userId == null) {
                userIdList.add(e);
            }
        });
        // å½“前任务执行位置
        int loopCounterIndex = -1;
        for (int i = 0; i < userIdList.size(); i++) {
            Long userId = userIdList.get(i);
            if (currentUserId.equals(userId.toString())) {
                loopCounterIndex = i;
            }
        }
        Map<String, Object> variables = new HashMap<>(16);
        variables.put(NUMBER_OF_INSTANCES, userIdList.size());
        variables.put(assigneeList, userIdList);
        variables.put(LOOP_COUNTER, loopCounterIndex);
        entity.setVariables(variables);
        return null;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package org.dromara.workflow.flowable.cmd;
import org.dromara.common.core.utils.StreamUtils;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.io.Serializable;
import java.util.List;
/**
 * èŽ·å–å¹¶è¡Œç½‘å…³æ‰§è¡ŒåŽä¿ç•™çš„æ‰§è¡Œå®žä¾‹æ•°æ®
 *
 * @author may
 */
public class ExecutionChildByExecutionIdCmd implements Command<List<ExecutionEntity>>, Serializable {
    /**
     * å½“前任务执行实例id
     */
    private final String executionId;
    public ExecutionChildByExecutionIdCmd(String executionId) {
        this.executionId = executionId;
    }
    @Override
    public List<ExecutionEntity> execute(CommandContext commandContext) {
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
        // èŽ·å–å½“å‰æ‰§è¡Œæ•°æ®
        ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
        // é€šè¿‡å½“前执行数据的父执行,查询所有子执行数据
        List<ExecutionEntity> allChildrenExecution =
            executionEntityManager.collectChildren(executionEntity.getParent());
        return StreamUtils.filter(allChildrenExecution, e -> !e.isActive());
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.workflow.flowable.cmd;
import org.dromara.common.core.exception.ServiceException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
/**
 * ä¿®æ”¹æµç¨‹çŠ¶æ€
 *
 * @author may
 */
public class UpdateBusinessStatusCmd implements Command<Boolean> {
    private final String processInstanceId;
    private final String status;
    public UpdateBusinessStatusCmd(String processInstanceId, String status) {
        this.processInstanceId = processInstanceId;
        this.status = status;
    }
    @Override
    public Boolean execute(CommandContext commandContext) {
        try {
            HistoricProcessInstanceEntityManager manager = CommandContextUtil.getHistoricProcessInstanceEntityManager();
            HistoricProcessInstanceEntity processInstance = manager.findById(processInstanceId);
            processInstance.setBusinessStatus(status);
            manager.update(processInstance);
            return true;
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.workflow.flowable.cmd;
import org.dromara.common.core.exception.ServiceException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.task.service.HistoricTaskService;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
import java.util.Date;
import java.util.List;
/**
 * ä¿®æ”¹æµç¨‹åŽ†å²
 *
 * @author may
 */
public class UpdateHiTaskInstCmd implements Command<Boolean> {
    private final List<String> taskIds;
    private final String processDefinitionId;
    private final String processInstanceId;
    public UpdateHiTaskInstCmd(List<String> taskIds, String processDefinitionId, String processInstanceId) {
        this.taskIds = taskIds;
        this.processDefinitionId = processDefinitionId;
        this.processInstanceId = processInstanceId;
    }
    @Override
    public Boolean execute(CommandContext commandContext) {
        try {
            HistoricTaskService historicTaskService = CommandContextUtil.getHistoricTaskService();
            for (String taskId : taskIds) {
                HistoricTaskInstanceEntity historicTask = historicTaskService.getHistoricTask(taskId);
                if (historicTask != null) {
                    historicTask.setProcessDefinitionId(processDefinitionId);
                    historicTask.setProcessInstanceId(processInstanceId);
                    historicTask.setCreateTime(new Date());
                    CommandContextUtil.getHistoricTaskService().updateHistoricTask(historicTask, true);
                }
            }
            return true;
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package org.dromara.workflow.flowable.config;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import org.dromara.workflow.flowable.handler.TaskTimeoutJobHandler;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
/**
 * flowable配置
 *
 * @author may
 */
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    @Autowired
    private GlobalFlowableListener globalFlowableListener;
    @Autowired
    private IdentifierGenerator identifierGenerator;
    @Override
    public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
        processEngineConfiguration.setIdGenerator(() -> identifierGenerator.nextId(null).toString());
        processEngineConfiguration.setEventListeners(Collections.singletonList(globalFlowableListener));
        processEngineConfiguration.addCustomJobHandler(new TaskTimeoutJobHandler());
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,139 @@
package org.dromara.workflow.flowable.config;
import cn.hutool.core.collection.CollUtil;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.flowable.handler.TaskTimeoutJobHandler;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.api.delegate.event.*;
import org.flowable.common.engine.impl.cfg.TransactionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.task.Comment;
import org.flowable.job.service.TimerJobService;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.job.service.impl.persistence.entity.TimerJobEntity;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
/**
 * å¼•擎调度监听
 *
 * @author may
 */
@Component
public class GlobalFlowableListener implements FlowableEventListener {
    @Autowired
    @Lazy
    private TaskService taskService;
    @Autowired
    @Lazy
    private RuntimeService runtimeService;
    @Autowired
    @Lazy
    private RepositoryService repositoryService;
    @Value("${flowable.async-executor-activate}")
    private boolean asyncExecutorActivate;
    @Override
    public void onEvent(FlowableEvent flowableEvent) {
        if (flowableEvent instanceof FlowableEngineEvent flowableEngineEvent) {
            FlowableEngineEventType engineEventType = (FlowableEngineEventType) flowableEvent.getType();
            switch (engineEventType) {
                case JOB_EXECUTION_SUCCESS -> jobExecutionSuccess((FlowableEngineEntityEvent) flowableEngineEvent);
                case TASK_DUEDATE_CHANGED, TASK_CREATED -> {
                    FlowableEntityEvent flowableEntityEvent = (FlowableEntityEvent) flowableEngineEvent;
                    Object entityObject = flowableEntityEvent.getEntity();
                    TaskEntity task = (TaskEntity) entityObject;
                    if (asyncExecutorActivate && task.getDueDate() != null && task.getDueDate().after(new Date())) {
                        //删除之前已经存在的定时任务
                        TimerJobService timerJobService = CommandContextUtil.getTimerJobService();
                        List<TimerJobEntity> timerJobEntityList = timerJobService.findTimerJobsByProcessInstanceId(task.getProcessInstanceId());
                        if (!CollUtil.isEmpty(timerJobEntityList)) {
                            for (TimerJobEntity timerJobEntity : timerJobEntityList) {
                                String taskId = timerJobEntity.getJobHandlerConfiguration();
                                if (task.getId().equals(taskId)) {
                                    timerJobService.deleteTimerJob(timerJobEntity);
                                }
                            }
                        }
                        //创建job对象
                        TimerJobEntity timer = timerJobService.createTimerJob();
                        timer.setTenantId(TenantHelper.getTenantId());
                        //设置job类型
                        timer.setJobType(JobEntity.JOB_TYPE_TIMER);
                        timer.setJobHandlerType(TaskTimeoutJobHandler.TYPE);
                        timer.setDuedate(task.getDueDate());
                        timer.setProcessInstanceId(task.getProcessInstanceId());
                        //设置任务id
                        timer.setJobHandlerConfiguration(task.getId());
                        //保存并触发事件
                        timerJobService.scheduleTimerJob(timer);
                    }
                }
            }
        }
    }
    @Override
    public boolean isFailOnException() {
        return true;
    }
    @Override
    public boolean isFireOnTransactionLifecycleEvent() {
        return false;
    }
    @Override
    public String getOnTransaction() {
        return TransactionState.COMMITTED.name();
    }
    /**
     * å¤„理边界定时事件自动审批记录
     *
     * @param event äº‹ä»¶
     */
    protected void jobExecutionSuccess(FlowableEngineEntityEvent event) {
        if (event != null && StringUtils.isNotBlank(event.getExecutionId())) {
            Execution execution = runtimeService.createExecutionQuery().executionId(event.getExecutionId()).singleResult();
            if (execution != null) {
                BpmnModel bpmnModel = repositoryService.getBpmnModel(event.getProcessDefinitionId());
                FlowElement flowElement = bpmnModel.getFlowElement(execution.getActivityId());
                if (flowElement instanceof BoundaryEvent) {
                    String attachedToRefId = ((BoundaryEvent) flowElement).getAttachedToRefId();
                    List<Execution> list = runtimeService.createExecutionQuery().activityId(attachedToRefId).list();
                    for (Execution ex : list) {
                        Task task = QueryUtils.taskQuery().executionId(ex.getId()).singleResult();
                        if (task != null) {
                            List<Comment> taskComments = taskService.getTaskComments(task.getId());
                            if (CollUtil.isEmpty(taskComments)) {
                                taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), "超时自动审批!");
                            }
                        }
                    }
                }
            }
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/handler/TaskTimeoutJobHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package org.dromara.workflow.flowable.handler;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.jobexecutor.TimerEventHandler;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.job.service.JobHandler;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.variable.api.delegate.VariableScope;
/**
 * åŠžç†è¶…æ—¶(过期)任务
 *
 * @author may
 */
public class TaskTimeoutJobHandler extends TimerEventHandler implements JobHandler {
    public static final String TYPE = "taskTimeout";
    @Override
    public String getType() {
        return TYPE;
    }
    @Override
    public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) {
        TaskService taskService = CommandContextUtil.getProcessEngineConfiguration(commandContext)
            .getTaskService();
        Task task = taskService.createTaskQuery().taskId(configuration).singleResult();
        if (task != null) {
            taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.TIMEOUT.getStatus(), "超时自动审批!");
            taskService.complete(configuration);
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package org.dromara.workflow.flowable.strategy;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.workflow.annotation.FlowListenerAnnotation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
 * æµç¨‹ä»»åŠ¡ç›‘å¬ç­–ç•¥
 *
 * @author may
 * @date 2023-12-27
 */
@Component
public class FlowEventStrategy implements BeanPostProcessor {
    private final Map<String, FlowTaskEventHandler> flowTaskEventHandlers = new HashMap<>();
    private final Map<String, FlowProcessEventHandler> flowProcessEventHandlers = new HashMap<>();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof FlowTaskEventHandler) {
            FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class);
            if (null != annotation) {
                if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isNotBlank(annotation.taskDefId())) {
                    String id = annotation.processDefinitionKey() + "_" + annotation.taskDefId();
                    if (!flowTaskEventHandlers.containsKey(id)) {
                        flowTaskEventHandlers.put(id, (FlowTaskEventHandler) bean);
                    }
                }
            }
        }
        if (bean instanceof FlowProcessEventHandler) {
            FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class);
            if (null != annotation) {
                if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isBlank(annotation.taskDefId())) {
                    if (!flowProcessEventHandlers.containsKey(annotation.processDefinitionKey())) {
                        flowProcessEventHandlers.put(annotation.processDefinitionKey(), (FlowProcessEventHandler) bean);
                    }
                }
            }
        }
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
    /**
     * èŽ·å–å¯æ‰§è¡Œbean
     *
     * @param key key
     */
    public FlowTaskEventHandler getTaskHandler(String key) {
        if (!flowTaskEventHandlers.containsKey(key)) {
            return null;
        }
        return flowTaskEventHandlers.get(key);
    }
    /**
     * èŽ·å–å¯æ‰§è¡Œbean
     *
     * @param key key
     */
    public FlowProcessEventHandler getProcessHandler(String key) {
        if (!flowProcessEventHandlers.containsKey(key)) {
            return null;
        }
        return flowProcessEventHandlers.get(key);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package org.dromara.workflow.flowable.strategy;
/**
 * æµç¨‹ç›‘听
 *
 * @author may
 * @date 2023-12-27
 */
public interface FlowProcessEventHandler {
    /**
     * æ‰§è¡ŒåŠžç†ä»»åŠ¡ç›‘å¬
     *
     * @param businessKey ä¸šåŠ¡id
     * @param status      çŠ¶æ€
     * @param submit      å½“为true时为申请人节点办理
     */
    void handleProcess(String businessKey, String status, boolean submit);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.dromara.workflow.flowable.strategy;
/**
 * æµç¨‹ä»»åŠ¡ç›‘å¬
 *
 * @author may
 * @date 2023-12-27
 */
public interface FlowTaskEventHandler {
    /**
     * æ‰§è¡ŒåŠžç†ä»»åŠ¡ç›‘å¬
     *
     * @param taskId      ä»»åŠ¡ID
     * @param businessKey ä¸šåŠ¡id
     */
    void handleTask(String taskId, String businessKey);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package org.dromara.workflow.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.workflow.annotation.FlowListenerAnnotation;
import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
import org.springframework.stereotype.Component;
/**
 * è‡ªå®šä¹‰ç›‘听测试
 *
 * @author may
 * @date 2023-12-27
 */
@Slf4j
@Component
@FlowListenerAnnotation(processDefinitionKey = "leave1")
public class TestCustomProcessHandler implements FlowProcessEventHandler {
    /**
     * æ‰§è¡ŒåŠžç†ä»»åŠ¡ç›‘å¬
     *
     * @param businessKey ä¸šåŠ¡id
     * @param status      çŠ¶æ€
     * @param submit      å½“为true时为申请人节点办理
     */
    @Override
    public void handleProcess(String businessKey, String status, boolean submit) {
        log.info("业务ID:" + businessKey + ",状态:" + status);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package org.dromara.workflow.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.workflow.annotation.FlowListenerAnnotation;
import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler;
import org.springframework.stereotype.Component;
/**
 * è‡ªå®šä¹‰ç›‘听测试
 *
 * @author may
 * @date 2023-12-27
 */
@Slf4j
@Component
@FlowListenerAnnotation(processDefinitionKey = "leave1", taskDefId = "Activity_14633hx")
public class TestCustomTaskHandler implements FlowTaskEventHandler {
    @Override
    public void handleTask(String taskId, String businessKey) {
        log.info("任务ID:" + taskId + ",业务ID:" + businessKey);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.dromara.workflow.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Component;
/**
 * æµç¨‹å®žä¾‹ç›‘听测试
 *
 * @author may
 * @date 2023-12-12
 */
@Slf4j
@RequiredArgsConstructor
@Component("testLeaveExecutionListener")
public class TestLeaveExecutionListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) {
        Task task = QueryUtils.taskQuery().executionId(execution.getId()).singleResult();
        log.info("执行监听【" + task.getName() + "】");
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package org.dromara.workflow.listener;
import lombok.extern.slf4j.Slf4j;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.stereotype.Component;
/**
 * æµç¨‹ä»»åŠ¡ç›‘å¬æµ‹è¯•
 *
 * @author may
 * @date 2023-12-12
 */
@Slf4j
@Component("testLeaveTaskListener")
public class TestLeaveTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        log.info("执行监听【" + delegateTask.getName() + "】");
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package org.dromara.workflow.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.workflow.domain.ActHiProcinst;
/**
 * æµç¨‹å®žä¾‹Mapper接口
 *
 * @author may
 * @date 2023-07-22
 */
@InterceptorIgnore(tenantLine = "true")
public interface ActHiProcinstMapper extends BaseMapperPlus<ActHiProcinst, ActHiProcinst> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package org.dromara.workflow.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.dromara.workflow.domain.ActHiTaskinst;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
 * æµç¨‹åŽ†å²ä»»åŠ¡Mapper接口
 *
 * @author may
 * @date 2024-03-02
 */
@InterceptorIgnore(tenantLine = "true")
public interface ActHiTaskinstMapper extends BaseMapperPlus<ActHiTaskinst, ActHiTaskinst> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package org.dromara.workflow.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.workflow.domain.vo.TaskVo;
/**
 * ä»»åŠ¡ä¿¡æ¯Mapper接口
 *
 * @author may
 * @date 2024-03-02
 */
@InterceptorIgnore(tenantLine = "true")
public interface ActTaskMapper extends BaseMapperPlus<TaskVo, TaskVo> {
    /**
     * èŽ·å–å¾…åŠžä¿¡æ¯
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<TaskVo> getTaskWaitByPage(@Param("page") Page<TaskVo> page, @Param(Constants.WRAPPER) Wrapper<TaskVo> queryWrapper);
    /**
     * èŽ·å–å·²åŠž
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<TaskVo> getTaskFinishByPage(@Param("page") Page<TaskVo> page, @Param(Constants.WRAPPER) Wrapper<TaskVo> queryWrapper);
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<TaskVo> getTaskCopyByPage(@Param("page") Page<TaskVo> page, @Param(Constants.WRAPPER) QueryWrapper<TaskVo> queryWrapper);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.dromara.workflow.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.workflow.domain.TestLeave;
import org.dromara.workflow.domain.vo.TestLeaveVo;
/**
 * è¯·å‡Mapper接口
 *
 * @author may
 * @date 2023-07-21
 */
public interface TestLeaveMapper extends BaseMapperPlus<TestLeave, TestLeaveVo> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.dromara.workflow.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.workflow.domain.WfCategory;
import org.dromara.workflow.domain.vo.WfCategoryVo;
/**
 * æµç¨‹åˆ†ç±»Mapper接口
 *
 * @author may
 * @date 2023-06-27
 */
public interface WfCategoryMapper extends BaseMapperPlus<WfCategory, WfCategoryVo> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfDefinitionConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.dromara.workflow.mapper;
import org.dromara.workflow.domain.WfDefinitionConfig;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
 * æµç¨‹å®šä¹‰é…ç½®Mapper接口
 *
 * @author may
 * @date 2024-03-18
 */
public interface WfDefinitionConfigMapper extends BaseMapperPlus<WfDefinitionConfig, WfDefinitionConfigVo> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfFormManageMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.dromara.workflow.mapper;
import org.dromara.workflow.domain.WfFormManage;
import org.dromara.workflow.domain.vo.WfFormManageVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
 * è¡¨å•管理Mapper接口
 *
 * @author may
 * @date 2024-03-29
 */
public interface WfFormManageMapper extends BaseMapperPlus<WfFormManage, WfFormManageVo> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfNodeConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.dromara.workflow.mapper;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.workflow.domain.vo.WfNodeConfigVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
 * èŠ‚ç‚¹é…ç½®Mapper接口
 *
 * @author may
 * @date 2024-03-30
 */
public interface WfNodeConfigMapper extends BaseMapperPlus<WfNodeConfig, WfNodeConfigVo> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfTaskBackNodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package org.dromara.workflow.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.workflow.domain.WfTaskBackNode;
/**
 * èŠ‚ç‚¹é©³å›žè®°å½•Mapper接口
 *
 * @author may
 * @date 2024-03-13
 */
public interface WfTaskBackNodeMapper extends BaseMapperPlus<WfTaskBackNode, WfTaskBackNode> {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.ActHiProcinst;
import java.util.List;
/**
 * æµç¨‹å®žä¾‹Service接口
 *
 * @author may
 * @date 2023-07-22
 */
public interface IActHiProcinstService {
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询
     *
     * @param businessKeys ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    List<ActHiProcinst> selectByBusinessKeyIn(List<String> businessKeys);
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询
     *
     * @param businessKey ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    ActHiProcinst selectByBusinessKey(String businessKey);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package org.dromara.workflow.service;
/**
 * æµç¨‹åŽ†å²ä»»åŠ¡Service接口
 *
 * @author may
 * @date 2024-03-02
 */
public interface IActHiTaskinstService {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package org.dromara.workflow.service;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.bo.ModelBo;
import org.dromara.workflow.domain.vo.ModelVo;
import org.flowable.engine.repository.Model;
import java.util.List;
/**
 * æ¨¡åž‹ç®¡ç† æœåС层
 *
 * @author may
 */
public interface IActModelService {
    /**
     * åˆ†é¡µæŸ¥è¯¢æ¨¡åž‹
     *
     * @param modelBo   æ¨¡åž‹å‚æ•°
     * @param pageQuery å‚æ•°
     * @return è¿”回分页列表
     */
    TableDataInfo<Model> page(ModelBo modelBo, PageQuery pageQuery);
    /**
     * æ–°å¢žæ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹è¯·æ±‚对象
     * @return ç»“æžœ
     */
    boolean saveNewModel(ModelBo modelBo);
    /**
     * æŸ¥è¯¢æ¨¡åž‹
     *
     * @param modelId æ¨¡åž‹id
     * @return æ¨¡åž‹æ•°æ®
     */
    ModelVo getInfo(String modelId);
    /**
     * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    boolean update(ModelBo modelBo);
    /**
     * ç¼–辑模型XML
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    boolean editModelXml(ModelBo modelBo);
    /**
     * æ¨¡åž‹éƒ¨ç½²
     *
     * @param id æ¨¡åž‹id
     * @return ç»“æžœ
     */
    boolean modelDeploy(String id);
    /**
     * å¯¼å‡ºæ¨¡åž‹zip压缩包
     *
     * @param modelIds æ¨¡åž‹id
     * @param response å“åº”
     */
    void exportZip(List<String> modelIds, HttpServletResponse response);
    /**
     * å¤åˆ¶æ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    boolean copyModel(ModelBo modelBo);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
package org.dromara.workflow.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
 * æµç¨‹å®šä¹‰ æœåС层
 *
 * @author may
 */
public interface IActProcessDefinitionService {
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param processDefinitionBo å‚æ•°
     * @param pageQuery           åˆ†é¡µ
     * @return è¿”回分页列表
     */
    TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo processDefinitionBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢åŽ†å²æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @param key æµç¨‹å®šä¹‰key
     * @return ç»“æžœ
     */
    List<ProcessDefinitionVo> getListByKey(String key);
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰å›¾ç‰‡
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    String definitionImage(String processDefinitionId);
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰xml文件
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    String definitionXml(String processDefinitionId);
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     *
     * @param deploymentIds        éƒ¨ç½²id
     * @param processDefinitionIds æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean deleteDeployment(List<String> deploymentIds, List<String> processDefinitionIds);
    /**
     * æ¿€æ´»æˆ–者挂起流程定义
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean updateDefinitionState(String processDefinitionId);
    /**
     * è¿ç§»æµç¨‹å®šä¹‰
     *
     * @param currentProcessDefinitionId å½“前流程定义id
     * @param fromProcessDefinitionId    éœ€è¦è¿ç§»åˆ°çš„æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean migrationDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId);
    /**
     * æµç¨‹å®šä¹‰è½¬æ¢ä¸ºæ¨¡åž‹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean convertToModel(String processDefinitionId);
    /**
     * é€šè¿‡zip或xml部署流程定义
     *
     * @param file         æ–‡ä»¶
     * @param categoryCode åˆ†ç±»
     */
    void deployByFile(MultipartFile file, String categoryCode);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package org.dromara.workflow.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.bo.ProcessInstanceBo;
import org.dromara.workflow.domain.bo.ProcessInvalidBo;
import org.dromara.workflow.domain.bo.TaskUrgingBo;
import org.dromara.workflow.domain.vo.ActHistoryInfoVo;
import org.dromara.workflow.domain.vo.ProcessInstanceVo;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹å®žä¾‹ æœåС层
 *
 * @author may
 */
public interface IActProcessInstanceService {
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    String getHistoryImage(String processInstanceId);
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图运行中,历史等节点
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    Map<String, Object> getHistoryList(String processInstanceId);
    /**
     * åˆ†é¡µæŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹
     *
     * @param processInstanceBo å‚æ•°
     * @param pageQuery         åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<ProcessInstanceVo> getPageByRunning(ProcessInstanceBo processInstanceBo, PageQuery pageQuery);
    /**
     * åˆ†é¡µæŸ¥è¯¢å·²ç»“束的流程实例
     *
     * @param processInstanceBo å‚æ•°
     * @param pageQuery         åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<ProcessInstanceVo> getPageByFinish(ProcessInstanceBo processInstanceBo, PageQuery pageQuery);
    /**
     * èŽ·å–å®¡æ‰¹è®°å½•
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    List<ActHistoryInfoVo> getHistoryRecord(String processInstanceId);
    /**
     * ä½œåºŸæµç¨‹å®žä¾‹ï¼Œä¸ä¼šåˆ é™¤åŽ†å²è®°å½•(删除运行中的实例)
     *
     * @param processInvalidBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean deleteRunInstance(ProcessInvalidBo processInvalidBo);
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean deleteRunAndHisInstance(List<String> processInstanceIds);
    /**
     * æŒ‰ç…§ä¸šåŠ¡id删除 è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param businessKeys ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    boolean deleteRunAndHisInstanceByBusinessKeys(List<String> businessKeys);
    /**
     * å·²å®Œæˆçš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean deleteFinishAndHisInstance(List<String> processInstanceIds);
    /**
     * æ’¤é”€æµç¨‹ç”³è¯·
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean cancelProcessApply(String processInstanceId);
    /**
     * åˆ†é¡µæŸ¥è¯¢å½“前登录人单据
     *
     * @param processInstanceBo å‚æ•°
     * @param pageQuery         åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<ProcessInstanceVo> getPageByCurrent(ProcessInstanceBo processInstanceBo, PageQuery pageQuery);
    /**
     * ä»»åŠ¡å‚¬åŠž(给当前任务办理人发送站内信,邮件,短信等)
     *
     * @param taskUrgingBo ä»»åŠ¡å‚¬åŠž
     * @return ç»“æžœ
     */
    boolean taskUrging(TaskUrgingBo taskUrgingBo);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,161 @@
package org.dromara.workflow.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.TaskVo;
import org.dromara.workflow.domain.vo.VariableVo;
import java.util.List;
import java.util.Map;
/**
 * ä»»åŠ¡ æœåС层
 *
 * @author may
 */
public interface IActTaskService {
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     * @return ç»“æžœ
     */
    Map<String, Object> startWorkFlow(StartProcessBo startProcessBo);
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     * @return ç»“æžœ
     */
    boolean completeTask(CompleteTaskBo completeTaskBo);
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param taskBo    å‚æ•°
     * @param pageQuery åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<TaskVo> getPageByTaskWait(TaskBo taskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前租户所有待办任务
     *
     * @param taskBo    å‚æ•°
     * @param pageQuery åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<TaskVo> getPageByAllTaskWait(TaskBo taskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前用户的已办任务
     *
     * @param taskBo    å‚æ•°
     * @param pageQuery å‚æ•°
     * @return ç»“æžœ
     */
    TableDataInfo<TaskVo> getPageByTaskFinish(TaskBo taskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param taskBo    å‚æ•°
     * @param pageQuery å‚æ•°
     * @return ç»“æžœ
     */
    TableDataInfo<TaskVo> getPageByTaskCopy(TaskBo taskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前租户所有已办任务
     *
     * @param taskBo    å‚æ•°
     * @param pageQuery å‚æ•°
     * @return ç»“æžœ
     */
    TableDataInfo<TaskVo> getPageByAllTaskFinish(TaskBo taskBo, PageQuery pageQuery);
    /**
     * å§”派任务
     *
     * @param delegateBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean delegateTask(DelegateBo delegateBo);
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param terminationBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean terminationTask(TerminationBo terminationBo);
    /**
     * è½¬åŠžä»»åŠ¡
     *
     * @param transmitBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean transferTask(TransmitBo transmitBo);
    /**
     * ä¼šç­¾ä»»åŠ¡åŠ ç­¾
     *
     * @param addMultiBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean addMultiInstanceExecution(AddMultiBo addMultiBo);
    /**
     * ä¼šç­¾ä»»åŠ¡å‡ç­¾
     *
     * @param deleteMultiBo å‚æ•°
     * @return ç»“æžœ
     */
    boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo);
    /**
     * é©³å›žå®¡æ‰¹
     *
     * @param backProcessBo å‚æ•°
     * @return æµç¨‹å®žä¾‹id
     */
    String backProcess(BackProcessBo backProcessBo);
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
     *
     * @param taskIds ä»»åŠ¡id
     * @param userId  åŠžç†äººid
     * @return ç»“æžœ
     */
    boolean updateAssignee(String[] taskIds, String userId);
    /**
     * æŸ¥è¯¢æµç¨‹å˜é‡
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<VariableVo> getInstanceVariable(String taskId);
    /**
     * æŸ¥è¯¢å·¥ä½œæµä»»åŠ¡ç”¨æˆ·é€‰æ‹©åŠ ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    String getTaskUserIdsByAddMultiInstance(String taskId);
    /**
     * æŸ¥è¯¢å·¥ä½œæµé€‰æ‹©å‡ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<TaskVo> getListByDeleteMultiInstance(String taskId);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package org.dromara.workflow.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.TestLeave;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import java.util.Collection;
import java.util.List;
/**
 * è¯·å‡Service接口
 *
 * @author may
 * @date 2023-07-21
 */
public interface ITestLeaveService {
    /**
     * æŸ¥è¯¢è¯·å‡
     */
    TestLeaveVo queryById(Long id);
    /**
     * æŸ¥è¯¢è¯·å‡åˆ—表
     */
    TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢è¯·å‡åˆ—表
     */
    List<TestLeaveVo> queryList(TestLeaveBo bo);
    /**
     * æ–°å¢žè¯·å‡
     */
    TestLeaveVo insertByBo(TestLeaveBo bo);
    /**
     * ä¿®æ”¹è¯·å‡
     */
    TestLeaveVo updateByBo(TestLeaveBo bo);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤è¯·å‡ä¿¡æ¯
     */
    Boolean deleteWithValidByIds(Collection<Long> ids);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.WfCategory;
import org.dromara.workflow.domain.bo.WfCategoryBo;
import org.dromara.workflow.domain.vo.WfCategoryVo;
import java.util.Collection;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»Service接口
 *
 * @author may
 * @date 2023-06-28
 */
public interface IWfCategoryService {
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»
     */
    WfCategoryVo queryById(Long id);
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
     */
    List<WfCategoryVo> queryList(WfCategoryBo bo);
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     */
    Boolean insertByBo(WfCategoryBo bo);
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     */
    Boolean updateByBo(WfCategoryBo bo);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤æµç¨‹åˆ†ç±»ä¿¡æ¯
     */
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
    /**
     * æŒ‰ç…§ç±»åˆ«ç¼–码查询
     *
     * @param categoryCode åˆ†ç±»æ¯”吗
     * @return ç»“æžœ
     */
    WfCategory queryByCategoryCode(String categoryCode);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfDefinitionConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.workflow.domain.bo.WfDefinitionConfigBo;
import java.util.Collection;
import java.util.List;
/**
 * æµç¨‹å®šä¹‰é…ç½®Service接口
 *
 * @author may
 * @date 2024-03-18
 */
public interface IWfDefinitionConfigService {
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    WfDefinitionConfigVo getByDefId(String definitionId);
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     *
     * @param tableName è¡¨å
     * @return ç»“æžœ
     */
    WfDefinitionConfigVo getByTableNameLastVersion(String tableName);
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param tableName    è¡¨å
     * @return ç»“æžœ
     */
    WfDefinitionConfigVo getByDefIdAndTableName(String definitionId, String tableName);
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®æŽ’除当前查询的流程定义
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param tableName    è¡¨å
     * @return ç»“æžœ
     */
    List<WfDefinitionConfigVo> getByTableNameNotDefId(String tableName, String definitionId);
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®åˆ—表
     *
     * @param definitionIds æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    List<WfDefinitionConfigVo> queryList(List<String> definitionIds);
    /**
     * æ–°å¢žæµç¨‹å®šä¹‰é…ç½®
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    Boolean saveOrUpdate(WfDefinitionConfigBo bo);
    /**
     * åˆ é™¤
     *
     * @param ids id
     * @return ç»“æžœ
     */
    Boolean deleteByIds(Collection<Long> ids);
    /**
     * æŒ‰ç…§æµç¨‹å®šä¹‰id删除
     *
     * @param ids æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    Boolean deleteByDefIds(Collection<String> ids);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfFormManageService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.vo.WfFormManageVo;
import org.dromara.workflow.domain.bo.WfFormManageBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
 * è¡¨å•管理Service接口
 *
 * @author may
 * @date 2024-03-29
 */
public interface IWfFormManageService {
    /**
     * æŸ¥è¯¢è¡¨å•管理
     *
     * @param id ä¸»é”®
     * @return ç»“æžœ
     */
    WfFormManageVo queryById(Long id);
    /**
     * æŸ¥è¯¢è¡¨å•管理
     *
     * @param ids ä¸»é”®
     * @return ç»“æžœ
     */
    List<WfFormManageVo> queryByIds(List<Long> ids);
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     *
     * @param bo        å‚æ•°
     * @param pageQuery åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<WfFormManageVo> queryPageList(WfFormManageBo bo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     *
     * @return ç»“æžœ
     */
    List<WfFormManageVo> selectList();
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    List<WfFormManageVo> queryList(WfFormManageBo bo);
    /**
     * æ–°å¢žè¡¨å•管理
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    Boolean insertByBo(WfFormManageBo bo);
    /**
     * ä¿®æ”¹è¡¨å•管理
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    Boolean updateByBo(WfFormManageBo bo);
    /**
     * æ‰¹é‡åˆ é™¤è¡¨å•管理信息
     *
     * @param ids ä¸»é”®
     * @return ç»“æžœ
     */
    Boolean deleteByIds(Collection<Long> ids);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfNodeConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.workflow.domain.vo.WfNodeConfigVo;
import java.util.Collection;
import java.util.List;
/**
 * èŠ‚ç‚¹é…ç½®Service接口
 *
 * @author may
 * @date 2024-03-30
 */
public interface IWfNodeConfigService {
    /**
     * æŸ¥è¯¢èŠ‚ç‚¹é…ç½®
     *
     * @param id ä¸»é”®
     * @return ç»“æžœ
     */
    WfNodeConfigVo queryById(Long id);
    /**
     * ä¿å­˜èŠ‚ç‚¹é…ç½®
     *
     * @param list å‚æ•°
     * @return ç»“æžœ
     */
    Boolean saveOrUpdate(List<WfNodeConfig> list);
    /**
     * æ‰¹é‡åˆ é™¤èŠ‚ç‚¹é…ç½®ä¿¡æ¯
     *
     * @param ids ä¸»é”®
     * @return ç»“æžœ
     */
    Boolean deleteByIds(Collection<Long> ids);
    /**
     * æŒ‰ç…§æµç¨‹å®šä¹‰id删除
     *
     * @param ids æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    Boolean deleteByDefIds(Collection<String> ids);
    /**
     * æŒ‰ç…§æµç¨‹å®šä¹‰id查询
     *
     * @param ids æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    List<WfNodeConfigVo> selectByDefIds(Collection<String> ids);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfTaskBackNodeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.WfTaskBackNode;
import org.flowable.task.api.Task;
import java.util.List;
/**
 * èŠ‚ç‚¹é©³å›žè®°å½•Service接口
 *
 * @author may
 * @date 2024-03-13
 */
public interface IWfTaskBackNodeService {
    /**
     * è®°å½•审批节点
     *
     * @param task ä»»åŠ¡
     */
    void recordExecuteNode(Task task);
    /**
     * æŒ‰æµç¨‹å®žä¾‹id查询
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    List<WfTaskBackNode> getListByInstanceId(String processInstanceId);
    /**
     * æŒ‰ç…§æµç¨‹å®žä¾‹id,节点id查询
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @param nodeId            èŠ‚ç‚¹id
     * @return ç»“æžœ
     */
    WfTaskBackNode getListByInstanceIdAndNodeId(String processInstanceId, String nodeId);
    /**
     * åˆ é™¤é©³å›žåŽçš„节点
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @param targetActivityId  èŠ‚ç‚¹id
     * @return ç»“æžœ
     */
    boolean deleteBackTaskNode(String processInstanceId, String targetActivityId);
    /**
     * æŒ‰æµç¨‹å®žä¾‹id删除
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean deleteByInstanceId(String processInstanceId);
    /**
     * æŒ‰æµç¨‹å®žä¾‹id删除
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean deleteByInstanceIds(List<String> processInstanceIds);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.dromara.workflow.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.domain.ActHiProcinst;
import org.dromara.workflow.mapper.ActHiProcinstMapper;
import org.dromara.workflow.service.IActHiProcinstService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * æµç¨‹å®žä¾‹Service业务层处理
 *
 * @author may
 * @date 2023-07-22
 */
@RequiredArgsConstructor
@Service
public class ActHiProcinstServiceImpl implements IActHiProcinstService {
    private final ActHiProcinstMapper baseMapper;
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询
     *
     * @param businessKeys ä¸šåŠ¡id
     */
    @Override
    public List<ActHiProcinst> selectByBusinessKeyIn(List<String> businessKeys) {
        return baseMapper.selectList(new LambdaQueryWrapper<ActHiProcinst>()
            .in(ActHiProcinst::getBusinessKey, businessKeys)
            .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId()));
    }
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询
     *
     * @param businessKey ä¸šåŠ¡id
     */
    @Override
    public ActHiProcinst selectByBusinessKey(String businessKey) {
        return baseMapper.selectOne(new LambdaQueryWrapper<ActHiProcinst>()
            .eq(ActHiProcinst::getBusinessKey, businessKey)
            .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId()));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package org.dromara.workflow.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.dromara.workflow.service.IActHiTaskinstService;
/**
 * æµç¨‹åŽ†å²ä»»åŠ¡Service业务层处理
 *
 * @author may
 * @date 2024-03-02
 */
@RequiredArgsConstructor
@Service
public class ActHiTaskinstServiceImpl implements IActHiTaskinstService {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,424 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.util.StringUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.workflow.domain.bo.ModelBo;
import org.dromara.workflow.domain.bo.WfDefinitionConfigBo;
import org.dromara.workflow.domain.vo.ModelVo;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.workflow.service.IActModelService;
import org.dromara.workflow.service.IWfDefinitionConfigService;
import org.dromara.workflow.service.IWfNodeConfigService;
import org.dromara.workflow.utils.ModelUtils;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.validation.ValidationError;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
 * æ¨¡åž‹ç®¡ç† æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@RequiredArgsConstructor
@Service
public class ActModelServiceImpl implements IActModelService {
    private final RepositoryService repositoryService;
    private final IWfNodeConfigService wfNodeConfigService;
    private final IWfDefinitionConfigService wfDefinitionConfigService;
    /**
     * åˆ†é¡µæŸ¥è¯¢æ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹å‚æ•°
     * @return è¿”回分页列表
     */
    @Override
    public TableDataInfo<Model> page(ModelBo modelBo, PageQuery pageQuery) {
        ModelQuery query = QueryUtils.modelQuery();
        if (StringUtils.isNotBlank(modelBo.getName())) {
            query.modelNameLike("%" + modelBo.getName() + "%");
        }
        if (StringUtils.isNotBlank(modelBo.getKey())) {
            query.modelKey(modelBo.getKey());
        }
        if (StringUtils.isNotBlank(modelBo.getCategoryCode())) {
            query.modelCategory(modelBo.getCategoryCode());
        }
        query.orderByLastUpdateTime().desc();
        // åˆ›å»ºæ—¶é—´é™åºæŽ’列
        query.orderByCreateTime().desc();
        // åˆ†é¡µæŸ¥è¯¢
        List<Model> modelList = query.listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        // æ€»è®°å½•æ•°
        long total = query.count();
        TableDataInfo<Model> build = TableDataInfo.build();
        build.setRows(modelList);
        build.setTotal(total);
        return build;
    }
    /**
     * æ–°å¢žæ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹è¯·æ±‚对象
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveNewModel(ModelBo modelBo) {
        try {
            int version = 0;
            String key = modelBo.getKey();
            String name = modelBo.getName();
            String description = modelBo.getDescription();
            String categoryCode = modelBo.getCategoryCode();
            String xml = modelBo.getXml();
            Model checkModel = QueryUtils.modelQuery().modelKey(key).singleResult();
            if (ObjectUtil.isNotNull(checkModel)) {
                throw new ServiceException("模型key已存在!");
            }
            //初始空的模型
            Model model = repositoryService.newModel();
            model.setKey(key);
            model.setName(name);
            model.setVersion(version);
            model.setCategory(categoryCode);
            model.setMetaInfo(description);
            model.setTenantId(TenantHelper.getTenantId());
            //保存初始化的模型基本信息数据
            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æŸ¥è¯¢æ¨¡åž‹
     *
     * @param id æ¨¡åž‹id
     * @return æ¨¡åž‹æ•°æ®
     */
    @Override
    public ModelVo getInfo(String id) {
        ModelVo modelVo = new ModelVo();
        Model model = repositoryService.getModel(id);
        if (model != null) {
            try {
                byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
                modelVo.setXml(StrUtil.utf8Str(modelEditorSource));
                modelVo.setId(model.getId());
                modelVo.setKey(model.getKey());
                modelVo.setName(model.getName());
                modelVo.setCategoryCode(model.getCategory());
                modelVo.setDescription(model.getMetaInfo());
                return modelVo;
            } catch (Exception e) {
                throw new ServiceException(e.getMessage());
            }
        }
        return modelVo;
    }
    /**
     * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    @Override
    public boolean update(ModelBo modelBo) {
        try {
            Model model = repositoryService.getModel(modelBo.getId());
            List<Model> list = QueryUtils.modelQuery().modelKey(modelBo.getKey()).list();
            list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> {
                throw new ServiceException("模型KEY已存在!");
            });
            model.setCategory(modelBo.getCategoryCode());
            model.setMetaInfo(modelBo.getDescription());
            repositoryService.saveModel(model);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
        return true;
    }
    /**
     * ç¼–辑模型XML
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean editModelXml(ModelBo modelBo) {
        try {
            String xml = modelBo.getXml();
            String svg = modelBo.getSvg();
            String modelId = modelBo.getId();
            String key = modelBo.getKey();
            String name = modelBo.getName();
            BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xml);
            ModelUtils.checkBpmnModel(bpmnModel);
            Model model = repositoryService.getModel(modelId);
            List<Model> list = QueryUtils.modelQuery().modelKey(key).list();
            list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> {
                throw new ServiceException("模型KEY已存在!");
            });
            // æ ¡éªŒkey命名规范
            if (!Validator.isMatchRegex(FlowConstant.MODEL_KEY_PATTERN, key)) {
                throw new ServiceException("模型标识KEY只能字符或者下划线开头!");
            }
            model.setKey(key);
            model.setName(name);
            model.setVersion(model.getVersion() + 1);
            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml));
            // è½¬æ¢å›¾ç‰‡
            InputStream svgStream = new ByteArrayInputStream(StrUtil.utf8Bytes(svg));
            TranscoderInput input = new TranscoderInput(svgStream);
            PNGTranscoder transcoder = new PNGTranscoder();
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            TranscoderOutput output = new TranscoderOutput(outStream);
            transcoder.transcode(input, output);
            final byte[] result = outStream.toByteArray();
            repositoryService.addModelEditorSourceExtra(model.getId(), result);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æ¨¡åž‹éƒ¨ç½²
     *
     * @param id æ¨¡åž‹id
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modelDeploy(String id) {
        try {
            // æŸ¥è¯¢æµç¨‹å®šä¹‰æ¨¡åž‹xml
            byte[] xmlBytes = repositoryService.getModelEditorSource(id);
            if (ArrayUtil.isEmpty(xmlBytes)) {
                throw new ServiceException("模型数据为空,请先设计流程定义模型,再进行部署!");
            }
            if (JSONUtil.isTypeJSON(new String(xmlBytes, StandardCharsets.UTF_8))) {
                byte[] bytes = ModelUtils.bpmnJsonToXmlBytes(xmlBytes);
                if (ArrayUtil.isEmpty(bytes)) {
                    throw new ServiceException("模型不能为空,请至少设计一条主线流程!");
                }
            }
            BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xmlBytes);
            // æ ¡éªŒæ¨¡åž‹
            ModelUtils.checkBpmnModel(bpmnModel);
            List<ValidationError> validationErrors = repositoryService.validateProcess(bpmnModel);
            if (CollUtil.isNotEmpty(validationErrors)) {
                String errorMsg = validationErrors.stream().map(ValidationError::getProblem).distinct().collect(Collectors.joining(","));
                throw new ServiceException(errorMsg);
            }
            // æŸ¥è¯¢æ¨¡åž‹çš„基本信息
            Model model = repositoryService.getModel(id);
            ProcessDefinition processDefinition = QueryUtils.definitionQuery().processDefinitionKey(model.getKey()).latestVersion().singleResult();
            // xml资源的名称 ï¼Œå¯¹åº”act_ge_bytearray表中的name_字段
            String processName = model.getName() + ".bpmn20.xml";
            // è°ƒç”¨éƒ¨ç½²ç›¸å…³çš„api方法进行部署流程定义
            Deployment deployment = repositoryService.createDeployment()
                // éƒ¨ç½²åç§°
                .name(model.getName())
                // éƒ¨ç½²æ ‡è¯†key
                .key(model.getKey())
                // éƒ¨ç½²æµç¨‹åˆ†ç±»
                .category(model.getCategory())
                // bpmn20.xml资源
                .addBytes(processName, xmlBytes)
                // ç§Ÿæˆ·id
                .tenantId(TenantHelper.getTenantId())
                .deploy();
            // æ›´æ–° éƒ¨ç½²id åˆ°æµç¨‹å®šä¹‰æ¨¡åž‹æ•°æ®è¡¨ä¸­
            model.setDeploymentId(deployment.getId());
            repositoryService.saveModel(model);
            // æ›´æ–°åˆ†ç±»
            ProcessDefinition definition = QueryUtils.definitionQuery().deploymentId(deployment.getId()).singleResult();
            repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
            //更新流程定义配置
            if (processDefinition != null) {
                WfDefinitionConfigVo definitionVo = wfDefinitionConfigService.getByDefId(processDefinition.getId());
                if (definitionVo != null) {
                    wfDefinitionConfigService.deleteByDefIds(Collections.singletonList(processDefinition.getId()));
                    WfDefinitionConfigBo wfFormDefinition = new WfDefinitionConfigBo();
                    wfFormDefinition.setDefinitionId(definition.getId());
                    wfFormDefinition.setProcessKey(definition.getKey());
                    wfFormDefinition.setTableName(definitionVo.getTableName());
                    wfFormDefinition.setVersion(definition.getVersion());
                    wfFormDefinition.setRemark(definitionVo.getRemark());
                    wfDefinitionConfigService.saveOrUpdate(wfFormDefinition);
                }
            }
            //更新流程节点配置表单
            List<UserTask> userTasks = ModelUtils.getUserTaskFlowElements(definition.getId());
            UserTask applyUserTask = ModelUtils.getApplyUserTask(definition.getId());
            List<WfNodeConfig> wfNodeConfigList = new ArrayList<>();
            for (UserTask userTask : userTasks) {
                if (StringUtils.isNotBlank(userTask.getFormKey()) && userTask.getFormKey().contains(StrUtil.COLON)) {
                    WfNodeConfig wfNodeConfig = new WfNodeConfig();
                    wfNodeConfig.setNodeId(userTask.getId());
                    wfNodeConfig.setNodeName(userTask.getName());
                    wfNodeConfig.setDefinitionId(definition.getId());
                    String[] split = userTask.getFormKey().split(StrUtil.COLON);
                    wfNodeConfig.setFormType(split[0]);
                    wfNodeConfig.setFormId(Long.valueOf(split[1]));
                    wfNodeConfig.setApplyUserTask(applyUserTask.getId().equals(userTask.getId()) ? FlowConstant.TRUE : FlowConstant.FALSE);
                    wfNodeConfigList.add(wfNodeConfig);
                }
            }
            if (CollUtil.isNotEmpty(wfNodeConfigList)) {
                wfNodeConfigService.saveOrUpdate(wfNodeConfigList);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * å¯¼å‡ºæ¨¡åž‹zip压缩包
     *
     * @param modelIds æ¨¡åž‹id
     * @param response ç›¸åº”
     */
    @Override
    public void exportZip(List<String> modelIds, HttpServletResponse response) {
        try (ZipOutputStream zos = ZipUtil.getZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8)) {
            // åŽ‹ç¼©åŒ…æ–‡ä»¶å
            String zipName = "模型不存在";
            // æŸ¥è¯¢æ¨¡åž‹åŸºæœ¬ä¿¡æ¯
            for (String modelId : modelIds) {
                Model model = repositoryService.getModel(modelId);
                byte[] xmlBytes = repositoryService.getModelEditorSource(modelId);
                if (ObjectUtil.isNotNull(model)) {
                    if (JSONUtil.isTypeJSON(new String(xmlBytes, StandardCharsets.UTF_8)) && ArrayUtil.isEmpty(ModelUtils.bpmnJsonToXmlBytes(xmlBytes))) {
                        zipName = "模型不能为空,请至少设计一条主线流程!";
                        zos.putNextEntry(new ZipEntry(zipName + ".txt"));
                        zos.write(zipName.getBytes(StandardCharsets.UTF_8));
                    } else if (ArrayUtil.isEmpty(xmlBytes)) {
                        zipName = "模型数据为空,请先设计流程定义模型,再进行部署!";
                        zos.putNextEntry(new ZipEntry(zipName + ".txt"));
                        zos.write(zipName.getBytes(StandardCharsets.UTF_8));
                    } else {
                        String fileName = model.getName() + "-" + model.getKey();
                        // åŽ‹ç¼©åŒ…æ–‡ä»¶å
                        zipName = fileName + ".zip";
                        // å°†xml添加到压缩包中(指定xml文件名:请假流程.bpmn20.xml
                        zos.putNextEntry(new ZipEntry(fileName + ".bpmn20.xml"));
                        zos.write(xmlBytes);
                    }
                }
            }
            response.setHeader("Content-Disposition",
                "attachment; filename=" + URLEncoder.encode(zipName, StandardCharsets.UTF_8) + ".zip");
            response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
            // åˆ·å‡ºå“åº”流
            response.flushBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * å¤åˆ¶æ¨¡åž‹
     *
     * @param modelBo æ¨¡åž‹æ•°æ®
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean copyModel(ModelBo modelBo) {
        try {
            String key = modelBo.getKey();
            if (StringUtils.isNotBlank(key)) {
                // æŸ¥è¯¢æ¨¡åž‹
                Model model = repositoryService.createModelQuery().modelId(modelBo.getId()).singleResult();
                if (ObjectUtil.isNotNull(model)) {
                    byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
                    List<Model> list = QueryUtils.modelQuery().modelKey(key).list();
                    if (CollUtil.isNotEmpty(list)) {
                        throw new ServiceException("模型KEY已存在!");
                    }
                    // æ ¡éªŒkey命名规范
                    if (!Validator.isMatchRegex(FlowConstant.MODEL_KEY_PATTERN, key)) {
                        throw new ServiceException("模型标识KEY只能字符或者下划线开头!");
                    }
                    // å¤åˆ¶æ¨¡åž‹æ•°æ®
                    Model newModel = repositoryService.newModel();
                    newModel.setKey(modelBo.getKey());
                    newModel.setName(modelBo.getName());
                    newModel.setCategory(modelBo.getCategoryCode());
                    newModel.setVersion(1);
                    newModel.setMetaInfo(modelBo.getDescription());
                    newModel.setTenantId(TenantHelper.getTenantId());
                    String xml = StrUtil.utf8Str(modelEditorSource);
                    BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xml);
                    Process mainProcess = bpmnModel.getMainProcess();
                    mainProcess.setId(modelBo.getKey());
                    mainProcess.setName(modelBo.getName());
                    byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
                    repositoryService.saveModel(newModel);
                    repositoryService.addModelEditorSource(newModel.getId(), xmlBytes);
                }
            }
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
        return true;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,438 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.WfCategory;
import org.dromara.workflow.domain.WfDefinitionConfig;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
import org.dromara.workflow.domain.bo.WfDefinitionConfigBo;
import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.workflow.mapper.WfDefinitionConfigMapper;
import org.dromara.workflow.service.IActProcessDefinitionService;
import org.dromara.workflow.service.IWfCategoryService;
import org.dromara.workflow.service.IWfDefinitionConfigService;
import org.dromara.workflow.service.IWfNodeConfigService;
import org.dromara.workflow.utils.ModelUtils;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.ProcessMigrationService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.deployer.ResourceNameUtil;
import org.flowable.engine.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
 * æµç¨‹å®šä¹‰ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@RequiredArgsConstructor
@Service
public class ActProcessDefinitionServiceImpl implements IActProcessDefinitionService {
    private final RepositoryService repositoryService;
    private final ProcessMigrationService processMigrationService;
    private final IWfCategoryService wfCategoryService;
    private final IWfDefinitionConfigService wfDefinitionConfigService;
    private final WfDefinitionConfigMapper wfDefinitionConfigMapper;
    private final IWfNodeConfigService wfNodeConfigService;
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param bo å‚æ•°
     * @return è¿”回分页列表
     */
    @Override
    public TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo bo, PageQuery pageQuery) {
        ProcessDefinitionQuery query = QueryUtils.definitionQuery();
        if (StringUtils.isNotEmpty(bo.getKey())) {
            query.processDefinitionKey(bo.getKey());
        }
        if (StringUtils.isNotEmpty(bo.getCategoryCode())) {
            query.processDefinitionCategory(bo.getCategoryCode());
        }
        if (StringUtils.isNotEmpty(bo.getName())) {
            query.processDefinitionNameLike("%" + bo.getName() + "%");
        }
        query.orderByDeploymentId().desc();
        // åˆ†é¡µæŸ¥è¯¢
        List<ProcessDefinitionVo> processDefinitionVoList = new ArrayList<>();
        List<ProcessDefinition> definitionList = query.latestVersion().listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        List<Deployment> deploymentList = null;
        if (CollUtil.isNotEmpty(definitionList)) {
            List<String> deploymentIds = StreamUtils.toList(definitionList, ProcessDefinition::getDeploymentId);
            deploymentList = QueryUtils.deploymentQuery(deploymentIds).list();
        }
        if (CollUtil.isNotEmpty(definitionList)) {
            List<String> ids = StreamUtils.toList(definitionList, ProcessDefinition::getId);
            List<WfDefinitionConfigVo> wfDefinitionConfigVos = wfDefinitionConfigService.queryList(ids);
            for (ProcessDefinition processDefinition : definitionList) {
                ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class);
                if (CollUtil.isNotEmpty(deploymentList)) {
                    // éƒ¨ç½²æ—¶é—´
                    deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> {
                        processDefinitionVo.setDeploymentTime(e.getDeploymentTime());
                    });
                }
                if (CollUtil.isNotEmpty(wfDefinitionConfigVos)) {
                    wfDefinitionConfigVos.stream().filter(e -> e.getDefinitionId().equals(processDefinition.getId())).findFirst().ifPresent(processDefinitionVo::setWfDefinitionConfigVo);
                }
                processDefinitionVoList.add(processDefinitionVo);
            }
        }
        // æ€»è®°å½•æ•°
        long total = query.count();
        TableDataInfo<ProcessDefinitionVo> build = TableDataInfo.build();
        build.setRows(processDefinitionVoList);
        build.setTotal(total);
        return build;
    }
    /**
     * æŸ¥è¯¢åŽ†å²æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @param key æµç¨‹å®šä¹‰key
     */
    @Override
    public List<ProcessDefinitionVo> getListByKey(String key) {
        List<ProcessDefinitionVo> processDefinitionVoList = new ArrayList<>();
        ProcessDefinitionQuery query = QueryUtils.definitionQuery();
        List<ProcessDefinition> definitionList = query.processDefinitionKey(key).list();
        List<Deployment> deploymentList = null;
        if (CollUtil.isNotEmpty(definitionList)) {
            List<String> deploymentIds = StreamUtils.toList(definitionList, ProcessDefinition::getDeploymentId);
            deploymentList = QueryUtils.deploymentQuery(deploymentIds).list();
        }
        if (CollUtil.isNotEmpty(definitionList)) {
            List<String> ids = StreamUtils.toList(definitionList, ProcessDefinition::getId);
            List<WfDefinitionConfigVo> wfDefinitionConfigVos = wfDefinitionConfigService.queryList(ids);
            for (ProcessDefinition processDefinition : definitionList) {
                ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class);
                if (CollUtil.isNotEmpty(deploymentList)) {
                    // éƒ¨ç½²æ—¶é—´
                    deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> {
                        processDefinitionVo.setDeploymentTime(e.getDeploymentTime());
                    });
                    if (CollUtil.isNotEmpty(wfDefinitionConfigVos)) {
                        wfDefinitionConfigVos.stream().filter(e -> e.getDefinitionId().equals(processDefinition.getId())).findFirst().ifPresent(processDefinitionVo::setWfDefinitionConfigVo);
                    }
                }
                processDefinitionVoList.add(processDefinitionVo);
            }
        }
        return CollUtil.reverse(processDefinitionVoList);
    }
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰å›¾ç‰‡
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @SneakyThrows
    @Override
    public String definitionImage(String processDefinitionId) {
        InputStream inputStream = repositoryService.getProcessDiagram(processDefinitionId);
        return Base64.encode(IoUtil.readBytes(inputStream));
    }
    /**
     * æŸ¥çœ‹æµç¨‹å®šä¹‰xml文件
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @Override
    public String definitionXml(String processDefinitionId) {
        StringBuilder xml = new StringBuilder();
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
        InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        xml.append(IoUtil.read(inputStream, StandardCharsets.UTF_8));
        return xml.toString();
    }
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     *
     * @param deploymentIds        éƒ¨ç½²id
     * @param processDefinitionIds æµç¨‹å®šä¹‰id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteDeployment(List<String> deploymentIds, List<String> processDefinitionIds) {
        try {
            List<HistoricProcessInstance> historicProcessInstances = QueryUtils.hisInstanceQuery().deploymentIdIn(deploymentIds).list();
            if (CollUtil.isNotEmpty(historicProcessInstances)) {
                Set<String> defIds = StreamUtils.toSet(historicProcessInstances, HistoricProcessInstance::getProcessDefinitionId);
                List<ProcessDefinition> processDefinitions = QueryUtils.definitionQuery().processDefinitionIds(defIds).list();
                if (CollUtil.isNotEmpty(processDefinitions)) {
                    Set<String> keys = StreamUtils.toSet(processDefinitions, ProcessDefinition::getKey);
                    throw new ServiceException("当前【" + String.join(",", keys) + "】流程定义已被使用不可删除!");
                }
            }
            //删除流程定义
            for (String deploymentId : deploymentIds) {
                repositoryService.deleteDeployment(deploymentId);
            }
            //删除流程定义配置
            wfDefinitionConfigService.deleteByDefIds(processDefinitionIds);
            //删除节点配置
            wfNodeConfigService.deleteByDefIds(processDefinitionIds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æ¿€æ´»æˆ–者挂起流程定义
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @Override
    public boolean updateDefinitionState(String processDefinitionId) {
        try {
            ProcessDefinition processDefinition = QueryUtils.definitionQuery()
                .processDefinitionId(processDefinitionId).singleResult();
            //将当前为挂起状态更新为激活状态
            //参数说明:参数1:流程定义id,参数2:是否激活(true是否级联对应流程实例,激活了则对应流程实例都可以审批),
            //参数3:什么时候激活,如果为null则立即激活,如果为具体时间则到达此时间后激活
            if (processDefinition.isSuspended()) {
                repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            } else {
                repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("操作失败:" + e.getMessage());
        }
    }
    /**
     * è¿ç§»æµç¨‹å®šä¹‰
     *
     * @param currentProcessDefinitionId å½“前流程定义id
     * @param fromProcessDefinitionId    éœ€è¦è¿ç§»åˆ°çš„æµç¨‹å®šä¹‰id
     */
    @Override
    public boolean migrationDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId) {
        try {
            // è¿ç§»éªŒè¯
            boolean migrationValid = processMigrationService.createProcessInstanceMigrationBuilder()
                .migrateToProcessDefinition(currentProcessDefinitionId)
                .validateMigrationOfProcessInstances(fromProcessDefinitionId)
                .isMigrationValid();
            if (!migrationValid) {
                throw new ServiceException("流程定义差异过大无法迁移,请修改流程图");
            }
            // å·²ç»“束的流程实例不会迁移
            processMigrationService.createProcessInstanceMigrationBuilder()
                .migrateToProcessDefinition(currentProcessDefinitionId)
                .migrateProcessInstances(fromProcessDefinitionId);
            return true;
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æµç¨‹å®šä¹‰è½¬æ¢ä¸ºæ¨¡åž‹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    @Override
    public boolean convertToModel(String processDefinitionId) {
        ProcessDefinition pd = QueryUtils.definitionQuery()
            .processDefinitionId(processDefinitionId).singleResult();
        InputStream inputStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), pd.getResourceName());
        ModelQuery query = QueryUtils.modelQuery();
        Model model = query.modelKey(pd.getKey()).singleResult();
        try {
            if (ObjectUtil.isNotNull(model)) {
                repositoryService.addModelEditorSource(model.getId(), IoUtil.readBytes(inputStream));
            } else {
                Model modelData = repositoryService.newModel();
                modelData.setKey(pd.getKey());
                modelData.setName(pd.getName());
                modelData.setTenantId(pd.getTenantId());
                repositoryService.saveModel(modelData);
                repositoryService.addModelEditorSource(modelData.getId(), IoUtil.readBytes(inputStream));
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * é€šè¿‡zip或xml部署流程定义
     *
     * @param file         æ–‡ä»¶
     * @param categoryCode åˆ†ç±»
     */
    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deployByFile(MultipartFile file, String categoryCode) {
        WfCategory wfCategory = wfCategoryService.queryByCategoryCode(categoryCode);
        if (wfCategory == null) {
            throw new ServiceException("流程分类不存在");
        }
        // æ–‡ä»¶åŽç¼€å
        String suffix = FileUtil.extName(file.getOriginalFilename());
        InputStream inputStream = file.getInputStream();
        if (FlowConstant.ZIP.equalsIgnoreCase(suffix)) {
            ZipInputStream zipInputStream = null;
            try {
                zipInputStream = new ZipInputStream(inputStream);
                ZipEntry zipEntry;
                while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                    String filename = zipEntry.getName();
                    String[] splitFilename = filename.substring(0, filename.lastIndexOf(".")).split("-");
                    //流程名称
                    String processName = splitFilename[0];
                    //流程key
                    String processKey = splitFilename[1];
                    ProcessDefinition oldProcessDefinition = QueryUtils.definitionQuery().processDefinitionKey(processKey).latestVersion().singleResult();
                    DeploymentBuilder builder = repositoryService.createDeployment();
                    Deployment deployment = builder.addInputStream(filename, zipInputStream)
                        .tenantId(TenantHelper.getTenantId())
                        .name(processName).key(processKey).category(categoryCode).deploy();
                    ProcessDefinition definition = QueryUtils.definitionQuery().deploymentId(deployment.getId()).singleResult();
                    repositoryService.setProcessDefinitionCategory(definition.getId(), categoryCode);
                    setWfConfig(oldProcessDefinition, definition);
                    zipInputStream.closeEntry();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                if (zipInputStream != null) {
                    zipInputStream.close();
                }
            }
            //初始化配置数据(demo使用,不用可删除)
            initWfDefConfig();
        } else {
            String originalFilename = file.getOriginalFilename();
            String bpmnResourceSuffix = ResourceNameUtil.BPMN_RESOURCE_SUFFIXES[0];
            if (originalFilename.contains(bpmnResourceSuffix)) {
                // æ–‡ä»¶å = æµç¨‹åç§°-流程key
                String[] splitFilename = originalFilename.substring(0, originalFilename.lastIndexOf(".")).split("-");
                if (splitFilename.length < 2) {
                    throw new ServiceException("文件名 = æµç¨‹åç§°-流程KEY");
                }
                //流程名称
                String processName = splitFilename[0];
                //流程key
                String processKey = splitFilename[1];
                ProcessDefinition oldProcessDefinition = QueryUtils.definitionQuery().processDefinitionKey(processKey).latestVersion().singleResult();
                DeploymentBuilder builder = repositoryService.createDeployment();
                Deployment deployment = builder.addInputStream(originalFilename, inputStream)
                    .tenantId(TenantHelper.getTenantId())
                    .name(processName).key(processKey).category(categoryCode).deploy();
                // æ›´æ–°åˆ†ç±»
                ProcessDefinition definition = QueryUtils.definitionQuery().deploymentId(deployment.getId()).singleResult();
                repositoryService.setProcessDefinitionCategory(definition.getId(), categoryCode);
                setWfConfig(oldProcessDefinition, definition);
            } else {
                throw new ServiceException("文件类型上传错误!");
            }
        }
    }
    /**
     * åˆå§‹åŒ–配置数据(demo使用,不用可删除)
     */
    private void initWfDefConfig() {
        List<WfDefinitionConfig> wfDefinitionConfigs = wfDefinitionConfigMapper.selectList();
        if (CollUtil.isEmpty(wfDefinitionConfigs)) {
            ProcessDefinition processDefinition = QueryUtils.definitionQuery().processDefinitionKey("leave1").latestVersion().singleResult();
            if (processDefinition != null) {
                WfDefinitionConfigBo wfDefinitionConfigBo = new WfDefinitionConfigBo();
                wfDefinitionConfigBo.setDefinitionId(processDefinition.getId());
                wfDefinitionConfigBo.setProcessKey(processDefinition.getKey());
                wfDefinitionConfigBo.setTableName("test_leave");
                wfDefinitionConfigBo.setVersion(processDefinition.getVersion());
                wfDefinitionConfigService.saveOrUpdate(wfDefinitionConfigBo);
            }
        }
    }
    /**
     * è®¾ç½®è¡¨å•内容
     *
     * @param oldProcessDefinition éƒ¨ç½²å‰æœ€æ–°æµç¨‹å®šä¹‰
     * @param definition           éƒ¨ç½²åŽæœ€æ–°æµç¨‹å®šä¹‰
     */
    private void setWfConfig(ProcessDefinition oldProcessDefinition, ProcessDefinition definition) {
        //更新流程定义表单
        if (oldProcessDefinition != null) {
            WfDefinitionConfigVo definitionVo = wfDefinitionConfigService.getByDefId(oldProcessDefinition.getId());
            if (definitionVo != null) {
                wfDefinitionConfigService.deleteByDefIds(Collections.singletonList(oldProcessDefinition.getId()));
                WfDefinitionConfigBo wfDefinitionConfigBo = new WfDefinitionConfigBo();
                wfDefinitionConfigBo.setDefinitionId(definition.getId());
                wfDefinitionConfigBo.setProcessKey(definition.getKey());
                wfDefinitionConfigBo.setTableName(definitionVo.getTableName());
                wfDefinitionConfigBo.setVersion(definition.getVersion());
                wfDefinitionConfigBo.setRemark(definitionVo.getRemark());
                wfDefinitionConfigService.saveOrUpdate(wfDefinitionConfigBo);
            }
        }
        //更新流程节点配置表单
        List<UserTask> userTasks = ModelUtils.getUserTaskFlowElements(definition.getId());
        UserTask applyUserTask = ModelUtils.getApplyUserTask(definition.getId());
        List<WfNodeConfig> wfNodeConfigList = new ArrayList<>();
        for (UserTask userTask : userTasks) {
            if (StringUtils.isNotBlank(userTask.getFormKey()) && userTask.getFormKey().contains(StrUtil.COLON)) {
                WfNodeConfig wfNodeConfig = new WfNodeConfig();
                wfNodeConfig.setNodeId(userTask.getId());
                wfNodeConfig.setNodeName(userTask.getName());
                wfNodeConfig.setDefinitionId(definition.getId());
                String[] split = userTask.getFormKey().split(StrUtil.COLON);
                wfNodeConfig.setFormType(split[0]);
                wfNodeConfig.setFormId(Long.valueOf(split[1]));
                wfNodeConfig.setApplyUserTask(applyUserTask.getId().equals(userTask.getId()) ? FlowConstant.TRUE : FlowConstant.FALSE);
                wfNodeConfigList.add(wfNodeConfig);
            }
        }
        if (CollUtil.isNotEmpty(wfNodeConfigList)) {
            wfNodeConfigService.saveOrUpdate(wfNodeConfigList);
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,683 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.BusinessStatusEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiProcinst;
import org.dromara.workflow.domain.bo.ProcessInstanceBo;
import org.dromara.workflow.domain.bo.ProcessInvalidBo;
import org.dromara.workflow.domain.bo.TaskUrgingBo;
import org.dromara.workflow.domain.vo.*;
import org.dromara.workflow.flowable.CustomDefaultProcessDiagramGenerator;
import org.dromara.workflow.flowable.cmd.DeleteExecutionCmd;
import org.dromara.workflow.flowable.cmd.ExecutionChildByExecutionIdCmd;
import org.dromara.workflow.flowable.strategy.FlowEventStrategy;
import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
import org.dromara.workflow.service.IActHiProcinstService;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.dromara.workflow.service.IWfNodeConfigService;
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.QueryUtils;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.bpmn.model.*;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
 * æµç¨‹å®žä¾‹ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class ActProcessInstanceServiceImpl implements IActProcessInstanceService {
    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final TaskService taskService;
    private final IActHiProcinstService actHiProcinstService;
    private final ManagementService managementService;
    private final FlowEventStrategy flowEventStrategy;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
    private final IWfNodeConfigService wfNodeConfigService;
    @Value("${flowable.activity-font-name}")
    private String activityFontName;
    @Value("${flowable.label-font-name}")
    private String labelFontName;
    @Value("${flowable.annotation-font-name}")
    private String annotationFontName;
    /**
     * åˆ†é¡µæŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹
     *
     * @param bo å‚æ•°
     */
    @Override
    public TableDataInfo<ProcessInstanceVo> getPageByRunning(ProcessInstanceBo bo, PageQuery pageQuery) {
        List<ProcessInstanceVo> list = new ArrayList<>();
        ProcessInstanceQuery query = QueryUtils.instanceQuery();
        if (StringUtils.isNotBlank(bo.getName())) {
            query.processInstanceNameLikeIgnoreCase("%" + bo.getName() + "%");
        }
        if (StringUtils.isNotBlank(bo.getKey())) {
            query.processDefinitionKey(bo.getKey());
        }
        if (StringUtils.isNotBlank(bo.getStartUserId())) {
            query.startedBy(bo.getStartUserId());
        }
        if (StringUtils.isNotBlank(bo.getBusinessKey())) {
            query.processInstanceBusinessKey(bo.getBusinessKey());
        }
        if (StringUtils.isNotBlank(bo.getCategoryCode())) {
            query.processDefinitionCategory(bo.getCategoryCode());
        }
        query.orderByStartTime().desc();
        List<ProcessInstance> processInstances = query.listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        for (ProcessInstance processInstance : processInstances) {
            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class);
            processInstanceVo.setIsSuspended(processInstance.isSuspended());
            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus()));
            list.add(processInstanceVo);
        }
        if (CollUtil.isNotEmpty(list)) {
            List<String> processDefinitionIds = StreamUtils.toList(list, ProcessInstanceVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (ProcessInstanceVo processInstanceVo : list) {
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(processInstanceVo.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(processInstanceVo::setWfNodeConfigVo);
                }
            }
        }
        long count = query.count();
        TableDataInfo<ProcessInstanceVo> build = TableDataInfo.build();
        build.setRows(list);
        build.setTotal(count);
        return build;
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å·²ç»“束的流程实例
     *
     * @param bo å‚æ•°
     */
    @Override
    public TableDataInfo<ProcessInstanceVo> getPageByFinish(ProcessInstanceBo bo, PageQuery pageQuery) {
        List<ProcessInstanceVo> list = new ArrayList<>();
        HistoricProcessInstanceQuery query = QueryUtils.hisInstanceQuery()
            .finished().orderByProcessInstanceEndTime().desc();
        if (StringUtils.isNotEmpty(bo.getName())) {
            query.processInstanceNameLikeIgnoreCase("%" + bo.getName() + "%");
        }
        if (StringUtils.isNotBlank(bo.getKey())) {
            query.processDefinitionKey(bo.getKey());
        }
        if (StringUtils.isNotEmpty(bo.getStartUserId())) {
            query.startedBy(bo.getStartUserId());
        }
        if (StringUtils.isNotBlank(bo.getBusinessKey())) {
            query.processInstanceBusinessKey(bo.getBusinessKey());
        }
        if (StringUtils.isNotBlank(bo.getCategoryCode())) {
            query.processDefinitionCategory(bo.getCategoryCode());
        }
        List<HistoricProcessInstance> historicProcessInstances = query.listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(historicProcessInstance, ProcessInstanceVo.class);
            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(historicProcessInstance.getBusinessStatus()));
            list.add(processInstanceVo);
        }
        if (CollUtil.isNotEmpty(list)) {
            List<String> processDefinitionIds = StreamUtils.toList(list, ProcessInstanceVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (ProcessInstanceVo processInstanceVo : list) {
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(processInstanceVo.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(processInstanceVo::setWfNodeConfigVo);
                }
            }
        }
        long count = query.count();
        TableDataInfo<ProcessInstanceVo> build = TableDataInfo.build();
        build.setRows(list);
        build.setTotal(count);
        return build;
    }
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @SneakyThrows
    @Override
    public String getHistoryImage(String processInstanceId) {
        String processDefinitionId;
        // èŽ·å–å½“å‰çš„æµç¨‹å®žä¾‹
        ProcessInstance processInstance = QueryUtils.instanceQuery(processInstanceId).singleResult();
        // å¦‚果流程已经结束,则得到结束节点
        if (Objects.isNull(processInstance)) {
            HistoricProcessInstance pi = QueryUtils.hisInstanceQuery(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {
            // æ ¹æ®æµç¨‹å®žä¾‹ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = QueryUtils.instanceQuery(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }
        // èŽ·å¾—æ´»åŠ¨çš„èŠ‚ç‚¹
        List<HistoricActivityInstance> highLightedFlowList = QueryUtils.hisActivityInstanceQuery(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        List<String> highLightedFlows = new ArrayList<>();
        List<String> highLightedNodes = new ArrayList<>();
        //高亮
        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
            if (FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType())) {
                //高亮线
                highLightedFlows.add(tempActivity.getActivityId());
            } else {
                //高亮节点
                if (tempActivity.getEndTime() == null) {
                    highLightedNodes.add(Color.RED.toString() + tempActivity.getActivityId());
                } else {
                    highLightedNodes.add(tempActivity.getActivityId());
                }
            }
        }
        List<String> highLightedNodeList = new ArrayList<>();
        //运行中的节点
        List<String> redNodeCollect = StreamUtils.filter(highLightedNodes, e -> e.contains(Color.RED.toString()));
        //排除与运行中相同的节点
        for (String nodeId : highLightedNodes) {
            if (!nodeId.contains(Color.RED.toString()) && !redNodeCollect.contains(Color.RED + nodeId)) {
                highLightedNodeList.add(nodeId);
            }
        }
        highLightedNodeList.addAll(redNodeCollect);
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        CustomDefaultProcessDiagramGenerator diagramGenerator = new CustomDefaultProcessDiagramGenerator();
        InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodeList, highLightedFlows, activityFontName, labelFontName, annotationFontName, null, 1.0, true);
        return Base64.encode(IoUtil.readBytes(inputStream));
    }
    /**
     * é€šè¿‡æµç¨‹å®žä¾‹id获取历史流程图运行中,历史等节点
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @Override
    public Map<String, Object> getHistoryList(String processInstanceId) {
        Map<String, Object> map = new HashMap<>();
        List<Map<String, Object>> taskList = new ArrayList<>();
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery(processInstanceId).singleResult();
        StringBuilder xml = new StringBuilder();
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
        // èŽ·å–èŠ‚ç‚¹
        List<HistoricActivityInstance> highLightedFlowList = QueryUtils.hisActivityInstanceQuery(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
            Map<String, Object> task = new HashMap<>();
            if (!FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType()) &&
                !FlowConstant.PARALLEL_GATEWAY.equals(tempActivity.getActivityType()) &&
                !FlowConstant.EXCLUSIVE_GATEWAY.equals(tempActivity.getActivityType()) &&
                !FlowConstant.INCLUSIVE_GATEWAY.equals(tempActivity.getActivityType())
            ) {
                task.put("key", tempActivity.getActivityId());
                task.put("completed", tempActivity.getEndTime() != null);
                task.put("activityType", tempActivity.getActivityType());
                taskList.add(task);
            }
        }
        ProcessInstance processInstance = QueryUtils.instanceQuery(processInstanceId).singleResult();
        if (processInstance != null) {
            taskList = taskList.stream().filter(e -> !e.get("activityType").equals(FlowConstant.END_EVENT)).collect(Collectors.toList());
        }
        //查询出运行中节点
        List<Map<String, Object>> runtimeNodeList = taskList.stream().filter(e -> !(Boolean) e.get("completed")).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(runtimeNodeList)) {
            Iterator<Map<String, Object>> iterator = taskList.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> next = iterator.next();
                runtimeNodeList.stream().filter(t -> t.get("key").equals(next.get("key")) && (Boolean) next.get("completed")).findFirst().ifPresent(t -> iterator.remove());
            }
        }
        map.put("taskList", taskList);
        List<ActHistoryInfoVo> historyTaskList = getHistoryTaskList(processInstanceId);
        map.put("historyList", historyTaskList);
        InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        xml.append(IoUtil.read(inputStream, StandardCharsets.UTF_8));
        map.put("xml", xml.toString());
        return map;
    }
    /**
     * èŽ·å–åŽ†å²ä»»åŠ¡èŠ‚ç‚¹ä¿¡æ¯
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    private List<ActHistoryInfoVo> getHistoryTaskList(String processInstanceId) {
        //查询任务办理记录
        List<HistoricTaskInstance> list = QueryUtils.hisTaskInstanceQuery(processInstanceId).orderByHistoricTaskInstanceEndTime().desc().list();
        list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed());
        List<ActHistoryInfoVo> actHistoryInfoVoList = new ArrayList<>();
        for (HistoricTaskInstance historicTaskInstance : list) {
            ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo();
            BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo);
            actHistoryInfoVo.setStatus(actHistoryInfoVo.getEndTime() == null ? "待处理" : "已处理");
            if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) {
                actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis()));
            }
            actHistoryInfoVoList.add(actHistoryInfoVo);
        }
        List<ActHistoryInfoVo> historyInfoVoList = new ArrayList<>();
        Map<String, List<ActHistoryInfoVo>> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey);
        for (Map.Entry<String, List<ActHistoryInfoVo>> entry : groupByKey.entrySet()) {
            ActHistoryInfoVo historyInfoVo = new ActHistoryInfoVo();
            BeanUtils.copyProperties(entry.getValue().get(0), historyInfoVo);
            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst()
                .ifPresent(e -> {
                    historyInfoVo.setStatus("待处理");
                    historyInfoVo.setStartTime(e.getStartTime());
                    historyInfoVo.setEndTime(null);
                    historyInfoVo.setRunDuration(null);
                    if (ObjectUtil.isEmpty(e.getAssignee())) {
                        ParticipantVo participantVo = WorkflowUtils.getCurrentTaskParticipant(e.getId());
                        if (ObjectUtil.isNotEmpty(participantVo) && CollUtil.isNotEmpty(participantVo.getCandidate())) {
                            historyInfoVo.setAssignee(StreamUtils.join(participantVo.getCandidate(), Convert::toStr));
                        }
                    }
                });
            historyInfoVoList.add(historyInfoVo);
        }
        return historyInfoVoList;
    }
    /**
     * èŽ·å–å®¡æ‰¹è®°å½•
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @Override
    public List<ActHistoryInfoVo> getHistoryRecord(String processInstanceId) {
        // æŸ¥è¯¢ä»»åŠ¡åŠžç†è®°å½•
        List<HistoricTaskInstance> list = QueryUtils.hisTaskInstanceQuery(processInstanceId).orderByHistoricTaskInstanceEndTime().desc().list();
        list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed());
        List<ActHistoryInfoVo> actHistoryInfoVoList = new ArrayList<>();
        List<Comment> processInstanceComments = taskService.getProcessInstanceComments(processInstanceId);
        //附件
        List<Attachment> attachmentList = taskService.getProcessInstanceAttachments(processInstanceId);
        for (HistoricTaskInstance historicTaskInstance : list) {
            ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo();
            BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo);
            if (actHistoryInfoVo.getEndTime() == null) {
                actHistoryInfoVo.setStatus(TaskStatusEnum.WAITING.getStatus());
                actHistoryInfoVo.setStatusName(TaskStatusEnum.WAITING.getDesc());
            }
            if (CollUtil.isNotEmpty(processInstanceComments)) {
                processInstanceComments.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).findFirst().ifPresent(e -> {
                    actHistoryInfoVo.setComment(e.getFullMessage());
                    actHistoryInfoVo.setStatus(e.getType());
                    actHistoryInfoVo.setStatusName(TaskStatusEnum.findByStatus(e.getType()));
                });
            }
            if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) {
                actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis()));
            }
            //附件
            if (CollUtil.isNotEmpty(attachmentList)) {
                List<Attachment> attachments = attachmentList.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).collect(Collectors.toList());
                if (CollUtil.isNotEmpty(attachments)) {
                    actHistoryInfoVo.setAttachmentList(attachments);
                }
            }
            //设置人员id
            if (ObjectUtil.isEmpty(historicTaskInstance.getAssignee())) {
                ParticipantVo participantVo = WorkflowUtils.getCurrentTaskParticipant(historicTaskInstance.getId());
                if (ObjectUtil.isNotEmpty(participantVo) && CollUtil.isNotEmpty(participantVo.getCandidate())) {
                    actHistoryInfoVo.setAssignee(StreamUtils.join(participantVo.getCandidate(), Convert::toStr));
                }
            }
            actHistoryInfoVoList.add(actHistoryInfoVo);
        }
        // å®¡æ‰¹è®°å½•
        Map<String, List<ActHistoryInfoVo>> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey);
        for (Map.Entry<String, List<ActHistoryInfoVo>> entry : groupByKey.entrySet()) {
            ActHistoryInfoVo actHistoryInfoVo = BeanUtil.toBean(entry.getValue().get(0), ActHistoryInfoVo.class);
            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() != null).findFirst()
                .ifPresent(e -> {
                    actHistoryInfoVo.setStatus("已处理");
                    actHistoryInfoVo.setStartTime(e.getStartTime());
                });
            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst()
                .ifPresent(e -> {
                    actHistoryInfoVo.setStatus("待处理");
                    actHistoryInfoVo.setStartTime(e.getStartTime());
                    actHistoryInfoVo.setEndTime(null);
                    actHistoryInfoVo.setRunDuration(null);
                });
        }
        List<ActHistoryInfoVo> recordList = new ArrayList<>();
        // å¾…办理
        recordList.addAll(StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() == null));
        // å·²åŠžç†
        recordList.addAll(StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() != null));
        return recordList;
    }
    /**
     * ä»»åŠ¡å®Œæˆæ—¶é—´å¤„ç†
     *
     * @param time æ—¶é—´
     */
    private String getDuration(long time) {
        long day = time / (24 * 60 * 60 * 1000);
        long hour = (time / (60 * 60 * 1000) - day * 24);
        long minute = ((time / (60 * 1000)) - day * 24 * 60 - hour * 60);
        long second = (time / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);
        if (day > 0) {
            return day + "天" + hour + "小时" + minute + "分钟";
        }
        if (hour > 0) {
            return hour + "小时" + minute + "分钟";
        }
        if (minute > 0) {
            return minute + "分钟";
        }
        if (second > 0) {
            return second + "秒";
        } else {
            return 0 + "秒";
        }
    }
    /**
     * ä½œåºŸæµç¨‹å®žä¾‹ï¼Œä¸ä¼šåˆ é™¤åŽ†å²è®°å½•(删除运行中的实例)
     *
     * @param processInvalidBo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteRunInstance(ProcessInvalidBo processInvalidBo) {
        try {
            List<Task> list = QueryUtils.taskQuery(processInvalidBo.getProcessInstanceId()).list();
            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
            if (CollUtil.isNotEmpty(subTasks)) {
                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
            }
            String deleteReason = LoginHelper.getLoginUser().getNickname() + "作废了当前申请!";
            if (StringUtils.isNotBlank(processInvalidBo.getDeleteReason())) {
                deleteReason = LoginHelper.getLoginUser().getNickname() + "作废理由:" + processInvalidBo.getDeleteReason();
            }
            for (Task task : StreamUtils.filter(list, e -> StringUtils.isBlank(e.getParentTaskId()))) {
                taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.INVALID.getStatus(), deleteReason);
            }
            HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery(processInvalidBo.getProcessInstanceId()).singleResult();
            BusinessStatusEnum.checkInvalidStatus(historicProcessInstance.getBusinessStatus());
            runtimeService.updateBusinessStatus(processInvalidBo.getProcessInstanceId(), BusinessStatusEnum.INVALID.getStatus());
            runtimeService.deleteProcessInstance(processInvalidBo.getProcessInstanceId(), deleteReason);
            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey());
            if (processHandler != null) {
                processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.INVALID.getStatus(), false);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteRunAndHisInstance(List<String> processInstanceIds) {
        try {
            // 1.删除运行中流程实例
            List<Task> list = QueryUtils.taskQuery(processInstanceIds).list();
            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
            if (CollUtil.isNotEmpty(subTasks)) {
                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
            }
            runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "删除了当前流程申请");
            // 2.删除历史记录
            List<HistoricProcessInstance> historicProcessInstanceList = QueryUtils.hisInstanceQuery(new HashSet<>(processInstanceIds)).list();
            if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) {
                historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
            }
            wfTaskBackNodeService.deleteByInstanceIds(processInstanceIds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æŒ‰ç…§ä¸šåŠ¡id删除 è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param businessKeys ä¸šåŠ¡id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteRunAndHisInstanceByBusinessKeys(List<String> businessKeys) {
        try {
            // 1.删除运行中流程实例
            List<ActHiProcinst> actHiProcinsts = actHiProcinstService.selectByBusinessKeyIn(businessKeys);
            if (CollUtil.isEmpty(actHiProcinsts)) {
                log.warn("当前业务ID:{}查询到流程实例为空!", businessKeys);
                return false;
            }
            List<String> processInstanceIds = StreamUtils.toList(actHiProcinsts, ActHiProcinst::getId);
            List<Task> list = QueryUtils.taskQuery(processInstanceIds).list();
            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
            if (CollUtil.isNotEmpty(subTasks)) {
                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
            }
            runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "删除了当前流程申请");
            // 2.删除历史记录
            List<HistoricProcessInstance> historicProcessInstanceList = QueryUtils.hisInstanceQuery(new HashSet<>(processInstanceIds)).list();
            if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) {
                historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
            }
            wfTaskBackNodeService.deleteByInstanceIds(processInstanceIds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * å·²å®Œæˆçš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param processInstanceIds æµç¨‹å®žä¾‹id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteFinishAndHisInstance(List<String> processInstanceIds) {
        try {
            historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
            wfTaskBackNodeService.deleteByInstanceIds(processInstanceIds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æ’¤é”€æµç¨‹ç”³è¯·
     *
     * @param processInstanceId æµç¨‹å®žä¾‹id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancelProcessApply(String processInstanceId) {
        try {
            ProcessInstance processInstance = QueryUtils.instanceQuery(processInstanceId)
                .startedBy(String.valueOf(LoginHelper.getUserId())).singleResult();
            if (ObjectUtil.isNull(processInstance)) {
                throw new ServiceException("您不是流程发起人,撤销失败!");
            }
            if (processInstance.isSuspended()) {
                throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
            }
            BusinessStatusEnum.checkCancelStatus(processInstance.getBusinessStatus());
            List<Task> taskList = QueryUtils.taskQuery(processInstanceId).list();
            for (Task task : taskList) {
                taskService.setAssignee(task.getId(), null);
                taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.CANCEL.getStatus(), LoginHelper.getLoginUser().getNickname() + ":撤销申请");
            }
            HistoricTaskInstance historicTaskInstance = QueryUtils.hisTaskInstanceQuery().finished().orderByHistoricTaskInstanceEndTime().asc().list().get(0);
            List<String> nodeIds = StreamUtils.toList(taskList, Task::getTaskDefinitionKey);
            runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId)
                .moveActivityIdsToSingleActivityId(nodeIds, historicTaskInstance.getTaskDefinitionKey()).changeState();
            Task task = QueryUtils.taskQuery(processInstanceId).list().get(0);
            taskService.setAssignee(task.getId(), historicTaskInstance.getAssignee());
            //获取并行网关执行后保留的执行实例数据
            ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId());
            List<ExecutionEntity> executionEntities = managementService.executeCommand(childByExecutionIdCmd);
            //删除流程实例垃圾数据
            for (ExecutionEntity executionEntity : executionEntities) {
                DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId());
                managementService.executeCommand(deleteExecutionCmd);
            }
            runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.CANCEL.getStatus());
            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
            if (processHandler != null) {
                processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.CANCEL.getStatus(), false);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("撤销失败:" + e.getMessage());
        }
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å½“前登录人单据
     *
     * @param bo å‚æ•°
     */
    @Override
    public TableDataInfo<ProcessInstanceVo> getPageByCurrent(ProcessInstanceBo bo, PageQuery pageQuery) {
        List<ProcessInstanceVo> list = new ArrayList<>();
        HistoricProcessInstanceQuery query = QueryUtils.hisInstanceQuery();
        query.startedBy(String.valueOf(LoginHelper.getUserId()));
        if (StringUtils.isNotBlank(bo.getName())) {
            query.processInstanceNameLikeIgnoreCase("%" + bo.getName() + "%");
        }
        if (StringUtils.isNotBlank(bo.getKey())) {
            query.processDefinitionKey(bo.getKey());
        }
        if (StringUtils.isNotBlank(bo.getBusinessKey())) {
            query.processInstanceBusinessKey(bo.getBusinessKey());
        }
        if (StringUtils.isNotBlank(bo.getCategoryCode())) {
            query.processDefinitionCategory(bo.getCategoryCode());
        }
        query.orderByProcessInstanceStartTime().desc();
        List<HistoricProcessInstance> historicProcessInstanceList = query.listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        List<TaskVo> taskVoList = new ArrayList<>();
        if (CollUtil.isNotEmpty(historicProcessInstanceList)) {
            List<String> processInstanceIds = StreamUtils.toList(historicProcessInstanceList, HistoricProcessInstance::getId);
            List<Task> taskList = QueryUtils.taskQuery(processInstanceIds).list();
            for (Task task : taskList) {
                taskVoList.add(BeanUtil.toBean(task, TaskVo.class));
            }
        }
        for (HistoricProcessInstance processInstance : historicProcessInstanceList) {
            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class);
            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus()));
            if (CollUtil.isNotEmpty(taskVoList)) {
                List<TaskVo> collect = StreamUtils.filter(taskVoList, e -> e.getProcessInstanceId().equals(processInstance.getId()));
                processInstanceVo.setTaskVoList(CollUtil.isNotEmpty(collect) ? collect : Collections.emptyList());
            }
            list.add(processInstanceVo);
        }
        if (CollUtil.isNotEmpty(list)) {
            List<String> processDefinitionIds = StreamUtils.toList(list, ProcessInstanceVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (ProcessInstanceVo processInstanceVo : list) {
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(processInstanceVo.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(processInstanceVo::setWfNodeConfigVo);
                }
            }
        }
        long count = query.count();
        TableDataInfo<ProcessInstanceVo> build = TableDataInfo.build();
        build.setRows(list);
        build.setTotal(count);
        return build;
    }
    /**
     * ä»»åŠ¡å‚¬åŠž(给当前任务办理人发送站内信,邮件,短信等)
     *
     * @param taskUrgingBo ä»»åŠ¡å‚¬åŠž
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean taskUrging(TaskUrgingBo taskUrgingBo) {
        try {
            ProcessInstance processInstance = QueryUtils.instanceQuery(taskUrgingBo.getProcessInstanceId()).singleResult();
            if (processInstance == null) {
                throw new ServiceException("任务已结束!");
            }
            String message = taskUrgingBo.getMessage();
            if (StringUtils.isBlank(message)) {
                message = "您的【" + processInstance.getName() + "】单据还未审批,请您及时处理。";
            }
            List<Task> list = QueryUtils.taskQuery(taskUrgingBo.getProcessInstanceId()).list();
            WorkflowUtils.sendMessage(list, processInstance.getName(), taskUrgingBo.getMessageType(), message);
        } catch (ServiceException e) {
            throw new ServiceException(e.getMessage());
        }
        return true;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,872 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.BusinessStatusEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiTaskinst;
import org.dromara.workflow.domain.WfTaskBackNode;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.*;
import org.dromara.workflow.flowable.cmd.*;
import org.dromara.workflow.flowable.strategy.FlowEventStrategy;
import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler;
import org.dromara.workflow.mapper.ActHiTaskinstMapper;
import org.dromara.workflow.mapper.ActTaskMapper;
import org.dromara.workflow.service.IActTaskService;
import org.dromara.workflow.service.IWfDefinitionConfigService;
import org.dromara.workflow.service.IWfNodeConfigService;
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.ModelUtils;
import org.dromara.workflow.utils.QueryUtils;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
import static org.dromara.workflow.common.constant.FlowConstant.*;
/**
 * ä»»åŠ¡ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@RequiredArgsConstructor
@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;
    private final FlowEventStrategy flowEventStrategy;
    private final ActTaskMapper actTaskMapper;
    private final IWfTaskBackNodeService wfTaskBackNodeService;
    private final ActHiTaskinstMapper actHiTaskinstMapper;
    private final IWfNodeConfigService wfNodeConfigService;
    private final IWfDefinitionConfigService wfDefinitionConfigService;
    private final UserService userService;
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> startWorkFlow(StartProcessBo startProcessBo) {
        Map<String, Object> map = new HashMap<>();
        if (StringUtils.isBlank(startProcessBo.getBusinessKey())) {
            throw new ServiceException("启动工作流时必须包含业务ID");
        }
        // åˆ¤æ–­å½“前业务是否启动过流程
        HistoricProcessInstanceQuery query = QueryUtils.hisInstanceQuery();
        HistoricProcessInstance historicProcessInstance = query.processInstanceBusinessKey(startProcessBo.getBusinessKey()).singleResult();
        if (ObjectUtil.isNotEmpty(historicProcessInstance)) {
            BusinessStatusEnum.checkStartStatus(historicProcessInstance.getBusinessStatus());
        }
        List<Task> taskResult = QueryUtils.taskQuery().processInstanceBusinessKey(startProcessBo.getBusinessKey()).list();
        if (CollUtil.isNotEmpty(taskResult)) {
            if (CollUtil.isNotEmpty(startProcessBo.getVariables())) {
                taskService.setVariables(taskResult.get(0).getId(), startProcessBo.getVariables());
            }
            map.put(PROCESS_INSTANCE_ID, taskResult.get(0).getProcessInstanceId());
            map.put("taskId", taskResult.get(0).getId());
            return map;
        }
        WfDefinitionConfigVo wfDefinitionConfigVo = wfDefinitionConfigService.getByTableNameLastVersion(startProcessBo.getTableName());
        if (wfDefinitionConfigVo == null) {
            throw new ServiceException("请到流程定义绑定业务表名与流程KEY!");
        }
        // è®¾ç½®å¯åŠ¨äºº
        identityService.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId()));
        Authentication.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId()));
        // å¯åŠ¨æµç¨‹å®žä¾‹ï¼ˆæäº¤ç”³è¯·ï¼‰
        Map<String, Object> variables = startProcessBo.getVariables();
        // å¯åŠ¨è·³è¿‡è¡¨è¾¾å¼
        variables.put(FLOWABLE_SKIP_EXPRESSION_ENABLED, true);
        // æµç¨‹å‘起人
        variables.put(INITIATOR, (String.valueOf(LoginHelper.getUserId())));
        ProcessInstance pi;
        try {
            if (TenantHelper.isEnable()) {
                pi = runtimeService.startProcessInstanceByKeyAndTenantId(wfDefinitionConfigVo.getProcessKey(), startProcessBo.getBusinessKey(), variables, TenantHelper.getTenantId());
            } else {
                pi = runtimeService.startProcessInstanceByKey(wfDefinitionConfigVo.getProcessKey(), startProcessBo.getBusinessKey(), variables);
            }
        } catch (FlowableObjectNotFoundException e) {
            throw new ServiceException("找不到当前【" + wfDefinitionConfigVo.getProcessKey() + "】流程定义!");
        }
        // å°†æµç¨‹å®šä¹‰åç§° ä½œä¸º æµç¨‹å®žä¾‹åç§°
        runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName());
        // ç”³è¯·äººæ‰§è¡Œæµç¨‹
        List<Task> taskList = QueryUtils.taskQuery(pi.getId()).list();
        if (taskList.size() > 1) {
            throw new ServiceException("请检查流程第一个环节是否为申请人!");
        }
        runtimeService.updateBusinessStatus(pi.getProcessInstanceId(), BusinessStatusEnum.DRAFT.getStatus());
        taskService.setAssignee(taskList.get(0).getId(), LoginHelper.getUserId().toString());
        taskService.setVariable(taskList.get(0).getId(), PROCESS_INSTANCE_ID, pi.getProcessInstanceId());
        taskService.setVariable(taskList.get(0).getId(), BUSINESS_KEY, pi.getBusinessKey());
        map.put("processInstanceId", pi.getProcessInstanceId());
        map.put("taskId", taskList.get(0).getId());
        return map;
    }
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean completeTask(CompleteTaskBo completeTaskBo) {
        try {
            List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
            String userId = String.valueOf(LoginHelper.getUserId());
            TaskQuery taskQuery = QueryUtils.taskQuery();
            taskQuery.taskId(completeTaskBo.getTaskId()).taskCandidateOrAssigned(userId);
            if (CollUtil.isNotEmpty(roles)) {
                List<String> groupIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId()));
                taskQuery.taskCandidateGroupIn(groupIds);
            }
            Task task = taskQuery.singleResult();
            if (task == null) {
                throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
            }
            if (task.isSuspended()) {
                throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
            }
            ProcessInstance processInstance = QueryUtils.instanceQuery(task.getProcessInstanceId()).singleResult();
            //办理委托任务
            if (ObjectUtil.isNotEmpty(task.getDelegationState()) && FlowConstant.PENDING.equals(task.getDelegationState().name())) {
                taskService.resolveTask(completeTaskBo.getTaskId());
                TaskEntity newTask = WorkflowUtils.createNewTask(task);
                taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), StringUtils.isNotBlank(completeTaskBo.getMessage()) ? completeTaskBo.getMessage() : StrUtil.EMPTY);
                taskService.complete(newTask.getId());
                return true;
            }
            //附件上传
            AttachmentCmd attachmentCmd = new AttachmentCmd(completeTaskBo.getFileId(), task.getId(), task.getProcessInstanceId());
            managementService.executeCommand(attachmentCmd);
            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
            String businessStatus = WorkflowUtils.getBusinessStatus(task.getProcessInstanceId());
            if (BusinessStatusEnum.DRAFT.getStatus().equals(businessStatus) || BusinessStatusEnum.BACK.getStatus().equals(businessStatus) || BusinessStatusEnum.CANCEL.getStatus().equals(businessStatus)) {
                if (processHandler != null) {
                    processHandler.handleProcess(processInstance.getBusinessKey(), businessStatus, true);
                }
            }
            runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.WAITING.getStatus());
            String key = processInstance.getProcessDefinitionKey() + "_" + task.getTaskDefinitionKey();
            FlowTaskEventHandler taskHandler = flowEventStrategy.getTaskHandler(key);
            if (taskHandler != null) {
                taskHandler.handleTask(task.getId(), processInstance.getBusinessKey());
            }
            //办理意见
            taskService.addComment(completeTaskBo.getTaskId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), StringUtils.isBlank(completeTaskBo.getMessage()) ? "同意" : completeTaskBo.getMessage());
            //办理任务
            taskService.setAssignee(task.getId(), userId);
            if (CollUtil.isNotEmpty(completeTaskBo.getVariables())) {
                taskService.complete(completeTaskBo.getTaskId(), completeTaskBo.getVariables());
            } else {
                taskService.complete(completeTaskBo.getTaskId());
            }
            //记录执行过的流程任务节点
            wfTaskBackNodeService.recordExecuteNode(task);
            ProcessInstance pi = QueryUtils.instanceQuery(task.getProcessInstanceId()).singleResult();
            if (pi == null) {
                UpdateBusinessStatusCmd updateBusinessStatusCmd = new UpdateBusinessStatusCmd(task.getProcessInstanceId(), BusinessStatusEnum.FINISH.getStatus());
                managementService.executeCommand(updateBusinessStatusCmd);
                if (processHandler != null) {
                    processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.FINISH.getStatus(), false);
                }
            } else {
                List<Task> list = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
                for (Task t : list) {
                    if (ModelUtils.isUserTask(t.getProcessDefinitionId(), t.getTaskDefinitionKey())) {
                        List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForTask(t.getId());
                        if (CollUtil.isEmpty(links) && StringUtils.isBlank(t.getAssignee())) {
                            throw new ServiceException("下一节点【" + t.getName() + "】没有办理人!");
                        }
                    }
                }
                if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(completeTaskBo.getWfCopyList())) {
                    TaskEntity newTask = WorkflowUtils.createNewTask(task);
                    taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), LoginHelper.getLoginUser().getNickname() + "【抄送】给" + String.join(",", StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserName)));
                    taskService.complete(newTask.getId());
                    List<Task> taskList = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
                    WorkflowUtils.createCopyTask(taskList, StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserId));
                }
                sendMessage(list, processInstance.getName(), completeTaskBo.getMessageType(), null);
            }
            return true;
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * å‘送消息
     *
     * @param list        ä»»åŠ¡
     * @param name        æµç¨‹åç§°
     * @param messageType æ¶ˆæ¯ç±»åž‹
     * @param message     æ¶ˆæ¯å†…容,为空则发送默认配置的消息内容
     */
    @Async
    public void sendMessage(List<Task> list, String name, List<String> messageType, String message) {
        WorkflowUtils.sendMessage(list, name, messageType, message);
    }
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param taskBo å‚æ•°
     */
    @Override
    public TableDataInfo<TaskVo> getPageByTaskWait(TaskBo taskBo, PageQuery pageQuery) {
        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
        List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
        List<String> roleIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId()));
        String userId = String.valueOf(LoginHelper.getUserId());
        queryWrapper.eq("t.business_status_", BusinessStatusEnum.WAITING.getStatus());
        queryWrapper.eq(TenantHelper.isEnable(), "t.tenant_id_", TenantHelper.getTenantId());
        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 " + getInParam(roleIds) + " ) ))", userId)));
        if (StringUtils.isNotBlank(taskBo.getName())) {
            queryWrapper.like("t.name_", taskBo.getName());
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
            queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
            queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
        }
        Page<TaskVo> page = actTaskMapper.getTaskWaitByPage(pageQuery.build(), queryWrapper);
        List<TaskVo> taskList = page.getRecords();
        if (CollUtil.isNotEmpty(taskList)) {
            List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (TaskVo task : taskList) {
                task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
                task.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId()));
                task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && e.getNodeId().equals(task.getTaskDefinitionKey()) && FlowConstant.FALSE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                }
            }
        }
        return TableDataInfo.build(page);
    }
    private String getInParam(List<String> param) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < param.size(); i++) {
            sb.append("'").append(param.get(i)).append("'");
            if (i != param.size() - 1) {
                sb.append(",");
            }
        }
        sb.append(")");
        return sb.toString();
    }
    /**
     * æŸ¥è¯¢å½“前租户所有待办任务
     *
     * @param taskBo å‚æ•°
     */
    @Override
    public TableDataInfo<TaskVo> getPageByAllTaskWait(TaskBo taskBo, PageQuery pageQuery) {
        TaskQuery query = QueryUtils.taskQuery();
        if (StringUtils.isNotBlank(taskBo.getName())) {
            query.taskNameLike("%" + taskBo.getName() + "%");
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
            query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%");
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
            query.processDefinitionKey(taskBo.getProcessDefinitionKey());
        }
        query.orderByTaskCreateTime().desc();
        List<Task> taskList = query.listPage(pageQuery.getFirstNum(), pageQuery.getPageSize());
        List<ProcessInstance> processInstanceList = null;
        if (CollUtil.isNotEmpty(taskList)) {
            Set<String> processInstanceIds = StreamUtils.toSet(taskList, Task::getProcessInstanceId);
            processInstanceList = QueryUtils.instanceQuery(processInstanceIds).list();
        }
        List<TaskVo> list = new ArrayList<>();
        if (CollUtil.isNotEmpty(taskList)) {
            List<String> processDefinitionIds = StreamUtils.toList(taskList, Task::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (Task task : taskList) {
                TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class);
                if (CollUtil.isNotEmpty(processInstanceList)) {
                    processInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> {
                        taskVo.setBusinessStatus(e.getBusinessStatus());
                        taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus()));
                        taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey());
                        taskVo.setProcessDefinitionName(e.getProcessDefinitionName());
                        taskVo.setProcessDefinitionVersion(e.getProcessDefinitionVersion());
                        taskVo.setBusinessKey(e.getBusinessKey());
                    });
                }
                taskVo.setAssignee(StringUtils.isNotBlank(task.getAssignee()) ? Long.valueOf(task.getAssignee()) : null);
                taskVo.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId()));
                taskVo.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(taskVo::setWfNodeConfigVo);
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && e.getNodeId().equals(task.getTaskDefinitionKey()) && FlowConstant.FALSE.equals(e.getApplyUserTask())).findFirst().ifPresent(taskVo::setWfNodeConfigVo);
                }
                list.add(taskVo);
            }
        }
        long count = query.count();
        TableDataInfo<TaskVo> build = TableDataInfo.build();
        build.setRows(list);
        build.setTotal(count);
        return build;
    }
    /**
     * æŸ¥è¯¢å½“前用户的已办任务
     *
     * @param taskBo å‚æ•°
     */
    @Override
    public TableDataInfo<TaskVo> getPageByTaskFinish(TaskBo taskBo, PageQuery pageQuery) {
        String userId = String.valueOf(LoginHelper.getUserId());
        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(taskBo.getName()), "t.name_", taskBo.getName());
        queryWrapper.like(StringUtils.isNotBlank(taskBo.getProcessDefinitionName()), "t.processDefinitionName", taskBo.getProcessDefinitionName());
        queryWrapper.eq(StringUtils.isNotBlank(taskBo.getProcessDefinitionKey()), "t.processDefinitionKey", taskBo.getProcessDefinitionKey());
        queryWrapper.eq("t.assignee_", userId);
        Page<TaskVo> page = actTaskMapper.getTaskFinishByPage(pageQuery.build(), queryWrapper);
        List<TaskVo> taskList = page.getRecords();
        if (CollUtil.isNotEmpty(taskList)) {
            List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (TaskVo task : taskList) {
                task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && e.getNodeId().equals(task.getTaskDefinitionKey()) && FlowConstant.FALSE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                }
            }
        }
        return TableDataInfo.build(page);
    }
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param taskBo å‚æ•°
     */
    @Override
    public TableDataInfo<TaskVo> getPageByTaskCopy(TaskBo taskBo, PageQuery pageQuery) {
        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
        String userId = String.valueOf(LoginHelper.getUserId());
        if (StringUtils.isNotBlank(taskBo.getName())) {
            queryWrapper.like("t.name_", taskBo.getName());
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
            queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
        }
        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
            queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
        }
        queryWrapper.eq("t.assignee_", userId);
        Page<TaskVo> page = actTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper);
        List<TaskVo> taskList = page.getRecords();
        if (CollUtil.isNotEmpty(taskList)) {
            List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (TaskVo task : taskList) {
                task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && e.getNodeId().equals(task.getTaskDefinitionKey()) && FlowConstant.FALSE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                }
            }
        }
        return TableDataInfo.build(page);
    }
    /**
     * æŸ¥è¯¢å½“前租户所有已办任务
     *
     * @param taskBo å‚æ•°
     */
    @Override
    public TableDataInfo<TaskVo> getPageByAllTaskFinish(TaskBo taskBo, PageQuery pageQuery) {
        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(taskBo.getName()), "t.name_", taskBo.getName());
        queryWrapper.like(StringUtils.isNotBlank(taskBo.getProcessDefinitionName()), "t.processDefinitionName", taskBo.getProcessDefinitionName());
        queryWrapper.eq(StringUtils.isNotBlank(taskBo.getProcessDefinitionKey()), "t.processDefinitionKey", taskBo.getProcessDefinitionKey());
        Page<TaskVo> page = actTaskMapper.getTaskFinishByPage(pageQuery.build(), queryWrapper);
        List<TaskVo> taskList = page.getRecords();
        if (CollUtil.isNotEmpty(taskList)) {
            List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
            List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
            for (TaskVo task : taskList) {
                task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
                if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                    wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && e.getNodeId().equals(task.getTaskDefinitionKey()) && FlowConstant.FALSE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
                }
            }
        }
        return TableDataInfo.build(page);
    }
    /**
     * å§”派任务
     *
     * @param delegateBo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delegateTask(DelegateBo delegateBo) {
        TaskQuery query = QueryUtils.taskQuery();
        TaskEntity task = (TaskEntity) query.taskId(delegateBo.getTaskId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        try {
            TaskEntity newTask = WorkflowUtils.createNewTask(task);
            taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.PENDING.getStatus(), "【" + LoginHelper.getLoginUser().getNickname() + "】委派给【" + delegateBo.getNickName() + "】");
            //委托任务
            taskService.delegateTask(delegateBo.getTaskId(), delegateBo.getUserId());
            //办理生成的任务记录
            taskService.complete(newTask.getId());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param terminationBo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean terminationTask(TerminationBo terminationBo) {
        TaskQuery query = QueryUtils.taskQuery();
        Task task = query.taskId(terminationBo.getTaskId()).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        BusinessStatusEnum.checkInvalidStatus(historicProcessInstance.getBusinessStatus());
        try {
            if (StringUtils.isBlank(terminationBo.getComment())) {
                terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "终止了申请");
            } else {
                terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "终止了申请:" + terminationBo.getComment());
            }
            taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.TERMINATION.getStatus(), terminationBo.getComment());
            List<Task> list = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
            if (CollUtil.isNotEmpty(list)) {
                List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
                if (CollUtil.isNotEmpty(subTasks)) {
                    subTasks.forEach(e -> taskService.deleteTask(e.getId()));
                }
                runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.TERMINATION.getStatus());
                runtimeService.deleteProcessInstance(task.getProcessInstanceId(), StrUtil.EMPTY);
            }
            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey());
            if (processHandler != null) {
                processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.TERMINATION.getStatus(), false);
            }
            return true;
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * è½¬åŠžä»»åŠ¡
     *
     * @param transmitBo å‚æ•°
     */
    @Override
    public boolean transferTask(TransmitBo transmitBo) {
        Task task = QueryUtils.taskQuery().taskId(transmitBo.getTaskId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        try {
            TaskEntity newTask = WorkflowUtils.createNewTask(task);
            taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.TRANSFER.getStatus(), StringUtils.isNotBlank(transmitBo.getComment()) ? transmitBo.getComment() : LoginHelper.getUsername() + "转办了任务");
            taskService.complete(newTask.getId());
            taskService.setAssignee(task.getId(), transmitBo.getUserId());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * ä¼šç­¾ä»»åŠ¡åŠ ç­¾
     *
     * @param addMultiBo å‚æ•°
     */
    @Override
    public boolean addMultiInstanceExecution(AddMultiBo addMultiBo) {
        TaskQuery taskQuery = QueryUtils.taskQuery();
        taskQuery.taskId(addMultiBo.getTaskId());
        if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) {
            taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));
        }
        Task task = taskQuery.singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        String taskDefinitionKey = task.getTaskDefinitionKey();
        String processInstanceId = task.getProcessInstanceId();
        String processDefinitionId = task.getProcessDefinitionId();
        try {
            MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey);
            if (multiInstanceVo == null) {
                throw new ServiceException("当前环节不是会签节点");
            }
            if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) {
                for (Long assignee : addMultiBo.getAssignees()) {
                    runtimeService.addMultiInstanceExecution(taskDefinitionKey, processInstanceId, Collections.singletonMap(multiInstanceVo.getAssignee(), assignee));
                }
            } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) {
                AddSequenceMultiInstanceCmd addSequenceMultiInstanceCmd = new AddSequenceMultiInstanceCmd(task.getExecutionId(), multiInstanceVo.getAssigneeList(), addMultiBo.getAssignees());
                managementService.executeCommand(addSequenceMultiInstanceCmd);
            }
            List<String> assigneeNames = addMultiBo.getAssigneeNames();
            String username = LoginHelper.getUsername();
            TaskEntity newTask = WorkflowUtils.createNewTask(task);
            taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN.getStatus(), username + "加签【" + String.join(StringUtils.SEPARATOR, assigneeNames) + "】");
            taskService.complete(newTask.getId());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * ä¼šç­¾ä»»åŠ¡å‡ç­¾
     *
     * @param deleteMultiBo å‚æ•°
     */
    @Override
    public boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo) {
        TaskQuery taskQuery = QueryUtils.taskQuery();
        taskQuery.taskId(deleteMultiBo.getTaskId());
        if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) {
            taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));
        }
        Task task = taskQuery.singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        String taskDefinitionKey = task.getTaskDefinitionKey();
        String processInstanceId = task.getProcessInstanceId();
        String processDefinitionId = task.getProcessDefinitionId();
        try {
            MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey);
            if (multiInstanceVo == null) {
                throw new ServiceException("当前环节不是会签节点");
            }
            if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) {
                for (String executionId : deleteMultiBo.getExecutionIds()) {
                    runtimeService.deleteMultiInstanceExecution(executionId, false);
                }
                for (String taskId : deleteMultiBo.getTaskIds()) {
                    historyService.deleteHistoricTaskInstance(taskId);
                }
            } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) {
                DeleteSequenceMultiInstanceCmd deleteSequenceMultiInstanceCmd = new DeleteSequenceMultiInstanceCmd(task.getAssignee(), task.getExecutionId(), multiInstanceVo.getAssigneeList(), deleteMultiBo.getAssigneeIds());
                managementService.executeCommand(deleteSequenceMultiInstanceCmd);
            }
            List<String> assigneeNames = deleteMultiBo.getAssigneeNames();
            String username = LoginHelper.getUsername();
            TaskEntity newTask = WorkflowUtils.createNewTask(task);
            taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN_OFF.getStatus(), username + "减签【" + String.join(StringUtils.SEPARATOR, assigneeNames) + "】");
            taskService.complete(newTask.getId());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * é©³å›žå®¡æ‰¹
     *
     * @param backProcessBo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String backProcess(BackProcessBo backProcessBo) {
        TaskQuery query = QueryUtils.taskQuery();
        String userId = String.valueOf(LoginHelper.getUserId());
        Task task = query.taskId(backProcessBo.getTaskId()).taskCandidateOrAssigned(userId).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
        }
        if (task.isSuspended()) {
            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
        }
        try {
            String processInstanceId = task.getProcessInstanceId();
            ProcessInstance processInstance = QueryUtils.instanceQuery(task.getProcessInstanceId()).singleResult();
            //获取并行网关执行后保留的执行实例数据
            ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId());
            List<ExecutionEntity> executionEntities = managementService.executeCommand(childByExecutionIdCmd);
            //校验单据
            BusinessStatusEnum.checkBackStatus(processInstance.getBusinessStatus());
            //判断是否有多个任务
            List<Task> taskList = QueryUtils.taskQuery(processInstanceId).list();
            String backTaskDefinitionKey = backProcessBo.getTargetActivityId();
            taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.BACK.getStatus(), StringUtils.isNotBlank(backProcessBo.getMessage()) ? backProcessBo.getMessage() : "退回");
            if (taskList.size() > 1) {
                //当前多个任务驳回到单个节点
                runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdsToSingleActivityId(taskList.stream().map(Task::getTaskDefinitionKey).distinct().collect(Collectors.toList()), backTaskDefinitionKey).changeState();
                ActHiTaskinst actHiTaskinst = new ActHiTaskinst();
                actHiTaskinst.setAssignee(userId);
                actHiTaskinst.setId(task.getId());
                actHiTaskinstMapper.updateById(actHiTaskinst);
            } else {
                //当前单个节点驳回单个节点
                runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdTo(task.getTaskDefinitionKey(), backTaskDefinitionKey).changeState();
            }
            //删除并行环节未办理记录
            MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
            if (multiInstance == null && taskList.size() > 1) {
                List<Task> tasks = StreamUtils.filter(taskList, e -> !e.getTaskDefinitionKey().equals(task.getTaskDefinitionKey()));
                actHiTaskinstMapper.deleteBatchIds(StreamUtils.toList(tasks, Task::getId));
            }
            List<HistoricTaskInstance> instanceList = QueryUtils.hisTaskInstanceQuery(processInstanceId).finished().orderByHistoricTaskInstanceEndTime().desc().list();
            List<Task> list = QueryUtils.taskQuery(processInstanceId).list();
            for (Task t : list) {
                instanceList.stream().filter(e -> e.getTaskDefinitionKey().equals(t.getTaskDefinitionKey())).findFirst().ifPresent(e -> {
                    taskService.setAssignee(t.getId(), e.getAssignee());
                });
            }
            //发送消息
            String message = "您的【" + processInstance.getName() + "】单据已经被驳回,请您注意查收。";
            sendMessage(list, processInstance.getName(), backProcessBo.getMessageType(), message);
            //删除流程实例垃圾数据
            for (ExecutionEntity executionEntity : executionEntities) {
                DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId());
                managementService.executeCommand(deleteExecutionCmd);
            }
            WfTaskBackNode wfTaskBackNode = wfTaskBackNodeService.getListByInstanceIdAndNodeId(task.getProcessInstanceId(), backProcessBo.getTargetActivityId());
            if (ObjectUtil.isNotNull(wfTaskBackNode) && wfTaskBackNode.getOrderNo() == 0) {
                runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.BACK.getStatus());
                FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
                if (processHandler != null) {
                    processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.BACK.getStatus(), false);
                }
            }
            //删除驳回后的流程节点
            wfTaskBackNodeService.deleteBackTaskNode(processInstanceId, backProcessBo.getTargetActivityId());
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
        return task.getProcessInstanceId();
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
     *
     * @param taskIds ä»»åŠ¡id
     * @param userId  åŠžç†äººid
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateAssignee(String[] taskIds, String userId) {
        try {
            List<Task> list = QueryUtils.taskQuery().taskIds(Arrays.asList(taskIds)).list();
            for (Task task : list) {
                taskService.setAssignee(task.getId(), userId);
            }
        } catch (Exception e) {
            throw new ServiceException("修改失败:" + e.getMessage());
        }
        return true;
    }
    /**
     * æŸ¥è¯¢æµç¨‹å˜é‡
     *
     * @param taskId ä»»åŠ¡id
     */
    @Override
    public List<VariableVo> getInstanceVariable(String taskId) {
        List<VariableVo> variableVoList = new ArrayList<>();
        Map<String, VariableInstance> variableInstances = taskService.getVariableInstances(taskId);
        if (CollUtil.isNotEmpty(variableInstances)) {
            for (Map.Entry<String, VariableInstance> entry : variableInstances.entrySet()) {
                VariableVo variableVo = new VariableVo();
                variableVo.setKey(entry.getKey());
                variableVo.setValue(entry.getValue().getValue().toString());
                variableVoList.add(variableVo);
            }
        }
        return variableVoList;
    }
    /**
     * æŸ¥è¯¢å·¥ä½œæµä»»åŠ¡ç”¨æˆ·é€‰æ‹©åŠ ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public String getTaskUserIdsByAddMultiInstance(String taskId) {
        Task task = QueryUtils.taskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new ServiceException("任务不存在");
        }
        MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
        if (multiInstance == null) {
            return "";
        }
        List<Long> userIds;
        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
            userIds = (List<Long>) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList());
        } else {
            List<Task> list = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
            userIds = StreamUtils.toList(list, e -> Long.valueOf(e.getAssignee()));
        }
        return StringUtils.join(userIds, StringUtils.SEPARATOR);
    }
    /**
     * æŸ¥è¯¢å·¥ä½œæµé€‰æ‹©å‡ç­¾äººå‘˜
     *
     * @param taskId ä»»åŠ¡id ä»»åŠ¡id
     */
    @Override
    @SuppressWarnings("unchecked")
    public List<TaskVo> getListByDeleteMultiInstance(String taskId) {
        Task task = QueryUtils.taskQuery().taskId(taskId).singleResult();
        List<Task> taskList = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
        MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
        List<TaskVo> taskListVo = new ArrayList<>();
        if (multiInstance == null) {
            return List.of();
        }
        List<Long> assigneeList = new ArrayList<>();
        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
            List<Object> variable = (List<Object>) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList());
            for (Object o : variable) {
                assigneeList.add(Long.valueOf(o.toString()));
            }
        }
        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
            List<Long> userIds = StreamUtils.filter(assigneeList, e -> !String.valueOf(e).equals(task.getAssignee()));
            List<UserDTO> userList = userService.selectListByIds(userIds);
            for (Long userId : userIds) {
                TaskVo taskVo = new TaskVo();
                taskVo.setId("串行会签");
                taskVo.setExecutionId("串行会签");
                taskVo.setProcessInstanceId(task.getProcessInstanceId());
                taskVo.setName(task.getName());
                taskVo.setAssignee(userId);
                if (CollUtil.isNotEmpty(userList)) {
                    userList.stream().filter(u -> u.getUserId().toString().equals(userId.toString())).findFirst().ifPresent(u -> taskVo.setAssigneeName(u.getNickName()));
                }
                taskListVo.add(taskVo);
            }
            return taskListVo;
        } else if (multiInstance.getType() instanceof ParallelMultiInstanceBehavior) {
            List<Task> tasks = StreamUtils.filter(taskList, e -> StringUtils.isBlank(e.getParentTaskId()) && !e.getExecutionId().equals(task.getExecutionId()) && e.getTaskDefinitionKey().equals(task.getTaskDefinitionKey()));
            if (CollUtil.isNotEmpty(tasks)) {
                List<Long> userIds = StreamUtils.toList(tasks, e -> Long.valueOf(e.getAssignee()));
                List<UserDTO> userList = userService.selectListByIds(userIds);
                for (Task t : tasks) {
                    TaskVo taskVo = new TaskVo();
                    taskVo.setId(t.getId());
                    taskVo.setExecutionId(t.getExecutionId());
                    taskVo.setProcessInstanceId(t.getProcessInstanceId());
                    taskVo.setName(t.getName());
                    taskVo.setAssignee(Long.valueOf(t.getAssignee()));
                    if (CollUtil.isNotEmpty(userList)) {
                        userList.stream().filter(u -> u.getUserId().toString().equals(t.getAssignee())).findFirst().ifPresent(e -> taskVo.setAssigneeName(e.getNickName()));
                    }
                    taskListVo.add(taskVo);
                }
                return taskListVo;
            }
        }
        return List.of();
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
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.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.workflow.domain.TestLeave;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import org.dromara.workflow.mapper.TestLeaveMapper;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.dromara.workflow.service.ITestLeaveService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
 * è¯·å‡Service业务层处理
 *
 * @author may
 * @date 2023-07-21
 */
@RequiredArgsConstructor
@Service
public class TestLeaveServiceImpl implements ITestLeaveService {
    private final TestLeaveMapper baseMapper;
    private final IActProcessInstanceService actProcessInstanceService;
    /**
     * æŸ¥è¯¢è¯·å‡
     */
    @Override
    public TestLeaveVo queryById(Long id) {
        TestLeaveVo testLeaveVo = baseMapper.selectVoById(id);
        WorkflowUtils.setProcessInstanceVo(testLeaveVo, String.valueOf(id));
        return testLeaveVo;
    }
    /**
     * æŸ¥è¯¢è¯·å‡åˆ—表
     */
    @Override
    public TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
        Page<TestLeaveVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        TableDataInfo<TestLeaveVo> build = TableDataInfo.build(result);
        List<TestLeaveVo> rows = build.getRows();
        if (CollUtil.isNotEmpty(rows)) {
            List<String> ids = StreamUtils.toList(rows, e -> String.valueOf(e.getId()));
            WorkflowUtils.setProcessInstanceListVo(rows, ids, "id");
        }
        return build;
    }
    /**
     * æŸ¥è¯¢è¯·å‡åˆ—表
     */
    @Override
    public List<TestLeaveVo> queryList(TestLeaveBo bo) {
        LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<TestLeave> buildQueryWrapper(TestLeaveBo bo) {
        LambdaQueryWrapper<TestLeave> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getLeaveType()), TestLeave::getLeaveType, bo.getLeaveType());
        lqw.ge(bo.getStartLeaveDays() != null, TestLeave::getLeaveDays, bo.getStartLeaveDays());
        lqw.le(bo.getEndLeaveDays() != null, TestLeave::getLeaveDays, bo.getEndLeaveDays());
        lqw.orderByDesc(BaseEntity::getCreateTime);
        return lqw;
    }
    /**
     * æ–°å¢žè¯·å‡
     */
    @Override
    public TestLeaveVo insertByBo(TestLeaveBo bo) {
        TestLeave add = MapstructUtils.convert(bo, TestLeave.class);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        TestLeaveVo testLeaveVo = MapstructUtils.convert(add, TestLeaveVo.class);
        WorkflowUtils.setProcessInstanceVo(testLeaveVo, String.valueOf(add.getId()));
        return testLeaveVo;
    }
    /**
     * ä¿®æ”¹è¯·å‡
     */
    @Override
    public TestLeaveVo updateByBo(TestLeaveBo bo) {
        TestLeave update = MapstructUtils.convert(bo, TestLeave.class);
        baseMapper.updateById(update);
        TestLeaveVo testLeaveVo = MapstructUtils.convert(update, TestLeaveVo.class);
        WorkflowUtils.setProcessInstanceVo(testLeaveVo, String.valueOf(update.getId()));
        return testLeaveVo;
    }
    /**
     * æ‰¹é‡åˆ é™¤è¯·å‡
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        List<String> idList = StreamUtils.toList(ids, String::valueOf);
        actProcessInstanceService.deleteRunAndHisInstanceByBusinessKeys(idList);
        return baseMapper.deleteBatchIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
package org.dromara.workflow.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.workflow.domain.WfCategory;
import org.dromara.workflow.domain.bo.WfCategoryBo;
import org.dromara.workflow.domain.vo.WfCategoryVo;
import org.dromara.workflow.mapper.WfCategoryMapper;
import org.dromara.workflow.service.IWfCategoryService;
import org.dromara.workflow.utils.QueryUtils;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»Service业务层处理
 *
 * @author may
 * @date 2023-06-28
 */
@RequiredArgsConstructor
@Service
public class WfCategoryServiceImpl implements IWfCategoryService {
    private final WfCategoryMapper baseMapper;
    private final RepositoryService repositoryService;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»
     */
    @Override
    public WfCategoryVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
     */
    @Override
    public List<WfCategoryVo> queryList(WfCategoryBo bo) {
        LambdaQueryWrapper<WfCategory> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<WfCategory> buildQueryWrapper(WfCategoryBo bo) {
        LambdaQueryWrapper<WfCategory> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), WfCategory::getCategoryName, bo.getCategoryName());
        lqw.eq(StringUtils.isNotBlank(bo.getCategoryCode()), WfCategory::getCategoryCode, bo.getCategoryCode());
        return lqw;
    }
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     */
    @Override
    public Boolean insertByBo(WfCategoryBo bo) {
        WfCategory add = MapstructUtils.convert(bo, WfCategory.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(WfCategoryBo bo) {
        WfCategory update = MapstructUtils.convert(bo, WfCategory.class);
        validEntityBeforeSave(update);
        WfCategoryVo wfCategoryVo = baseMapper.selectVoById(bo.getId());
        List<ProcessDefinition> processDefinitionList = QueryUtils.definitionQuery().processDefinitionCategory(wfCategoryVo.getCategoryCode()).list();
        for (ProcessDefinition processDefinition : processDefinitionList) {
            repositoryService.setProcessDefinitionCategory(processDefinition.getId(), bo.getCategoryCode());
        }
        List<Deployment> deploymentList = QueryUtils.deploymentQuery().deploymentCategory(wfCategoryVo.getCategoryCode()).list();
        for (Deployment deployment : deploymentList) {
            repositoryService.setDeploymentCategory(deployment.getId(), bo.getCategoryCode());
        }
        List<Model> modelList = QueryUtils.modelQuery().modelCategory(wfCategoryVo.getCategoryCode()).list();
        for (Model model : modelList) {
            model.setCategory(bo.getCategoryCode());
            repositoryService.saveModel(model);
        }
        return baseMapper.updateById(update) > 0;
    }
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(WfCategory entity) {
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
    }
    /**
     * æ‰¹é‡åˆ é™¤æµç¨‹åˆ†ç±»
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    /**
     * æŒ‰ç…§ç±»åˆ«ç¼–码查询
     *
     * @param categoryCode åˆ†ç±»æ¯”吗
     */
    @Override
    public WfCategory queryByCategoryCode(String categoryCode) {
        return baseMapper.selectOne(new LambdaQueryWrapper<WfCategory>().eq(WfCategory::getCategoryCode, categoryCode));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfDefinitionConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import org.dromara.common.core.utils.MapstructUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.workflow.domain.WfDefinitionConfig;
import org.dromara.workflow.domain.bo.WfDefinitionConfigBo;
import org.dromara.workflow.domain.vo.WfDefinitionConfigVo;
import org.dromara.workflow.service.IWfDefinitionConfigService;
import org.springframework.stereotype.Service;
import org.dromara.workflow.mapper.WfDefinitionConfigMapper;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Collection;
/**
 * æµç¨‹å®šä¹‰é…ç½®Service业务层处理
 *
 * @author may
 * @date 2024-03-18
 */
@RequiredArgsConstructor
@Service
public class WfDefinitionConfigServiceImpl implements IWfDefinitionConfigService {
    private final WfDefinitionConfigMapper baseMapper;
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     */
    @Override
    public WfDefinitionConfigVo getByDefId(String definitionId) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<WfDefinitionConfig>().eq(WfDefinitionConfig::getDefinitionId, definitionId));
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     *
     * @param tableName è¡¨å
     * @return ç»“æžœ
     */
    @Override
    public WfDefinitionConfigVo getByTableNameLastVersion(String tableName) {
        List<WfDefinitionConfigVo> wfDefinitionConfigVos = baseMapper.selectVoList(
            new LambdaQueryWrapper<WfDefinitionConfig>().eq(WfDefinitionConfig::getTableName, tableName).orderByDesc(WfDefinitionConfig::getVersion));
        if (CollUtil.isNotEmpty(wfDefinitionConfigVos)) {
            return wfDefinitionConfigVos.get(0);
        }
        return null;
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param tableName    è¡¨å
     * @return ç»“æžœ
     */
    @Override
    public WfDefinitionConfigVo getByDefIdAndTableName(String definitionId, String tableName) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<WfDefinitionConfig>()
            .eq(WfDefinitionConfig::getDefinitionId, definitionId)
            .eq(WfDefinitionConfig::getTableName, tableName));
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®æŽ’除当前查询的流程定义
     *
     * @param tableName    è¡¨å
     * @param definitionId æµç¨‹å®šä¹‰id
     */
    @Override
    public List<WfDefinitionConfigVo> getByTableNameNotDefId(String tableName, String definitionId) {
        return baseMapper.selectVoList(new LambdaQueryWrapper<WfDefinitionConfig>()
            .eq(WfDefinitionConfig::getTableName, tableName)
            .ne(WfDefinitionConfig::getDefinitionId, definitionId));
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰é…ç½®åˆ—表
     */
    @Override
    public List<WfDefinitionConfigVo> queryList(List<String> definitionIds) {
        return baseMapper.selectVoList(new LambdaQueryWrapper<WfDefinitionConfig>().in(WfDefinitionConfig::getDefinitionId, definitionIds));
    }
    /**
     * æ–°å¢žæµç¨‹å®šä¹‰é…ç½®
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveOrUpdate(WfDefinitionConfigBo bo) {
        WfDefinitionConfig add = MapstructUtils.convert(bo, WfDefinitionConfig.class);
        baseMapper.delete(new LambdaQueryWrapper<WfDefinitionConfig>().eq(WfDefinitionConfig::getTableName, bo.getTableName()));
        add.setTableName(add.getTableName().toLowerCase());
        boolean flag = baseMapper.insertOrUpdate(add);
        if (baseMapper.insertOrUpdate(add)) {
            bo.setId(add.getId());
        }
        return flag;
    }
    /**
     * æ‰¹é‡åˆ é™¤æµç¨‹å®šä¹‰é…ç½®
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    @Override
    public Boolean deleteByDefIds(Collection<String> ids) {
        return baseMapper.delete(new LambdaQueryWrapper<WfDefinitionConfig>().in(WfDefinitionConfig::getDefinitionId, ids)) > 0;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfFormManageServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
package org.dromara.workflow.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.workflow.common.enums.FormTypeEnum;
import org.springframework.stereotype.Service;
import org.dromara.workflow.domain.bo.WfFormManageBo;
import org.dromara.workflow.domain.vo.WfFormManageVo;
import org.dromara.workflow.domain.WfFormManage;
import org.dromara.workflow.mapper.WfFormManageMapper;
import org.dromara.workflow.service.IWfFormManageService;
import java.util.List;
import java.util.Collection;
/**
 * è¡¨å•管理Service业务层处理
 *
 * @author may
 * @date 2024-03-29
 */
@RequiredArgsConstructor
@Service
public class WfFormManageServiceImpl implements IWfFormManageService {
    private final WfFormManageMapper baseMapper;
    /**
     * æŸ¥è¯¢è¡¨å•管理
     */
    @Override
    public WfFormManageVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }
    @Override
    public List<WfFormManageVo> queryByIds(List<Long> ids) {
        return baseMapper.selectVoBatchIds(ids);
    }
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     */
    @Override
    public TableDataInfo<WfFormManageVo> queryPageList(WfFormManageBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<WfFormManage> lqw = buildQueryWrapper(bo);
        Page<WfFormManageVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }
    @Override
    public List<WfFormManageVo> selectList() {
        List<WfFormManageVo> wfFormManageVos = baseMapper.selectVoList(new LambdaQueryWrapper<WfFormManage>().orderByDesc(WfFormManage::getUpdateTime));
        for (WfFormManageVo wfFormManageVo : wfFormManageVos) {
            wfFormManageVo.setFormTypeName(FormTypeEnum.findByType(wfFormManageVo.getFormType()));
        }
        return wfFormManageVos;
    }
    /**
     * æŸ¥è¯¢è¡¨å•管理列表
     */
    @Override
    public List<WfFormManageVo> queryList(WfFormManageBo bo) {
        LambdaQueryWrapper<WfFormManage> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<WfFormManage> buildQueryWrapper(WfFormManageBo bo) {
        LambdaQueryWrapper<WfFormManage> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getFormName()), WfFormManage::getFormName, bo.getFormName());
        lqw.eq(StringUtils.isNotBlank(bo.getFormType()), WfFormManage::getFormType, bo.getFormType());
        return lqw;
    }
    /**
     * æ–°å¢žè¡¨å•管理
     */
    @Override
    public Boolean insertByBo(WfFormManageBo bo) {
        WfFormManage add = MapstructUtils.convert(bo, WfFormManage.class);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }
    /**
     * ä¿®æ”¹è¡¨å•管理
     */
    @Override
    public Boolean updateByBo(WfFormManageBo bo) {
        WfFormManage update = MapstructUtils.convert(bo, WfFormManage.class);
        return baseMapper.updateById(update) > 0;
    }
    /**
     * æ‰¹é‡åˆ é™¤è¡¨å•管理
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfNodeConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.workflow.domain.vo.WfFormManageVo;
import org.dromara.workflow.service.IWfFormManageService;
import org.springframework.stereotype.Service;
import org.dromara.workflow.domain.vo.WfNodeConfigVo;
import org.dromara.workflow.domain.WfNodeConfig;
import org.dromara.workflow.mapper.WfNodeConfigMapper;
import org.dromara.workflow.service.IWfNodeConfigService;
import java.util.Collection;
import java.util.List;
/**
 * èŠ‚ç‚¹é…ç½®Service业务层处理
 *
 * @author may
 * @date 2024-03-30
 */
@RequiredArgsConstructor
@Service
public class WfNodeConfigServiceImpl implements IWfNodeConfigService {
    private final WfNodeConfigMapper baseMapper;
    private final IWfFormManageService wfFormManageService;
    /**
     * æŸ¥è¯¢èŠ‚ç‚¹é…ç½®
     */
    @Override
    public WfNodeConfigVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }
    /**
     * ä¿å­˜èŠ‚ç‚¹é…ç½®
     */
    @Override
    public Boolean saveOrUpdate(List<WfNodeConfig> list) {
        return baseMapper.insertOrUpdateBatch(list);
    }
    /**
     * æ‰¹é‡åˆ é™¤èŠ‚ç‚¹é…ç½®
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    @Override
    public Boolean deleteByDefIds(Collection<String> ids) {
        return baseMapper.delete(new LambdaQueryWrapper<WfNodeConfig>().in(WfNodeConfig::getDefinitionId, ids)) > 0;
    }
    @Override
    public List<WfNodeConfigVo> selectByDefIds(Collection<String> ids) {
        List<WfNodeConfigVo> wfNodeConfigVos = baseMapper.selectVoList(new LambdaQueryWrapper<WfNodeConfig>().in(WfNodeConfig::getDefinitionId, ids));
        if (CollUtil.isNotEmpty(wfNodeConfigVos)) {
            List<Long> formIds = StreamUtils.toList(wfNodeConfigVos, WfNodeConfigVo::getFormId);
            List<WfFormManageVo> wfFormManageVos = wfFormManageService.queryByIds(formIds);
            for (WfNodeConfigVo wfNodeConfigVo : wfNodeConfigVos) {
                wfFormManageVos.stream().filter(e -> ObjectUtil.equals(e.getId(), wfNodeConfigVo.getFormId())).findFirst().ifPresent(wfNodeConfigVo::setWfFormManageVo);
            }
        }
        return wfNodeConfigVos;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfTaskBackNodeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.workflow.domain.WfTaskBackNode;
import org.dromara.workflow.domain.vo.MultiInstanceVo;
import org.dromara.workflow.mapper.WfTaskBackNodeMapper;
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import static org.dromara.workflow.common.constant.FlowConstant.MULTI_INSTANCE;
import static org.dromara.workflow.common.constant.FlowConstant.USER_TASK;
/**
 * èŠ‚ç‚¹é©³å›žè®°å½•Service业务层处理
 *
 * @author may
 * @date 2024-03-13
 */
@RequiredArgsConstructor
@Service
public class WfTaskBackNodeServiceImpl implements IWfTaskBackNodeService {
    private final WfTaskBackNodeMapper wfTaskBackNodeMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recordExecuteNode(Task task) {
        List<WfTaskBackNode> list = getListByInstanceId(task.getProcessInstanceId());
        WfTaskBackNode wfTaskBackNode = new WfTaskBackNode();
        wfTaskBackNode.setNodeId(task.getTaskDefinitionKey());
        wfTaskBackNode.setNodeName(task.getName());
        wfTaskBackNode.setInstanceId(task.getProcessInstanceId());
        wfTaskBackNode.setAssignee(String.valueOf(LoginHelper.getUserId()));
        MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
        if (ObjectUtil.isNotEmpty(multiInstance)) {
            wfTaskBackNode.setTaskType(MULTI_INSTANCE);
        } else {
            wfTaskBackNode.setTaskType(USER_TASK);
        }
        if (CollUtil.isEmpty(list)) {
            wfTaskBackNode.setOrderNo(0);
            wfTaskBackNodeMapper.insert(wfTaskBackNode);
        } else {
            WfTaskBackNode taskNode = list.stream().filter(e -> e.getNodeId().equals(wfTaskBackNode.getNodeId()) && e.getOrderNo() == 0).findFirst().orElse(null);
            if (ObjectUtil.isEmpty(taskNode)) {
                wfTaskBackNode.setOrderNo(list.get(0).getOrderNo() + 1);
                WfTaskBackNode node = getListByInstanceIdAndNodeId(wfTaskBackNode.getInstanceId(), wfTaskBackNode.getNodeId());
                if (ObjectUtil.isNotEmpty(node)) {
                    node.setAssignee(node.getAssignee() + StringUtils.SEPARATOR + LoginHelper.getUserId());
                    wfTaskBackNodeMapper.updateById(node);
                } else {
                    wfTaskBackNodeMapper.insert(wfTaskBackNode);
                }
            }
        }
    }
    @Override
    public List<WfTaskBackNode> getListByInstanceId(String processInstanceId) {
        LambdaQueryWrapper<WfTaskBackNode> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WfTaskBackNode::getInstanceId, processInstanceId);
        wrapper.orderByDesc(WfTaskBackNode::getOrderNo);
        return wfTaskBackNodeMapper.selectList(wrapper);
    }
    @Override
    public WfTaskBackNode getListByInstanceIdAndNodeId(String processInstanceId, String nodeId) {
        LambdaQueryWrapper<WfTaskBackNode> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(WfTaskBackNode::getInstanceId, processInstanceId);
        queryWrapper.eq(WfTaskBackNode::getNodeId, nodeId);
        return wfTaskBackNodeMapper.selectOne(queryWrapper);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteBackTaskNode(String processInstanceId, String targetActivityId) {
        try {
            LambdaQueryWrapper<WfTaskBackNode> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(WfTaskBackNode::getInstanceId, processInstanceId);
            queryWrapper.eq(WfTaskBackNode::getNodeId, targetActivityId);
            WfTaskBackNode actTaskNode = wfTaskBackNodeMapper.selectOne(queryWrapper);
            if (ObjectUtil.isNotNull(actTaskNode)) {
                Integer orderNo = actTaskNode.getOrderNo();
                List<WfTaskBackNode> taskNodeList = getListByInstanceId(processInstanceId);
                List<Long> ids = new ArrayList<>();
                if (CollUtil.isNotEmpty(taskNodeList)) {
                    for (WfTaskBackNode taskNode : taskNodeList) {
                        if (taskNode.getOrderNo() >= orderNo) {
                            ids.add(taskNode.getId());
                        }
                    }
                }
                if (CollUtil.isNotEmpty(ids)) {
                    wfTaskBackNodeMapper.deleteBatchIds(ids);
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("删除失败");
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByInstanceId(String processInstanceId) {
        LambdaQueryWrapper<WfTaskBackNode> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WfTaskBackNode::getInstanceId, processInstanceId);
        List<WfTaskBackNode> list = wfTaskBackNodeMapper.selectList(wrapper);
        int delete = wfTaskBackNodeMapper.delete(wrapper);
        if (list.size() != delete) {
            throw new ServiceException("删除失败");
        }
        return true;
    }
    @Override
    public boolean deleteByInstanceIds(List<String> processInstanceIds) {
        LambdaQueryWrapper<WfTaskBackNode> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(WfTaskBackNode::getInstanceId, processInstanceIds);
        List<WfTaskBackNode> list = wfTaskBackNodeMapper.selectList(wrapper);
        int delete = wfTaskBackNodeMapper.delete(wrapper);
        if (list.size() != delete) {
            throw new ServiceException("删除失败");
        }
        return true;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,289 @@
package org.dromara.workflow.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
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.json.utils.JsonUtils;
import org.dromara.workflow.domain.vo.MultiInstanceVo;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.ProcessEngine;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.ServerException;
import java.util.*;
import java.util.stream.Collectors;
/**
 * æ¨¡åž‹å·¥å…·
 *
 * @author may
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ModelUtils {
    private static final ProcessEngine PROCESS_ENGINE = SpringUtils.getBean(ProcessEngine.class);
    public static BpmnModel xmlToBpmnModel(String xml) throws IOException {
        if (xml == null) {
            throw new ServerException("xml不能为空");
        }
        try {
            InputStream inputStream = new ByteArrayInputStream(StrUtil.utf8Bytes(xml));
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLStreamReader reader = factory.createXMLStreamReader(inputStream);
            return new BpmnXMLConverter().convertToBpmnModel(reader);
        } catch (XMLStreamException e) {
            throw new ServerException(e.getMessage());
        }
    }
    /**
     * bpmnModel转为xml
     *
     * @param jsonBytes json
     */
    public static byte[] bpmnJsonToXmlBytes(byte[] jsonBytes) throws IOException {
        if (jsonBytes == null) {
            return new byte[0];
        }
        // 1. json字节码转成 BpmnModel å¯¹è±¡
        ObjectMapper objectMapper = JsonUtils.getObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(jsonBytes);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
        if (bpmnModel.getProcesses().isEmpty()) {
            return new byte[0];
        }
        // 2.将bpmnModel转为xml
        return new BpmnXMLConverter().convertToXML(bpmnModel);
    }
    /**
     * xml转为bpmnModel
     *
     * @param xmlBytes xml
     */
    public static BpmnModel xmlToBpmnModel(byte[] xmlBytes) throws XMLStreamException {
        ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(xmlBytes);
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xtr = xif.createXMLStreamReader(byteArrayInputStream);
        return new BpmnXMLConverter().convertToBpmnModel(xtr);
    }
    /**
     * æ ¡éªŒæ¨¡åž‹
     *
     * @param bpmnModel bpmn模型
     */
    public static void checkBpmnModel(BpmnModel bpmnModel) throws ServerException {
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        checkBpmnNode(flowElements, false);
        List<SubProcess> subProcessList = flowElements.stream().filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList());
        if (!CollUtil.isEmpty(subProcessList)) {
            for (SubProcess subProcess : subProcessList) {
                Collection<FlowElement> subProcessFlowElements = subProcess.getFlowElements();
                checkBpmnNode(subProcessFlowElements, true);
            }
        }
        List<MultiInstanceVo> multiInstanceVoList = new ArrayList<>();
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask && ObjectUtil.isNotEmpty(((UserTask) flowElement).getLoopCharacteristics()) && StringUtils.isNotBlank(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem())) {
                MultiInstanceVo multiInstanceVo = new MultiInstanceVo();
                multiInstanceVo.setAssigneeList(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem());
                multiInstanceVoList.add(multiInstanceVo);
            }
        }
        if (CollectionUtil.isNotEmpty(multiInstanceVoList) && multiInstanceVoList.size() > 1) {
            Map<String, List<MultiInstanceVo>> assigneeListGroup = StreamUtils.groupByKey(multiInstanceVoList, MultiInstanceVo::getAssigneeList);
            for (Map.Entry<String, List<MultiInstanceVo>> entry : assigneeListGroup.entrySet()) {
                List<MultiInstanceVo> value = entry.getValue();
                if (CollectionUtil.isNotEmpty(value) && value.size() > 1) {
                    String key = entry.getKey();
                    throw new ServerException("会签人员集合【" + key + "】重复,请重新设置集合KEY");
                }
            }
        }
    }
    /**
     * æ ¡éªŒbpmn节点是否合法
     *
     * @param flowElements èŠ‚ç‚¹é›†åˆ
     * @param subtask      æ˜¯å¦å­æµç¨‹
     */
    private static void checkBpmnNode(Collection<FlowElement> flowElements, boolean subtask) throws ServerException {
        if (CollUtil.isEmpty(flowElements)) {
            throw new ServerException(subtask ? "子流程必须存在节点" : "必须存在节点!");
        }
        List<StartEvent> startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList());
        if (CollUtil.isEmpty(startEventList)) {
            throw new ServerException(subtask ? "子流程必须存在开始节点" : "必须存在开始节点!");
        }
        if (startEventList.size() > 1) {
            throw new ServerException(subtask ? "子流程只能存在一个开始节点" : "只能存在一个开始节点!");
        }
        StartEvent startEvent = startEventList.get(0);
        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
        if (CollUtil.isEmpty(outgoingFlows)) {
            throw new ServerException(subtask ? "子流程流程节点为空,请至少设计一条主线流程!" : "流程节点为空,请至少设计一条主线流程!");
        }
        FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
        if (!(targetFlowElement instanceof UserTask) && !subtask) {
            throw new ServerException("开始节点后第一个节点必须是用户任务!");
        }
        //开始节点后第一个节点申请人节点
        if ((targetFlowElement instanceof UserTask) && !subtask) {
            UserTask userTask = (UserTask) targetFlowElement;
            if (StringUtils.isBlank(userTask.getFormKey())) {
                throw new ServerException("申请人节点必须选择表单!");
            }
        }
        List<EndEvent> endEventList = flowElements.stream().filter(EndEvent.class::isInstance).map(EndEvent.class::cast).collect(Collectors.toList());
        if (CollUtil.isEmpty(endEventList)) {
            throw new ServerException(subtask ? "子流程必须存在结束节点!" : "必须存在结束节点!");
        }
    }
    /**
     * èŽ·å–æµç¨‹å…¨éƒ¨ç”¨æˆ·èŠ‚ç‚¹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    public static List<UserTask> getUserTaskFlowElements(String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        List<UserTask> list = new ArrayList<>();
        List<Process> processes = bpmnModel.getProcesses();
        Collection<FlowElement> flowElements = processes.get(0).getFlowElements();
        buildUserTaskFlowElements(flowElements, list);
        return list;
    }
    /**
     * é€’归获取所有节点
     *
     * @param flowElements èŠ‚ç‚¹ä¿¡æ¯
     * @param list         é›†åˆ
     */
    private static void buildUserTaskFlowElements(Collection<FlowElement> flowElements, List<UserTask> list) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SubProcess) {
                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
                buildUserTaskFlowElements(subFlowElements, list);
            } else if (flowElement instanceof UserTask) {
                list.add((UserTask) flowElement);
            }
        }
    }
    /**
     * èŽ·å–æµç¨‹å…¨éƒ¨èŠ‚ç‚¹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    public static List<FlowElement> getFlowElements(String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        List<FlowElement> list = new ArrayList<>();
        List<Process> processes = bpmnModel.getProcesses();
        Collection<FlowElement> flowElements = processes.get(0).getFlowElements();
        buildFlowElements(flowElements, list);
        return list;
    }
    /**
     * é€’归获取所有节点
     *
     * @param flowElements èŠ‚ç‚¹ä¿¡æ¯
     * @param list         é›†åˆ
     */
    private static void buildFlowElements(Collection<FlowElement> flowElements, List<FlowElement> list) {
        for (FlowElement flowElement : flowElements) {
            list.add(flowElement);
            if (flowElement instanceof SubProcess) {
                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
                buildFlowElements(subFlowElements, list);
            }
        }
    }
    /**
     * èŽ·å–å…¨éƒ¨æ‰©å±•ä¿¡æ¯
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     */
    public static Map<String, List<ExtensionElement>> getExtensionElements(String processDefinitionId) {
        Map<String, List<ExtensionElement>> map = new HashMap<>();
        List<FlowElement> flowElements = getFlowElements(processDefinitionId);
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask && CollUtil.isNotEmpty(flowElement.getExtensionElements())) {
                map.putAll(flowElement.getExtensionElements());
            }
        }
        return map;
    }
    /**
     * èŽ·å–æŸä¸ªèŠ‚ç‚¹çš„æ‰©å±•ä¿¡æ¯
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @param flowElementId       èŠ‚ç‚¹id
     */
    public static Map<String, List<ExtensionElement>> getExtensionElement(String processDefinitionId, String flowElementId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        Process process = bpmnModel.getMainProcess();
        FlowElement flowElement = process.getFlowElement(flowElementId);
        return flowElement.getExtensionElements();
    }
    /**
     * åˆ¤æ–­å½“前节点是否为用户任务
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @param taskDefinitionKey   æµç¨‹å®šä¹‰id
     */
    public static boolean isUserTask(String processDefinitionId, String taskDefinitionKey) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
        return flowNode instanceof UserTask;
    }
    /**
     * èŽ·å–ç”³è¯·äººèŠ‚ç‚¹
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    public static UserTask getApplyUserTask(String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        List<StartEvent> startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList());
        StartEvent startEvent = startEventList.get(0);
        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
        FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
        return (UserTask) targetFlowElement;
    }
}
在上述文件截断后对比
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/QueryUtils.java ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfDefinitionConfigMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfFormManageMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfNodeConfigMapper.xml ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfTaskBackNodeMapper.xml script/bin/ry.bat script/bin/ry.sh script/bpmn/模型.zip script/docker/docker-compose.yml script/docker/nginx/conf/nginx.conf script/sql/flowable.sql script/sql/oracle/flowable.sql script/sql/oracle/oracle_ry_vue_5.X.sql script/sql/oracle/oracle_test.sql (已删除) script/sql/oracle/powerjob.sql (已删除) script/sql/oracle/snail_job_oracle.sql script/sql/postgres/flowable.sql script/sql/postgres/postgres_ry_vue_5.X.sql script/sql/postgres/postgres_test.sql (已删除) script/sql/postgres/powerjob.sql (已删除) script/sql/postgres/snail_job_postgre.sql script/sql/powerjob.sql (已删除) script/sql/ry_vue_5.X.sql script/sql/snail_job.sql script/sql/sqlserver/flowable.sql script/sql/sqlserver/powerjob.sql (已删除) script/sql/sqlserver/snail_job_sqlserver.sql script/sql/sqlserver/sqlserver_ry_vue_5.X.sql script/sql/sqlserver/sqlserver_test.sql (已删除) script/sql/test.sql (已删除) script/sql/update/oracle/update_5.0-5.1.sql script/sql/update/oracle/update_5.1.0-5.1.1.sql script/sql/update/oracle/update_5.1.1-5.1.2.sql script/sql/update/oracle/update_5.1.2-5.2.0.sql script/sql/update/postgres/update_5.0-5.1.sql script/sql/update/postgres/update_5.1.1-5.1.2.sql script/sql/update/postgres/update_5.1.2-5.2.0.sql script/sql/update/sqlserver/update_5.0-5.1.sql script/sql/update/sqlserver/update_5.1.2-5.2.0.sql script/sql/update/update_5.1.1-5.1.2.sql script/sql/update/update_5.1.2-5.2.0.sql