baoshiwei
2025-03-12 f1208474f771a1c233d7425c8ed13fbaa0d521ac
Merge remote-tracking branch 'origin/5.X' into 5.X

# Conflicts:
# pom.xml
# ruoyi-admin/src/main/resources/application-dev.yml
# ruoyi-admin/src/main/resources/application.yml
已添加77个文件
已复制1个文件
已重命名8个文件
已删除103个文件
已修改191个文件
25134 ■■■■ 文件已修改
.run/ruoyi-monitor-admin.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-server.run.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-snailjob-server.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/Dockerfile 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/logback-plus.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/zhFonts/.uuid 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/zhFonts/SIMSUN.TTC 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/zhFonts/fonts.dir 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/zhFonts/fonts.scale 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-bom/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java 79 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java 257 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/pom.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-websocket/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/Dockerfile 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/Dockerfile 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/pom.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | 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 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java 2 ●●● 补丁 | 查看 | 原始文档 | 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 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java 112 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/pom.xml 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java 113 ●●●● 补丁 | 查看 | 原始文档 | 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 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java 148 ●●●●● 补丁 | 查看 | 原始文档 | 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/FlwCategoryController.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | 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/FlowCategory.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 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java 20 ●●●● 补丁 | 查看 | 原始文档 | 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/FlowCancelBo.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java 8 ●●●● 补丁 | 查看 | 原始文档 | 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 8 ●●●● 补丁 | 查看 | 原始文档 | 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/TaskOperationBo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | 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/TestLeaveBo.java 1 ●●●● 补丁 | 查看 | 原始文档 | 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 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowVariableVo.java 2 ●●● 补丁 | 查看 | 原始文档 | 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/WfCategoryVo.java 58 ●●●●● 补丁 | 查看 | 原始文档 | 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 66 ●●●●● 补丁 | 查看 | 原始文档 | 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 83 ●●●●● 补丁 | 查看 | 原始文档 | 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/FlowProcessEventHandler.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/handler/TaskTimeoutJobHandler.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java 130 ●●●●● 补丁 | 查看 | 原始文档 | 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/FlwCategoryMapper.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java 57 ●●●●● 补丁 | 查看 | 原始文档 | 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 110 ●●●●● 补丁 | 查看 | 原始文档 | 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/IFlwCategoryService.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java 3 ●●●● 补丁 | 查看 | 原始文档 | 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 431 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java 444 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java 691 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java 858 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java 687 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | 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 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java 133 ●●●● 补丁 | 查看 | 原始文档 | 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 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java 363 ●●●●● 补丁 | 查看 | 原始文档 | 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/FlwCategoryMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml 115 ●●●●● 补丁 | 查看 | 原始文档 | 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/bpmn/模型.zip 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/database.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/docker-compose.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/leave/leave1.json 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/leave/leave2.json 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/leave/leave3.json 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/leave/leave4.json 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/leave/leave5.json 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/flowable.sql 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/flowable.sql 261 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/oracle_ry_job.sql 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/oracle_ry_vue_5.X.sql 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/oracle/oracle_ry_workflow.sql 414 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/flowable.sql 275 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/postgres_ry_job.sql 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/postgres_ry_vue_5.X.sql 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/postgres_ry_workflow.sql 405 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/ry_job.sql 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/ry_vue_5.X.sql 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/ry_workflow.sql 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/flowable.sql 456 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/sqlserver_ry_job.sql 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/sqlserver/sqlserver_ry_workflow.sql 1336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-monitor-admin.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.2" />
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
      </settings>
.run/ruoyi-server.run.xml
@@ -1,12 +1,12 @@
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机">
  <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.2.2" />
        <option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
      </settings>
    </deployment>
    <method v="2" />
  </configuration>
</component>
</component>
.run/ruoyi-snailjob-server.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.2" />
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
      </settings>
README.md
@@ -6,22 +6,24 @@
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]()
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
> RuoYi-Vue-Plus æ˜¯é‡å†™ RuoYi-Vue é’ˆå¯¹ `分布式集群与多租户` åœºæ™¯å…¨æ–¹ä½å‡çº§(不兼容原框架)
> Dromara RuoYi-Vue-Plus æ˜¯é‡å†™ RuoYi-Vue é’ˆå¯¹ `分布式集群与多租户` åœºæ™¯å…¨æ–¹ä½å‡çº§(不兼容原框架)
> é¡¹ç›®ä»£ç ã€æ–‡æ¡£ å‡å¼€æºå…è´¹å¯å•†ç”¨ éµå¾ªå¼€æºåè®®åœ¨é¡¹ç›®ä¸­ä¿ç•™å¼€æºåè®®æ–‡ä»¶å³å¯<br>
活到老写到老 ä¸ºå…´è¶£è€Œå¼€æº ä¸ºå­¦ä¹ è€Œå¼€æº ä¸ºè®©å¤§å®¶çœŸæ­£å¯ä»¥å­¦åˆ°æŠ€æœ¯è€Œå¼€æº
> ç³»ç»Ÿæ¼”示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> å‰ç«¯é¡¹ç›®åœ°å€: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
> å®˜æ–¹å‰ç«¯é¡¹ç›®åœ°å€: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
> æˆå‘˜å‰ç«¯é¡¹ç›®åœ°å€: åŸºäºŽvben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
> æ–‡æ¡£åœ°å€: [plus-doc](https://plus-doc.dromara.org)
@@ -31,6 +33,7 @@
CCFlow é©°è˜ä½Žä»£ç -流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 è½¯ä»¶å®šåˆ¶å¼€å‘APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 è½¯ä»¶å¼€å‘平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 å¤šç§Ÿæˆ·å•†åŸŽæºç å¯å…è´¹å•†ç”¨å¯äºŒæ¬¡å¼€å‘ - https://www.73app.cn/** </font><br>
[如何成为赞助商 åŠ ç¾¤è”ç³»ä½œè€…è¯¦è°ˆ](https://plus-doc.dromara.org/#/common/add_group)
# æœ¬æ¡†æž¶ä¸ŽRuoYi的功能差异
@@ -165,8 +168,8 @@
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829153637063344/3c21fd4c_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1735829181303499815/4522cefa_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1735829377205259767/76a705d7_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1722959592856812900/e2d0d342_1419627.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
pom.xml
@@ -13,51 +13,49 @@
    <description>兰宝车间质量管理系统</description>
    <properties>
        <revision>5.2.2</revision>
        <spring-boot.version>3.2.9</spring-boot.version>
        <revision>5.3.0</revision>
        <spring-boot.version>3.4.2</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <mybatis.version>3.5.16</mybatis.version>
        <springdoc.version>2.6.0</springdoc.version>
        <springdoc.version>2.8.4</springdoc.version>
        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
        <easyexcel.version>4.0.2</easyexcel.version>
        <easyexcel.version>4.0.3</easyexcel.version>
        <velocity.version>2.3</velocity.version>
        <satoken.version>1.38.0</satoken.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <satoken.version>1.40.0</satoken.version>
        <mybatis-plus.version>3.5.10</mybatis-plus.version>
        <p6spy.version>3.9.1</p6spy.version>
        <hutool.version>5.8.31</hutool.version>
        <okhttp.version>4.10.0</okhttp.version>
        <spring-boot-admin.version>3.2.3</spring-boot-admin.version>
        <redisson.version>3.34.1</redisson.version>
        <hutool.version>5.8.35</hutool.version>
        <spring-boot-admin.version>3.4.1</spring-boot-admin.version>
        <redisson.version>3.44.0</redisson.version>
        <lock4j.version>2.2.7</lock4j.version>
        <dynamic-ds.version>4.3.1</dynamic-ds.version>
        <snailjob.version>1.1.2</snailjob.version>
        <mapstruct-plus.version>1.4.4</mapstruct-plus.version>
        <snailjob.version>1.3.0</snailjob.version>
        <mapstruct-plus.version>1.4.6</mapstruct-plus.version>
        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
        <lombok.version>1.18.34</lombok.version>
        <lombok.version>1.18.36</lombok.version>
        <bouncycastle.version>1.76</bouncycastle.version>
        <justauth.version>1.16.6</justauth.version>
        <justauth.version>1.16.7</justauth.version>
        <!-- ç¦»çº¿IP地址定位库 -->
        <ip2region.version>2.7.0</ip2region.version>
        <undertow.version>2.3.15.Final</undertow.version>
        <!-- OSS é…ç½® -->
        <aws.sdk.version>2.25.15</aws.sdk.version>
        <aws.crt.version>0.29.13</aws.crt.version>
        <aws.sdk.version>2.28.22</aws.sdk.version>
        <aws.crt.version>0.31.3</aws.crt.version>
        <!-- SMS é…ç½® -->
        <sms4j.version>3.3.2</sms4j.version>
        <sms4j.version>3.3.3</sms4j.version>
        <!-- é™åˆ¶æ¡†æž¶ä¸­çš„fastjson版本 -->
        <fastjson.version>1.2.83</fastjson.version>
        <!-- é¢å‘运行时的D-ORM依赖 -->
        <anyline.version>8.7.2-20240808</anyline.version>
        <anyline.version>8.7.2-20250101</anyline.version>
        <!--工作流配置-->
        <flowable.version>7.0.1</flowable.version>
        <warm-flow.version>1.6.6</warm-flow.version>
        <!-- æ’件版本 -->
        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
        <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
        <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
        <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
    </properties>
@@ -69,6 +67,8 @@
                <!-- çŽ¯å¢ƒæ ‡è¯†ï¼Œéœ€è¦ä¸Žé…ç½®æ–‡ä»¶çš„åç§°ç›¸å¯¹åº” -->
                <profiles.active>local</profiles.active>
                <logging.level>info</logging.level>
                <monitor.username>ruoyi</monitor.username>
                <monitor.password>123456</monitor.password>
            </properties>
        </profile>
        <profile>
@@ -77,6 +77,8 @@
                <!-- çŽ¯å¢ƒæ ‡è¯†ï¼Œéœ€è¦ä¸Žé…ç½®æ–‡ä»¶çš„åç§°ç›¸å¯¹åº” -->
                <profiles.active>dev</profiles.active>
                <logging.level>info</logging.level>
                <monitor.username>ruoyi</monitor.username>
                <monitor.password>123456</monitor.password>
            </properties>
            <activation>
                <!-- é»˜è®¤çŽ¯å¢ƒ -->
@@ -88,6 +90,8 @@
            <properties>
                <profiles.active>prod</profiles.active>
                <logging.level>warn</logging.level>
                <monitor.username>ruoyi</monitor.username>
                <monitor.password>123456</monitor.password>
            </properties>
        </profile>
    </profiles>
@@ -114,12 +118,16 @@
                <scope>import</scope>
            </dependency>
            <!-- Warm-Flow国产工作流引擎, åœ¨çº¿æ–‡æ¡£ï¼šhttp://warm-flow.cn/ -->
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-bom</artifactId>
                <version>${flowable.version}</version>
                <type>pom</type>
                <scope>import</scope>
                <groupId>org.dromara.warm</groupId>
                <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
                <version>${warm-flow.version}</version>
            </dependency>
            <dependency>
                <groupId>org.dromara.warm</groupId>
                <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
                <version>${warm-flow.version}</version>
            </dependency>
            <!-- JustAuth çš„依赖配置-->
@@ -214,6 +222,12 @@
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-jsqlparser</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-annotation</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
@@ -223,12 +237,6 @@
                <groupId>p6spy</groupId>
                <artifactId>p6spy</artifactId>
                <version>${p6spy.version}</version>
            </dependency>
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>${okhttp.version}</version>
            </dependency>
            <!--  AWS SDK for Java 2.x  -->
@@ -313,25 +321,9 @@
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-core</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-servlet</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <groupId>io.undertow</groupId>
                <artifactId>undertow-websockets-jsr</artifactId>
                <version>${undertow.version}</version>
            </dependency>
            <dependency>
                <artifactId>commons-compress</artifactId>
                <groupId>org.apache.commons</groupId>
                <version>1.26.2</version>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.15.0</version>
            </dependency>
            <dependency>
@@ -394,7 +386,7 @@
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.verison}</version>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
ruoyi-admin/Dockerfile
@@ -1,6 +1,6 @@
# è´å°”实验室 Spring å®˜æ–¹æŽ¨èé•œåƒ JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@@ -16,6 +16,10 @@
EXPOSE ${SERVER_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
# å·¥ä½œæµå­—体文件
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
           # åº”用名称 å¦‚果想区分集群节点监控 æ”¹æˆä¸åŒçš„名称即可
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -2,6 +2,7 @@
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -12,7 +13,7 @@
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.RegisterBody;
@@ -92,7 +93,7 @@
        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
            log.info("客户端id: {} è®¤è¯ç±»åž‹ï¼š{} å¼‚常!.", clientId, grantType);
            return R.fail(MessageUtils.message("auth.grant.type.error"));
        } else if (!UserConstants.NORMAL.equals(client.getStatus())) {
        } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
            return R.fail(MessageUtils.message("auth.grant.type.blocked"));
        }
        // æ ¡éªŒç§Ÿæˆ·
@@ -111,7 +112,7 @@
    }
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•请求
     * èŽ·å–è·³è½¬URL
     *
     * @param source ç™»å½•来源
     * @return ç»“æžœ
@@ -133,13 +134,15 @@
    }
    /**
     * ç¬¬ä¸‰æ–¹ç™»å½•回调业务处理 ç»‘定授权
     * å‰ç«¯å›žè°ƒç»‘定授权(需要token)
     *
     * @param loginBody è¯·æ±‚体
     * @return ç»“æžœ
     */
    @PostMapping("/social/callback")
    public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
        // æ ¡éªŒtoken
        StpUtil.checkLogin();
        // èŽ·å–ç¬¬ä¸‰æ–¹ç™»å½•ä¿¡æ¯
        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
                loginBody.getSource(), loginBody.getSocialCode(),
@@ -155,12 +158,14 @@
    /**
     * å–消授权
     * å–消授权(需要token)
     *
     * @param socialId socialId
     */
    @DeleteMapping(value = "/unlock/{socialId}")
    public R<Void> unlockSocial(@PathVariable Long socialId) {
        // æ ¡éªŒtoken
        StpUtil.checkLogin();
        Boolean rows = socialUserService.deleteWithValidById(socialId);
        return rows ? R.ok() : R.fail("取消授权失败");
    }
@@ -226,7 +231,7 @@
        }
        // æ ¹æ®åŸŸåè¿›è¡Œç­›é€‰
        List<TenantListVo> list = StreamUtils.filter(voList, vo ->
                StringUtils.equals(vo.getDomain(), host));
            StringUtils.equalsIgnoreCase(vo.getDomain(), host));
        result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
        return R.ok(result);
    }
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -12,11 +12,12 @@
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
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.*;
@@ -60,6 +61,7 @@
    private final ISysSocialService sysSocialService;
    private final ISysRoleService roleService;
    private final ISysDeptService deptService;
    private final ISysPostService postService;
    private final SysUserMapper userMapper;
@@ -148,21 +150,24 @@
     */
    public LoginUser buildLoginUser(SysUserVo user) {
        LoginUser loginUser = new LoginUser();
        Long userId = user.getUserId();
        loginUser.setTenantId(user.getTenantId());
        loginUser.setUserId(user.getUserId());
        loginUser.setUserId(userId);
        loginUser.setDeptId(user.getDeptId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickname(user.getNickName());
        loginUser.setUserType(user.getUserType());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
        loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
        loginUser.setRolePermission(permissionService.getRolePermission(userId));
        if (ObjectUtil.isNotNull(user.getDeptId())) {
            Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
            loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
            loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
        }
        List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
        List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
        List<SysPostVo> posts = postService.selectPostsByUserId(userId);
        loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
        loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
        return loginUser;
    }
@@ -223,17 +228,17 @@
        if (!TenantHelper.isEnable()) {
            return;
        }
        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
            return;
        }
        if (StringUtils.isBlank(tenantId)) {
            throw new TenantException("tenant.number.not.blank");
        }
        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
            return;
        }
        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
        if (ObjectUtil.isNull(tenant)) {
            log.info("登录租户:{} ä¸å­˜åœ¨.", tenantId);
            throw new TenantException("tenant.not.exists");
        } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
            log.info("登录租户:{} å·²è¢«åœç”¨.", tenantId);
            throw new TenantException("tenant.blocked");
        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java
@@ -84,11 +84,11 @@
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
            throw new CaptchaException();
        }
    }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
@@ -8,10 +8,10 @@
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.EmailLoginBody;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
@@ -92,7 +92,7 @@
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:{} ä¸å­˜åœ¨.", email);
            throw new UserException("user.not.exists", email);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
            log.info("登录用户:{} å·²è¢«åœç”¨.", email);
            throw new UserException("user.blocked", email);
        }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -9,10 +9,10 @@
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
@@ -113,7 +113,7 @@
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:{} ä¸å­˜åœ¨.", username);
            throw new UserException("user.not.exists", username);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
            log.info("登录用户:{} å·²è¢«åœç”¨.", username);
            throw new UserException("user.blocked", username);
        }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
@@ -8,10 +8,10 @@
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SmsLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
@@ -92,7 +92,7 @@
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:{} ä¸å­˜åœ¨.", phonenumber);
            throw new UserException("user.not.exists", phonenumber);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
            log.info("登录用户:{} å·²è¢«åœç”¨.", phonenumber);
            throw new UserException("user.blocked", phonenumber);
        }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -11,9 +11,9 @@
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
@@ -121,7 +121,7 @@
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:{} ä¸å­˜åœ¨.", "");
            throw new UserException("user.not.exists", "");
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
            log.info("登录用户:{} å·²è¢«åœç”¨.", "");
            throw new UserException("user.blocked", "");
        }
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
@@ -5,13 +5,20 @@
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.XcxLoginBody;
import org.dromara.common.core.domain.model.XcxLoginUser;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils;
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;
@@ -40,12 +47,24 @@
        // å¤šä¸ªå°ç¨‹åºè¯†åˆ«ä½¿ç”¨
        String appid = loginBody.getAppid();
        // todo ä»¥ä¸‹è‡ªè¡Œå®žçް
        // æ ¡éªŒ appid + appsrcret + xcxCode è°ƒç”¨ç™»å½•凭证校验接口 èŽ·å– session_key ä¸Ž openid
        String openid = "";
        AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
            .clientId(appid).clientSecret("自行填写密钥 å¯æ ¹æ®ä¸åŒappid填入不同密钥")
            .ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
        AuthCallback authCallback = new AuthCallback();
        authCallback.setCode(xcxCode);
        AuthResponse<AuthUser> resp = authRequest.login(authCallback);
        String openid, unionId;
        if (resp.ok()) {
            AuthToken token = resp.getData().getToken();
            openid = token.getOpenId();
            // å¾®ä¿¡å°ç¨‹åºåªæœ‰å…³è”到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
            unionId = token.getUnionId();
        } else {
            throw new ServiceException(resp.getMsg());
        }
        // æ¡†æž¶ç™»å½•不限制从什么表查询 åªè¦æœ€ç»ˆæž„建出 LoginUser å³å¯
        SysUserVo user = loadUserByOpenid(openid);
        // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser å±žæ€§ä¸å¤Ÿç”¨ç»§æ‰¿æ‰©å±•就行了
        XcxLoginUser loginUser = new XcxLoginUser();
        loginUser.setTenantId(user.getTenantId());
@@ -82,7 +101,7 @@
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户:{} ä¸å­˜åœ¨.", openid);
            // todo ç”¨æˆ·ä¸å­˜åœ¨ ä¸šåŠ¡é€»è¾‘è‡ªè¡Œå®žçŽ°
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
            log.info("登录用户:{} å·²è¢«åœç”¨.", openid);
            // todo ç”¨æˆ·å·²è¢«åœç”¨ ä¸šåŠ¡é€»è¾‘è‡ªè¡Œå®žçŽ°
        }
ruoyi-admin/src/main/resources/application-dev.yml
@@ -8,8 +8,8 @@
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
  username: @monitor.username@
  password: @monitor.password@
--- # snail-job é…ç½®
snail-job:
@@ -25,6 +25,10 @@
  namespace: ${spring.profiles.active}
  # éšä¸»åº”用端口飘逸
  port: 2${server.port}
  # å®¢æˆ·ç«¯ip指定
  host:
  # RPC类型: netty, grpc
  rpc-type: grpc
--- # æ•°æ®æºé…ç½®
spring:
@@ -198,7 +202,7 @@
      redirect-uri: ${justauth.address}/social-callback?source=maxkey
    topiam:
      # topiam æœåŠ¡å™¨åœ°å€
      server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
      server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
      client-id: 449c4*********937************759
      client-secret: ac7***********1e0************28d
      redirect-uri: ${justauth.address}/social-callback?source=topiam
ruoyi-admin/src/main/resources/application-prod.yml
@@ -11,23 +11,27 @@
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
  username: @monitor.username@
  password: @monitor.password@
--- # snail-job é…ç½®
snail-job:
  enabled: true
  # éœ€è¦åœ¨ SnailJob åŽå°ç»„管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
  group: "ruoyi_group"
  # SnailJob æŽ¥å…¥éªŒè¯ä»¤ç‰Œ è¯¦è§ script/sql/snail_job.sql `sj_group_config` è¡¨
  # SnailJob æŽ¥å…¥éªŒè¯ä»¤ç‰Œ è¯¦è§ script/sql/ry_job.sql `sj_group_config`表
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
  server:
    host: 127.0.0.1
    port: 17888
  # è¯¦è§ script/sql/snail_job.sql `sj_namespace` è¡¨
  # å‘½åç©ºé—´UUID è¯¦è§ script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
  namespace: ${spring.profiles.active}
  # éšä¸»åº”用端口飘逸
  # éšä¸»åº”用端口漂移
  port: 2${server.port}
  # å®¢æˆ·ç«¯ip指定
  host:
  # RPC类型: netty, grpc
  rpc-type: grpc
--- # æ•°æ®æºé…ç½®
spring:
@@ -51,14 +55,14 @@
          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
        # ä»Žåº“数据源
        slave:
          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&nullCatalogMeansCurrent=true
          username:
          password:
#        # ä»Žåº“数据源
#        slave:
#          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&nullCatalogMeansCurrent=true
#          username:
#          password:
#        oracle:
#          type: ${spring.datasource.type}
#          driverClassName: oracle.jdbc.OracleDriver
ruoyi-admin/src/main/resources/application.yml
@@ -121,6 +121,7 @@
    # swagger æ–‡æ¡£é…ç½®
    - /*/api-docs
    - /*/api-docs/**
    - /warm-flow-ui/token-name
# å¤šç§Ÿæˆ·é…ç½®
tenant:
@@ -141,6 +142,8 @@
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
  # è‡ªå®šä¹‰é…ç½® æ˜¯å¦å…¨å±€å¼€å¯é€»è¾‘删除 å…³é—­åŽ æ‰€æœ‰é€»è¾‘删除功能将失效
  enableLogicDelete: true
  # å¤šåŒ…名使用 ä¾‹å¦‚ org.dromara.**.mapper,org.xxx.**.mapper
  mapperPackage: org.dromara.**.mapper,cn.shlanbao.**.mapper
  # å¯¹åº”çš„ XML æ–‡ä»¶ä½ç½®
@@ -217,7 +220,9 @@
      packages-to-scan: org.dromara.system
    - group: 4.代码生成模块
      packages-to-scan: org.dromara.generator
    - group: 5.质量模块
    - group: 5.工作流模块
      packages-to-scan: org.dromara.workflow
    - group: 6.质量模块
      packages-to-scan: cn.shlanbao.qms
# é˜²æ­¢XSS攻击
@@ -225,9 +230,9 @@
  # è¿‡æ»¤å¼€å…³
  enabled: true
  # æŽ’除链接(多个用逗号分隔)
  excludes: /system/notice
  # åŒ¹é…é“¾æŽ¥
  urlPatterns: /system/*,/monitor/*,/tool/*
  excludeUrls:
    - /system/notice
    - /warm-flow/save-xml
# å…¨å±€çº¿ç¨‹æ± ç›¸å…³é…ç½®
# å¦‚使用JDK21请直接使用虚拟线程 ä¸è¦å¼€å¯æ­¤é…ç½®
@@ -272,24 +277,11 @@
  # è®¾ç½®è®¿é—®æºåœ°å€
  allowedOrigins: '*'
--- #flowable配置
flowable:
  # å¼€å…³ ç”¨äºŽå¯åЍ/停用工作流
--- # warm-flow工作流配置
warm-flow:
  # æ˜¯å¦å¼€å¯å·¥ä½œæµï¼Œé»˜è®¤true
  enabled: true
  process.enabled: ${flowable.enabled}
  eventregistry.enabled: ${flowable.enabled}
  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
  # æ˜¯å¦å¼€å¯è®¾è®¡å™¨ui
  ui: true
  # é»˜è®¤Authorization,如果有多个token,用逗号分隔
  token-name: ${sa-token.token-name},clientid
ruoyi-admin/src/main/resources/logback-plus.xml
@@ -2,7 +2,7 @@
<configuration>
    <property name="log.path" value="./logs"/>
    <property name="console.log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
              value="%cyan(%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"/>
    <!-- æŽ§åˆ¶å°è¾“出 -->
ruoyi-admin/zhFonts/.uuid
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
3f2ee348-0303-40ca-bf03-03f48d2d2141
ruoyi-admin/zhFonts/SIMSUN.TTC
Binary files differ
ruoyi-admin/zhFonts/fonts.dir
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
ruoyi-admin/zhFonts/fonts.scale
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
ruoyi-common/ruoyi-common-bom/pom.xml
@@ -14,7 +14,7 @@
    </description>
    <properties>
        <revision>5.2.2</revision>
        <revision>5.3.0</revision>
    </properties>
    <dependencyManagement>
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
@@ -4,11 +4,13 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.dromara.common.core.config.properties.ThreadPoolProperties;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.Threads;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
@@ -49,8 +51,15 @@
     */
    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService() {
        // daemon å¿…须为 true
        BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
        if (SpringUtils.isVirtual()) {
            builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
        } else {
            builder.namingPattern("schedule-pool-%d");
        }
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
            new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
            builder.build(),
            new ThreadPoolExecutor.CallerRunsPolicy()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -61,6 +61,16 @@
    String SYS_OSS = "sys_oss#30d";
    /**
     * è§’色自定义权限
     */
    String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
    /**
     * éƒ¨é—¨åŠä»¥ä¸‹æƒé™
     */
    String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
    /**
     * OSS配置
     */
    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
@@ -68,12 +68,7 @@
    Integer CAPTCHA_EXPIRATION = 2;
    /**
     * ä»¤ç‰Œ
     */
    String TOKEN = "token";
    /**
     * é¡¶çº§éƒ¨é—¨id
     * é¡¶çº§çˆ¶çº§id
     */
    Long TOP_PARENT_ID = 0L;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package org.dromara.common.core.constant;
/**
 * ç³»ç»Ÿå¸¸é‡ä¿¡æ¯
 *
 * @author Lion Li
 */
public interface SystemConstants {
    /**
     * æ­£å¸¸çŠ¶æ€
     */
    String NORMAL = "0";
    /**
     * å¼‚常状态
     */
    String DISABLE = "1";
    /**
     * æ˜¯å¦ä¸ºç³»ç»Ÿé»˜è®¤ï¼ˆæ˜¯ï¼‰
     */
    String YES = "Y";
    /**
     * æ˜¯å¦ä¸ºç³»ç»Ÿé»˜è®¤ï¼ˆå¦ï¼‰
     */
    String NO = "N";
    /**
     * æ˜¯å¦èœå•外链(是)
     */
    String YES_FRAME = "0";
    /**
     * æ˜¯å¦èœå•外链(否)
     */
    String NO_FRAME = "1";
    /**
     * èœå•类型(目录)
     */
    String TYPE_DIR = "M";
    /**
     * èœå•类型(菜单)
     */
    String TYPE_MENU = "C";
    /**
     * èœå•类型(按钮)
     */
    String TYPE_BUTTON = "F";
    /**
     * Layout组件标识
     */
    String LAYOUT = "Layout";
    /**
     * ParentView组件标识
     */
    String PARENT_VIEW = "ParentView";
    /**
     * InnerLink组件标识
     */
    String INNER_LINK = "InnerLink";
    /**
     * è¶…级管理员ID
     */
    Long SUPER_ADMIN_ID = 1L;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
@@ -8,16 +8,6 @@
public interface TenantConstants {
    /**
     * ç§Ÿæˆ·æ­£å¸¸çŠ¶æ€
     */
    String NORMAL = "0";
    /**
     * ç§Ÿæˆ·å°ç¦çŠ¶æ€
     */
    String DISABLE = "1";
    /**
     * è¶…级管理员ID
     */
    Long SUPER_ADMIN_ID = 1L;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
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 CompleteTaskDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡id
     */
    private Long taskId;
    /**
     * é™„ä»¶id
     */
    private String fileId;
    /**
     * æŠ„送人员
     */
    private List<FlowCopyDTO> flowCopyList;
    /**
     * æ¶ˆæ¯ç±»åž‹
     */
    private List<String> messageType;
    /**
     * åŠžç†æ„è§
     */
    private String message;
    /**
     * æ¶ˆæ¯é€šçŸ¥
     */
    private String notice;
    /**
     * æµç¨‹å˜é‡
     */
    private Map<String, Object> variables;
    /**
     * æ‰©å±•变量(此处为逗号分隔的ossId)
     */
    private String ext;
    public Map<String, Object> getVariables() {
        if (variables == null) {
            return new HashMap<>(16);
        }
        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        return variables;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
 * éƒ¨é—¨
 *
 * @author AprilWind
 */
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * éƒ¨é—¨ID
     */
    private Long deptId;
    /**
     * çˆ¶éƒ¨é—¨ID
     */
    private Long parentId;
    /**
     * éƒ¨é—¨åç§°
     */
    private String deptName;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
copy from ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java copy to ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
Îļþ´Ó ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java ¸´ÖÆ
@@ -1,9 +1,10 @@
package org.dromara.workflow.domain.vo;
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æŠ„送
@@ -11,7 +12,7 @@
 * @author may
 */
@Data
public class WfCopy implements Serializable {
public class FlowCopyDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.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;
/**
 * å²—位
 *
 * @author AprilWind
 */
@Data
@NoArgsConstructor
public class PostDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * å²—位ID
     */
    private Long postId;
    /**
     * éƒ¨é—¨id
     */
    private Long deptId;
    /**
     * å²—位编码
     */
    private String postCode;
    /**
     * å²—位名称
     */
    private String postName;
    /**
     * å²—位类别编码
     */
    private String postCategory;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
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 StartProcessDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸šåС唝䏀值id
     */
    private String businessId;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * æµç¨‹å˜é‡ï¼Œå‰ç«¯ä¼šæäº¤ä¸€ä¸ªå…ƒç´ {'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-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * å¯åŠ¨æµç¨‹è¿”å›žå¯¹è±¡
 *
 * @author Lion Li
 */
@Data
public class StartProcessReturnDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private Long processInstanceId;
    /**
     * ä»»åŠ¡id
     */
    private Long taskId;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package org.dromara.common.core.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * ä»»åŠ¡å—è®©äºº
 *
 * @author AprilWind
 */
@Data
@NoArgsConstructor
public class TaskAssigneeDTO implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æ€»å¤§å°
     */
    private Long total = 0L;
    /**
     *
     */
    private List<TaskHandler> list;
    public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
        this.total = total;
        this.list = list;
    }
    /**
     * å°†æºåˆ—表转换为 TaskHandler åˆ—表
     *
     * @param <T>              é€šç”¨ç±»åž‹
     * @param sourceList       å¾…转换的源列表
     * @param storageId        æå– storageId çš„函数
     * @param handlerCode      æå– handlerCode çš„函数
     * @param handlerName      æå– handlerName çš„函数
     * @param groupName        æå– groupName çš„函数
     * @param createTimeMapper æå– createTime çš„函数
     * @return è½¬æ¢åŽçš„ TaskHandler åˆ—表
     */
    public static <T> List<TaskHandler> convertToHandlerList(
        List<T> sourceList,
        Function<T, Long> storageId,
        Function<T, String> handlerCode,
        Function<T, String> handlerName,
        Function<T, Long> groupName,
        Function<T, Date> createTimeMapper) {
        return sourceList.stream()
            .map(item -> new TaskHandler(
                String.valueOf(storageId.apply(item)),
                handlerCode.apply(item),
                handlerName.apply(item),
                groupName != null ? String.valueOf(groupName.apply(item)) : null,
                createTimeMapper.apply(item)
            )).collect(Collectors.toList());
    }
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class TaskHandler {
        /**
         * ä¸»é”®
         */
        private String storageId;
        /**
         * æƒé™ç¼–码
         */
        private String handlerCode;
        /**
         * æƒé™åç§°
         */
        private String handlerName;
        /**
         * æƒé™åˆ†ç»„
         */
        private String groupName;
        /**
         * åˆ›å»ºæ—¶é—´
         */
        private Date createTime;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * åˆ é™¤æµç¨‹ç›‘听
 *
 * @author AprilWind
 */
@Data
public class ProcessDeleteEvent implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * ä¸šåŠ¡id
     */
    private String businessId;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java
@@ -4,13 +4,13 @@
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
 * æ€»ä½“流程监听
 *
 * @author may
 */
@Data
public class ProcessEvent implements Serializable {
@@ -18,14 +18,19 @@
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰key
     * ç§Ÿæˆ·ID
     */
    private String key;
    private String tenantId;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * ä¸šåŠ¡id
     */
    private String businessKey;
    private String businessId;
    /**
     * çŠ¶æ€
@@ -33,9 +38,13 @@
    private String status;
    /**
     * åŠžç†å‚æ•°
     */
    private Map<String, Object> params;
    /**
     * å½“为true时为申请人节点办理
     */
    private boolean submit;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java
@@ -10,7 +10,6 @@
 *
 * @author may
 */
@Data
public class ProcessTaskEvent implements Serializable {
@@ -18,23 +17,28 @@
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰key
     * ç§Ÿæˆ·ID
     */
    private String key;
    private String tenantId;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹key
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String taskDefinitionKey;
    private String flowCode;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹ç¼–ç 
     */
    private String nodeCode;
    /**
     * ä»»åŠ¡id
     */
    private String taskId;
    private Long taskId;
    /**
     * ä¸šåŠ¡id
     */
    private String businessKey;
    private String businessId;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java
@@ -1,8 +1,9 @@
package org.dromara.common.core.domain.model;
import org.dromara.common.core.domain.dto.RoleDTO;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import java.io.Serial;
import java.io.Serializable;
@@ -112,6 +113,11 @@
    private List<RoleDTO> roles;
    /**
     * å²—位对象
     */
    private List<PostDTO> posts;
    /**
     * æ•°æ®æƒé™ å½“前角色ID
     */
    private Long roleId;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java
@@ -5,8 +5,6 @@
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
 * å¯†ç ç™»å½•对象
 *
@@ -20,14 +18,14 @@
     * ç”¨æˆ·å
     */
    @NotBlank(message = "{user.username.not.blank}")
    @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
    @Length(min = 2, max = 20, message = "{user.username.length.valid}")
    private String username;
    /**
     * ç”¨æˆ·å¯†ç 
     */
    @NotBlank(message = "{user.password.not.blank}")
    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
    @Length(min = 5, max = 20, message = "{user.password.length.valid}")
    private String password;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java
@@ -5,8 +5,6 @@
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
 * ç”¨æˆ·æ³¨å†Œå¯¹è±¡
 *
@@ -20,14 +18,14 @@
     * ç”¨æˆ·å
     */
    @NotBlank(message = "{user.username.not.blank}")
    @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
    @Length(min = 2, max = 20, message = "{user.username.length.valid}")
    private String username;
    /**
     * ç”¨æˆ·å¯†ç 
     */
    @NotBlank(message = "{user.password.not.blank}")
    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
    @Length(min = 5, max = 20, message = "{user.password.length.valid}")
    private String password;
    private String userType;
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package org.dromara.common.core.domain.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
 * ä»»åŠ¡å—è®©äºº
 *
 * @author AprilWind
 */
@Data
@NoArgsConstructor
public class TaskAssigneeBody implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æƒé™ç¼–码
     */
    private String handlerCode;
    /**
     * æƒé™åç§°
     */
    private String handlerName;
    /**
     * æƒé™åˆ†ç»„
     */
    private String groupId;
    /**
     * å¼€å§‹æ—¶é—´
     */
    private String beginTime;
    /**
     * ç»“束时间
     */
    private String endTime;
    /**
     * å½“前页
     */
    private Integer pageNum = 1;
    /**
     * æ¯é¡µæ˜¾ç¤ºæ¡æ•°
     */
    private Integer pageSize = 10;
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java
@@ -7,6 +7,10 @@
import org.dromara.common.core.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * ä¸šåŠ¡çŠ¶æ€æžšä¸¾
@@ -16,30 +20,37 @@
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
    /**
     * å·²æ’¤é”€
     */
    CANCEL("cancel", "已撤销"),
    /**
     * è‰ç¨¿
     */
    DRAFT("draft", "草稿"),
    /**
     * å¾…审核
     */
    WAITING("waiting", "待审核"),
    /**
     * å·²å®Œæˆ
     */
    FINISH("finish", "已完成"),
    /**
     * å·²ä½œåºŸ
     */
    INVALID("invalid", "已作废"),
    /**
     * å·²é€€å›ž
     */
    BACK("back", "已退回"),
    /**
     * å·²ç»ˆæ­¢
     */
@@ -55,20 +66,72 @@
     */
    private final String desc;
    private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
        .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
    /**
     * èŽ·å–ä¸šåŠ¡çŠ¶æ€
     * æ ¹æ®çŠ¶æ€èŽ·å–å¯¹åº”çš„ BusinessStatusEnum æžšä¸¾
     *
     * @param status çŠ¶æ€
     * @param status ä¸šåŠ¡çŠ¶æ€ç 
     * @return å¯¹åº”çš„ BusinessStatusEnum æžšä¸¾ï¼Œå¦‚果找不到则返回 null
     */
    public static BusinessStatusEnum getByStatus(String status) {
        // ä½¿ç”¨ STATUS_MAP èŽ·å–å¯¹åº”çš„æžšä¸¾ï¼Œè‹¥æ‰¾ä¸åˆ°åˆ™è¿”å›ž null
        return STATUS_MAP.get(status);
    }
    /**
     * æ ¹æ®çŠ¶æ€èŽ·å–å¯¹åº”çš„ä¸šåŠ¡çŠ¶æ€æè¿°ä¿¡æ¯
     *
     * @param status ä¸šåŠ¡çŠ¶æ€ç 
     * @return è¿”回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
     */
    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);
        BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
        return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºæŒ‡å®šçš„状态之一:草稿、已撤销或已退回
     *
     * @param status è¦æ£€æŸ¥çš„状态
     * @return å¦‚果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false
     */
    public static boolean isDraftOrCancelOrBack(String status) {
        return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºæ’¤é”€ï¼Œé€€å›žï¼Œä½œåºŸï¼Œç»ˆæ­¢
     *
     * @param status status
     * @return ç»“æžœ
     */
    public static boolean initialState(String status) {
        return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
    }
    /**
     * èŽ·å–è¿è¡Œä¸­çš„å®žä¾‹çŠ¶æ€åˆ—è¡¨
     *
     * @return åŒ…含运行中实例状态的不可变列表
     * ï¼ˆåŒ…含 DRAFT、WAITING、BACK å’Œ CANCEL çŠ¶æ€ï¼‰
     */
    public static List<String> runningStatus() {
        return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
    }
    /**
     * èŽ·å–ç»“æŸå®žä¾‹çš„çŠ¶æ€åˆ—è¡¨
     *
     * @return åŒ…含结束实例状态的不可变列表
     * ï¼ˆåŒ…含 FINISH、INVALID å’Œ TERMINATION çŠ¶æ€ï¼‰
     */
    public static List<String> finishStatus() {
        return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
    }
    /**
@@ -148,5 +211,5 @@
            throw new ServiceException("流程状态为空!");
        }
    }
}
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/*
 * æ—¥æœŸæ ¼å¼
 * "yyyy":4位数的年份,例如:2023年表示为"2023"。
 * "yy":2位数的年份,例如:2023年表示为"23"。
 * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。
 * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
 * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。
 * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
 * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
 * "E":星期的缩写,例如:星期三表示为"Wed"。
 * "DDD" æˆ– "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
 * æ—¶é—´æ ¼å¼
 * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
 * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
 * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
 * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
 * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
 */
/**
 * æ—¥æœŸæ ¼å¼ä¸Žæ—¶é—´æ ¼å¼æžšä¸¾
 */
@Getter
@AllArgsConstructor
public enum FormatsType {
    /**
     * ä¾‹å¦‚:2023年表示为"23"
     */
    YY("yy"),
    /**
     * ä¾‹å¦‚:2023年表示为"2023"
     */
    YYYY("yyyy"),
    /**
     * ä¾‹ä¾‹å¦‚,2023å¹´7月可以表示为 "2023-07"
     */
    YYYY_MM("yyyy-MM"),
    /**
     * ä¾‹å¦‚,日期 "2023å¹´7月22日" å¯ä»¥è¡¨ç¤ºä¸º "2023-07-22"
     */
    YYYY_MM_DD("yyyy-MM-dd"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
     */
    YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
     */
    YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
    /**
     * ä¾‹å¦‚:下午3点30分45秒,表示为 "15:30:45"
     */
    HH_MM_SS("HH:mm:ss"),
    /**
     * ä¾‹ä¾‹å¦‚,2023å¹´7月可以表示为 "2023/07"
     */
    YYYY_MM_SLASH("yyyy/MM"),
    /**
     * ä¾‹å¦‚,日期 "2023å¹´7月22日" å¯ä»¥è¡¨ç¤ºä¸º "2023/07/22"
     */
    YYYY_MM_DD_SLASH("yyyy/MM/dd"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
     */
    YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
     */
    YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
    /**
     * ä¾‹ä¾‹å¦‚,2023å¹´7月可以表示为 "2023.07"
     */
    YYYY_MM_DOT("yyyy.MM"),
    /**
     * ä¾‹å¦‚,日期 "2023å¹´7月22日" å¯ä»¥è¡¨ç¤ºä¸º "2023.07.22"
     */
    YYYY_MM_DD_DOT("yyyy.MM.dd"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
     */
    YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
    /**
     * ä¾‹å¦‚,当前时间如果是 "2023å¹´7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
     */
    YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
    /**
     * ä¾‹å¦‚,2023å¹´7月可以表示为 "202307"
     */
    YYYYMM("yyyyMM"),
    /**
     * ä¾‹å¦‚,2023å¹´7月22日可以表示为 "20230722"
     */
    YYYYMMDD("yyyyMMdd"),
    /**
     * ä¾‹å¦‚,2023å¹´7月22日下午3点可以表示为 "2023072215"
     */
    YYYYMMDDHH("yyyyMMddHH"),
    /**
     * ä¾‹å¦‚,2023å¹´7月22日下午3点30分可以表示为 "202307221530"
     */
    YYYYMMDDHHMM("yyyyMMddHHmm"),
    /**
     * ä¾‹å¦‚,2023å¹´7月22日下午3点30分45秒可以表示为 "20230722153045"
     */
    YYYYMMDDHHMMSS("yyyyMMddHHmmss");
    /**
     * æ—¶é—´æ ¼å¼
     */
    private final String timeFormat;
    public static FormatsType getFormatsType(String str) {
        for (FormatsType value : values()) {
            if (StringUtils.contains(str, value.getTimeFormat())) {
                return value;
            }
        }
        throw new RuntimeException("'FormatsType' not found By " + str);
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package org.dromara.common.core.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
 * sse ç‰¹åˆ¶å¼‚常
 *
 * @author LionLi
 */
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class SseException extends RuntimeException {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * é”™è¯¯ç 
     */
    private Integer code;
    /**
     * é”™è¯¯æç¤º
     */
    private String message;
    /**
     * é”™è¯¯æ˜Žç»†ï¼Œå†…部调试错误
     */
    private String detailMessage;
    public SseException(String message) {
        this.message = message;
    }
    public SseException(String message, Integer code) {
        this.message = message;
        this.code = code;
    }
    @Override
    public String getMessage() {
        return message;
    }
    public SseException setMessage(String message) {
        this.message = message;
        return this;
    }
    public SseException setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java
@@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DeptDTO;
import java.util.List;
/**
 * é€šç”¨ éƒ¨é—¨æœåŠ¡
 *
@@ -15,4 +19,19 @@
     */
    String selectDeptNameByIds(String deptIds);
    /**
     * æ ¹æ®éƒ¨é—¨ID查询部门负责人
     *
     * @param deptId éƒ¨é—¨ID,用于指定需要查询的部门
     * @return è¿”回该部门的负责人ID
     */
    Long selectDeptLeaderById(Long deptId);
    /**
     * æŸ¥è¯¢éƒ¨é—¨
     *
     * @return éƒ¨é—¨åˆ—表
     */
    List<DeptDTO> selectDeptsByList();
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
 * é€šç”¨ å²—位服务
 *
 * @author AprilWind
 */
public interface PostService {
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
 * é€šç”¨ è§’色服务
 *
 * @author AprilWind
 */
public interface RoleService {
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
/**
 * å·¥ä½œæµè®¾è®¡å™¨èŽ·å–ä»»åŠ¡æ‰§è¡Œäºº
 *
 * @author Lion Li
 */
public interface TaskAssigneeService {
    /**
     * æŸ¥è¯¢è§’色并返回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
    /**
     * æŸ¥è¯¢å²—位并返回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
    /**
     * æŸ¥è¯¢éƒ¨é—¨å¹¶è¿”回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
    /**
     * æŸ¥è¯¢ç”¨æˆ·å¹¶è¿”回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
@@ -82,4 +82,13 @@
     * @return ç”¨æˆ·
     */
    List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
    /**
     * é€šè¿‡å²—位ID查询用户
     *
     * @param postIds å²—位ids
     * @return ç”¨æˆ·
     */
    List<UserDTO> selectUsersByPostIds(List<Long> postIds);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java
@@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.CompleteTaskDTO;
import org.dromara.common.core.domain.dto.StartProcessDTO;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import java.util.List;
import java.util.Map;
@@ -13,64 +17,70 @@
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     *
     * @param businessKeys ä¸šåŠ¡id
     * @param businessIds ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    boolean deleteRunAndHisInstance(List<String> businessKeys);
    boolean deleteInstance(List<Long> businessIds);
    /**
     * èŽ·å–å½“å‰æµç¨‹çŠ¶æ€
     *
     * @param taskId ä»»åŠ¡id
     * @return çŠ¶æ€
     */
    String getBusinessStatusByTaskId(String taskId);
    String getBusinessStatusByTaskId(Long taskId);
    /**
     * èŽ·å–å½“å‰æµç¨‹çŠ¶æ€
     *
     * @param businessKey ä¸šåŠ¡id
     * @param businessId ä¸šåŠ¡id
     * @return çŠ¶æ€
     */
    String getBusinessStatus(String businessKey);
    String getBusinessStatus(String businessId);
    /**
     * è®¾ç½®æµç¨‹å˜é‡(全局变量)
     * è®¾ç½®æµç¨‹å˜é‡
     *
     * @param taskId       ä»»åŠ¡id
     * @param variableName å˜é‡åç§°
     * @param value        å˜é‡å€¼
     * @param instanceId æµç¨‹å®žä¾‹id
     * @param variable   æµç¨‹å˜é‡
     */
    void setVariable(String taskId, String variableName, Object value);
    void setVariable(Long instanceId, Map<String, Object> variable);
    /**
     * è®¾ç½®æµç¨‹å˜é‡(全局变量)
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param taskId    ä»»åŠ¡id
     * @param variables æµç¨‹å˜é‡
     * @param instanceId æµç¨‹å®žä¾‹id
     */
    void setVariables(String taskId, Map<String, Object> variables);
    /**
     * è®¾ç½®æµç¨‹å˜é‡(本地变量,非全局变量)
     *
     * @param taskId       ä»»åŠ¡id
     * @param variableName å˜é‡åç§°
     * @param value        å˜é‡å€¼
     */
    void setVariableLocal(String taskId, String variableName, Object value);
    /**
     * è®¾ç½®æµç¨‹å˜é‡(本地变量,非全局变量)
     *
     * @param taskId    ä»»åŠ¡id
     * @param variables æµç¨‹å˜é‡
     */
    void setVariablesLocal(String taskId, Map<String, Object> variables);
    Map<String, Object> instanceVariable(Long instanceId);
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询流程实例id
     *
     * @param businessKey ä¸šåŠ¡id
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    String getInstanceIdByBusinessKey(String businessKey);
    Long getInstanceIdByBusinessId(String businessId);
    /**
     * æ–°å¢žç§Ÿæˆ·æµç¨‹å®šä¹‰
     *
     * @param tenantId ç§Ÿæˆ·id
     */
    void syncDef(String tenantId);
    /**
     * å¯åŠ¨æµç¨‹
     *
     * @param startProcess å‚æ•°
     * @return ç»“æžœ
     */
    StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTask å‚æ•°
     * @return ç»“æžœ
     */
    boolean completeTask(CompleteTaskDTO completeTask);
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
@@ -1,106 +1,157 @@
package org.dromara.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
 * æ—¶é—´å·¥å…·ç±»
 *
 * @author ruoyi
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
    public static final String YYYY = "yyyy";
    public static final String YYYY_MM = "yyyy-MM";
    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    private static final String[] PARSE_PATTERNS = {
        "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
        "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
        "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
    @Deprecated
    private DateUtils() {
    }
    /**
     * èŽ·å–å½“å‰Date型日期
     * èŽ·å–å½“å‰æ—¥æœŸå’Œæ—¶é—´
     *
     * @return Date() å½“前日期
     * @return å½“前日期和时间的 Date å¯¹è±¡è¡¨ç¤º
     */
    public static Date getNowDate() {
        return new Date();
    }
    /**
     * èŽ·å–å½“å‰æ—¥æœŸ, é»˜è®¤æ ¼å¼ä¸ºyyyy-MM-dd
     * èŽ·å–å½“å‰æ—¥æœŸçš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼Œæ ¼å¼ä¸ºYYYY-MM-DD
     *
     * @return String
     * @return å½“前日期的字符串表示
     */
    public static String getDate() {
        return dateTimeNow(YYYY_MM_DD);
        return dateTimeNow(FormatsType.YYYY_MM_DD);
    }
    /**
     * èŽ·å–å½“å‰æ—¥æœŸçš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼Œæ ¼å¼ä¸ºyyyyMMdd
     *
     * @return å½“前日期的字符串表示
     */
    public static String getCurrentDate() {
        return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
    }
    /**
     * èŽ·å–å½“å‰æ—¥æœŸçš„è·¯å¾„æ ¼å¼å­—ç¬¦ä¸²ï¼Œæ ¼å¼ä¸º"yyyy/MM/dd"
     *
     * @return å½“前日期的路径格式字符串
     */
    public static String datePath() {
        Date now = new Date();
        return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
    }
    /**
     * èŽ·å–å½“å‰æ—¶é—´çš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼Œæ ¼å¼ä¸ºYYYY-MM-DD HH:MM:SS
     *
     * @return å½“前时间的字符串表示
     */
    public static String getTime() {
        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
        return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
    }
    /**
     * èŽ·å–å½“å‰æ—¶é—´çš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼Œæ ¼å¼ä¸º "HH:MM:SS"
     *
     * @return å½“前时间的字符串表示,格式为 "HH:MM:SS"
     */
    public static String getTimeWithHourMinuteSecond() {
        return dateTimeNow(FormatsType.HH_MM_SS);
    }
    /**
     * èŽ·å–å½“å‰æ—¥æœŸå’Œæ—¶é—´çš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼Œæ ¼å¼ä¸ºYYYYMMDDHHMMSS
     *
     * @return å½“前日期和时间的字符串表示
     */
    public static String dateTimeNow() {
        return dateTimeNow(YYYYMMDDHHMMSS);
        return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
    }
    public static String dateTimeNow(final String format) {
    /**
     * èŽ·å–å½“å‰æ—¥æœŸå’Œæ—¶é—´çš„æŒ‡å®šæ ¼å¼çš„å­—ç¬¦ä¸²è¡¨ç¤º
     *
     * @param format æ—¥æœŸæ—¶é—´æ ¼å¼ï¼Œä¾‹å¦‚"YYYY-MM-DD HH:MM:SS"
     * @return å½“前日期和时间的字符串表示
     */
    public static String dateTimeNow(final FormatsType format) {
        return parseDateToStr(format, new Date());
    }
    public static String dateTime(final Date date) {
        return parseDateToStr(YYYY_MM_DD, date);
    /**
     * å°†æŒ‡å®šæ—¥æœŸæ ¼å¼åŒ–为 YYYY-MM-DD æ ¼å¼çš„字符串
     *
     * @param date è¦æ ¼å¼åŒ–的日期对象
     * @return æ ¼å¼åŒ–后的日期字符串
     */
    public static String formatDate(final Date date) {
        return parseDateToStr(FormatsType.YYYY_MM_DD, date);
    }
    public static String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    /**
     * å°†æŒ‡å®šæ—¥æœŸæ ¼å¼åŒ–为 YYYY-MM-DD HH:MM:SS æ ¼å¼çš„字符串
     *
     * @param date è¦æ ¼å¼åŒ–的日期对象
     * @return æ ¼å¼åŒ–后的日期时间字符串
     */
    public static String formatDateTime(final Date date) {
        return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
    }
    public static Date dateTime(final String format, final String ts) {
    /**
     * å°†æŒ‡å®šæ—¥æœŸæŒ‰ç…§æŒ‡å®šæ ¼å¼è¿›è¡Œæ ¼å¼åŒ–
     *
     * @param format è¦ä½¿ç”¨çš„æ—¥æœŸæ—¶é—´æ ¼å¼ï¼Œä¾‹å¦‚"YYYY-MM-DD HH:MM:SS"
     * @param date   è¦æ ¼å¼åŒ–的日期对象
     * @return æ ¼å¼åŒ–后的日期时间字符串
     */
    public static String parseDateToStr(final FormatsType format, final Date date) {
        return new SimpleDateFormat(format.getTimeFormat()).format(date);
    }
    /**
     * å°†æŒ‡å®šæ ¼å¼çš„æ—¥æœŸæ—¶é—´å­—符串转换为 Date å¯¹è±¡
     *
     * @param format è¦è§£æžçš„æ—¥æœŸæ—¶é—´æ ¼å¼ï¼Œä¾‹å¦‚"YYYY-MM-DD HH:MM:SS"
     * @param ts     è¦è§£æžçš„æ—¥æœŸæ—¶é—´å­—符串
     * @return è§£æžåŽçš„ Date å¯¹è±¡
     * @throws RuntimeException å¦‚果解析过程中发生异常
     */
    public static Date parseDateTime(final FormatsType format, final String ts) {
        try {
            return new SimpleDateFormat(format).parse(ts);
            return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * æ—¥æœŸè·¯å¾„ å³å¹´/月/日 å¦‚2018/08/08
     */
    public static String datePath() {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyy/MM/dd");
    }
    /**
     * æ—¥æœŸè·¯å¾„ å³å¹´/月/日 å¦‚20180808
     */
    public static String dateTime() {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyyMMdd");
    }
    /**
     * æ—¥æœŸåž‹å­—符串转化为日期 æ ¼å¼
     * å°†å¯¹è±¡è½¬æ¢ä¸ºæ—¥æœŸå¯¹è±¡
     *
     * @param str è¦è½¬æ¢çš„对象,通常是字符串
     * @return è½¬æ¢åŽçš„æ—¥æœŸå¯¹è±¡ï¼Œå¦‚果转换失败或输入为null,则返回null
     */
    public static Date parseDate(Object str) {
        if (str == null) {
@@ -115,6 +166,8 @@
    /**
     * èŽ·å–æœåŠ¡å™¨å¯åŠ¨æ—¶é—´
     *
     * @return æœåŠ¡å™¨å¯åŠ¨æ—¶é—´çš„ Date å¯¹è±¡è¡¨ç¤º
     */
    public static Date getServerStartDate() {
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
@@ -122,35 +175,66 @@
    }
    /**
     * è®¡ç®—相差天数
     * è®¡ç®—两个日期之间的天数差(以毫秒为单位)
     *
     * @param date1 ç¬¬ä¸€ä¸ªæ—¥æœŸ
     * @param date2 ç¬¬äºŒä¸ªæ—¥æœŸ
     * @return ä¸¤ä¸ªæ—¥æœŸä¹‹é—´çš„天数差的绝对值
     */
    public static int differentDaysByMillisecond(Date date1, Date date2) {
        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
    }
    /**
     * è®¡ç®—两个时间差
     * è®¡ç®—两个日期之间的时间差,并以天、小时和分钟的格式返回
     *
     * @param endDate ç»“束日期
     * @param nowDate å½“前日期
     * @return è¡¨ç¤ºæ—¶é—´å·®çš„字符串,格式为"天 å°æ—¶ åˆ†é’Ÿ"
     */
    public static String getDatePoor(Date endDate, Date nowDate) {
        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        // long ns = 1000;
        // èŽ·å¾—ä¸¤ä¸ªæ—¶é—´çš„æ¯«ç§’æ—¶é—´å·®å¼‚
        long diff = endDate.getTime() - nowDate.getTime();
        // è®¡ç®—差多少天
        long day = diff / nd;
        // è®¡ç®—差多少小时
        long hour = diff % nd / nh;
        // è®¡ç®—差多少分钟
        long min = diff % nd % nh / nm;
        // è®¡ç®—差多少秒//输出结果
        // long sec = diff % nd % nh % nm / ns;
        return day + "天" + hour + "小时" + min + "分钟";
        long diffInMillis = endDate.getTime() - nowDate.getTime();
        long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
        long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
        long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
        return String.format("%d天 %d小时 %d分钟", day, hour, min);
    }
    /**
     * å¢žåŠ  LocalDateTime ==> Date
     * è®¡ç®—两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位
     *
     * @param endDate ç»“束时间
     * @param nowDate å½“前时间
     * @return æ—¶é—´å·®å­—符串,格式为 "x天 x小时 x分钟 x秒",若为 0 åˆ™ä¸æ˜¾ç¤º
     */
    public static String getTimeDifference(Date endDate, Date nowDate) {
        long diffInMillis = endDate.getTime() - nowDate.getTime();
        long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
        long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
        long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
        long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
        // æž„建时间差字符串,条件是值不为0才显示
        StringBuilder result = new StringBuilder();
        if (day > 0) {
            result.append(String.format("%d天 ", day));
        }
        if (hour > 0) {
            result.append(String.format("%d小时 ", hour));
        }
        if (min > 0) {
            result.append(String.format("%d分钟 ", min));
        }
        if (sec > 0) {
            result.append(String.format("%d秒", sec));
        }
        return result.length() > 0 ? result.toString().trim() : "0秒";
    }
    /**
     * å°† LocalDateTime å¯¹è±¡è½¬æ¢ä¸º Date å¯¹è±¡
     *
     * @param temporalAccessor è¦è½¬æ¢çš„ LocalDateTime å¯¹è±¡
     * @return è½¬æ¢åŽçš„ Date å¯¹è±¡
     */
    public static Date toDate(LocalDateTime temporalAccessor) {
        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
@@ -158,11 +242,46 @@
    }
    /**
     * å¢žåŠ  LocalDate ==> Date
     * å°† LocalDate å¯¹è±¡è½¬æ¢ä¸º Date å¯¹è±¡
     *
     * @param temporalAccessor è¦è½¬æ¢çš„ LocalDate å¯¹è±¡
     * @return è½¬æ¢åŽçš„ Date å¯¹è±¡
     */
    public static Date toDate(LocalDate temporalAccessor) {
        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }
    /**
     * æ ¡éªŒæ—¥æœŸèŒƒå›´
     *
     * @param startDate å¼€å§‹æ—¥æœŸ
     * @param endDate   ç»“束日期
     * @param maxValue  æœ€å¤§æ—¶é—´è·¨åº¦çš„限制值
     * @param unit      æ—¶é—´è·¨åº¦çš„单位,可选择 "DAYS"、"HOURS" æˆ– "MINUTES"
     */
    public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
        // æ ¡éªŒç»“束日期不能早于开始日期
        if (endDate.before(startDate)) {
            throw new ServiceException("结束日期不能早于开始日期");
        }
        // è®¡ç®—时间跨度
        long diffInMillis = endDate.getTime() - startDate.getTime();
        // æ ¹æ®å•位转换时间跨度
        long diff = switch (unit) {
            case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
            case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
            case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
            default -> throw new IllegalArgumentException("不支持的时间单位");
        };
        // æ ¡éªŒæ—¶é—´è·¨åº¦ä¸è¶…过最大限制
        if (diff > maxValue) {
            throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
        }
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package org.dromara.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.function.Function;
/**
 * å¯¹è±¡å·¥å…·ç±»
 *
 * @author ç§‹è¾žæœªå¯’
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils extends ObjectUtil {
    /**
     * å¦‚果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
     *
     * @param obj å¯¹è±¡
     * @param func èŽ·å–æ–¹æ³•
     * @return å¯¹è±¡å­—段
     */
    public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
        if (isNotNull(obj) && isNotNull(func)) {
            return func.apply(obj);
        }
        return null;
    }
    /**
     * å¦‚果对象不为空,则获取对象中的某个字段,否则返回默认值
     *
     * @param obj          å¯¹è±¡
     * @param func         èŽ·å–æ–¹æ³•
     * @param defaultValue é»˜è®¤å€¼
     * @return å¯¹è±¡å­—段
     */
    public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
        if (isNotNull(obj) && isNotNull(func)) {
            return func.apply(obj);
        }
        return defaultValue;
    }
    /**
     * å¦‚果值不为空,则返回值,否则返回默认值
     *
     * @param obj          å¯¹è±¡
     * @param defaultValue é»˜è®¤å€¼
     * @return å¯¹è±¡å­—段
     */
    public static <T> T notNull(T obj, T defaultValue) {
        if (isNotNull(obj)) {
            return obj;
        }
        return defaultValue;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
@@ -25,7 +25,7 @@
import java.util.Map;
/**
 * å®¢æˆ·ç«¯å·¥å…·ç±»
 * å®¢æˆ·ç«¯å·¥å…·ç±»ï¼Œæä¾›èŽ·å–è¯·æ±‚å‚æ•°ã€å“åº”å¤„ç†ã€å¤´éƒ¨ä¿¡æ¯ç­‰å¸¸ç”¨æ“ä½œ
 *
 * @author ruoyi
 */
@@ -33,52 +33,73 @@
public class ServletUtils extends JakartaServletUtil {
    /**
     * èŽ·å–String参数
     * èŽ·å–æŒ‡å®šåç§°çš„ String ç±»åž‹çš„请求参数
     *
     * @param name å‚数名
     * @return å‚数值
     */
    public static String getParameter(String name) {
        return getRequest().getParameter(name);
    }
    /**
     * èŽ·å–String参数
     * èŽ·å–æŒ‡å®šåç§°çš„ String ç±»åž‹çš„请求参数,若参数不存在,则返回默认值
     *
     * @param name         å‚数名
     * @param defaultValue é»˜è®¤å€¼
     * @return å‚数值或默认值
     */
    public static String getParameter(String name, String defaultValue) {
        return Convert.toStr(getRequest().getParameter(name), defaultValue);
    }
    /**
     * èŽ·å–Integer参数
     * èŽ·å–æŒ‡å®šåç§°çš„ Integer ç±»åž‹çš„请求参数
     *
     * @param name å‚数名
     * @return å‚数值
     */
    public static Integer getParameterToInt(String name) {
        return Convert.toInt(getRequest().getParameter(name));
    }
    /**
     * èŽ·å–Integer参数
     * èŽ·å–æŒ‡å®šåç§°çš„ Integer ç±»åž‹çš„请求参数,若参数不存在,则返回默认值
     *
     * @param name         å‚数名
     * @param defaultValue é»˜è®¤å€¼
     * @return å‚数值或默认值
     */
    public static Integer getParameterToInt(String name, Integer defaultValue) {
        return Convert.toInt(getRequest().getParameter(name), defaultValue);
    }
    /**
     * èŽ·å–Boolean参数
     * èŽ·å–æŒ‡å®šåç§°çš„ Boolean ç±»åž‹çš„请求参数
     *
     * @param name å‚数名
     * @return å‚数值
     */
    public static Boolean getParameterToBool(String name) {
        return Convert.toBool(getRequest().getParameter(name));
    }
    /**
     * èŽ·å–Boolean参数
     * èŽ·å–æŒ‡å®šåç§°çš„ Boolean ç±»åž‹çš„请求参数,若参数不存在,则返回默认值
     *
     * @param name         å‚数名
     * @param defaultValue é»˜è®¤å€¼
     * @return å‚数值或默认值
     */
    public static Boolean getParameterToBool(String name, Boolean defaultValue) {
        return Convert.toBool(getRequest().getParameter(name), defaultValue);
    }
    /**
     * èŽ·å¾—æ‰€æœ‰è¯·æ±‚å‚æ•°
     * èŽ·å–æ‰€æœ‰è¯·æ±‚å‚æ•°ï¼ˆä»¥ Map çš„形式返回)
     *
     * @param request è¯·æ±‚对象{@link ServletRequest}
     * @return Map
     * @return è¯·æ±‚参数的 Map,键为参数名,值为参数值数组
     */
    public static Map<String, String[]> getParams(ServletRequest request) {
        final Map<String, String[]> map = request.getParameterMap();
@@ -86,10 +107,10 @@
    }
    /**
     * èŽ·å¾—æ‰€æœ‰è¯·æ±‚å‚æ•°
     * èŽ·å–æ‰€æœ‰è¯·æ±‚å‚æ•°ï¼ˆä»¥ Map çš„形式返回,值为字符串形式的拼接)
     *
     * @param request è¯·æ±‚对象{@link ServletRequest}
     * @return Map
     * @return è¯·æ±‚参数的 Map,键为参数名,值为拼接后的字符串
     */
    public static Map<String, String> getParamMap(ServletRequest request) {
        Map<String, String> params = new HashMap<>();
@@ -100,7 +121,9 @@
    }
    /**
     * èŽ·å–request
     * èŽ·å–å½“å‰ HTTP è¯·æ±‚对象
     *
     * @return å½“前 HTTP è¯·æ±‚对象
     */
    public static HttpServletRequest getRequest() {
        try {
@@ -111,7 +134,9 @@
    }
    /**
     * èŽ·å–response
     * èŽ·å–å½“å‰ HTTP å“åº”对象
     *
     * @return å½“前 HTTP å“åº”对象
     */
    public static HttpServletResponse getResponse() {
        try {
@@ -122,12 +147,25 @@
    }
    /**
     * èŽ·å–session
     * èŽ·å–å½“å‰è¯·æ±‚çš„ HttpSession å¯¹è±¡
     * <p>
     * å¦‚果当前请求已经关联了一个会话(即已经存在有效的 session ID),
     * åˆ™è¿”回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。
     * <p>
     * HttpSession ç”¨äºŽå­˜å‚¨ä¼šè¯çº§åˆ«çš„æ•°æ®ï¼Œå¦‚用户登录信息、购物车内容等,
     * å¯ä»¥åœ¨å¤šä¸ªè¯·æ±‚之间共享会话数据
     *
     * @return å½“前请求的 HttpSession å¯¹è±¡
     */
    public static HttpSession getSession() {
        return getRequest().getSession();
    }
    /**
     * èŽ·å–å½“å‰è¯·æ±‚çš„è¯·æ±‚å±žæ€§
     *
     * @return {@link ServletRequestAttributes} è¯·æ±‚属性对象
     */
    public static ServletRequestAttributes getRequestAttributes() {
        try {
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
@@ -137,6 +175,13 @@
        }
    }
    /**
     * èŽ·å–æŒ‡å®šè¯·æ±‚å¤´çš„å€¼ï¼Œå¦‚æžœå¤´éƒ¨ä¸ºç©ºåˆ™è¿”å›žç©ºå­—ç¬¦ä¸²
     *
     * @param request è¯·æ±‚对象
     * @param name    å¤´éƒ¨åç§°
     * @return å¤´éƒ¨å€¼
     */
    public static String getHeader(HttpServletRequest request, String name) {
        String value = request.getHeader(name);
        if (StringUtils.isEmpty(value)) {
@@ -145,6 +190,12 @@
        return urlDecode(value);
    }
    /**
     * èŽ·å–æ‰€æœ‰è¯·æ±‚å¤´çš„ Map,键为头部名称,值为头部值
     *
     * @param request è¯·æ±‚对象
     * @return è¯·æ±‚头的 Map
     */
    public static Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedCaseInsensitiveMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
@@ -159,7 +210,7 @@
    }
    /**
     * å°†å­—符串渲染到客户端
     * å°†å­—符串渲染到客户端(以 JSON æ ¼å¼è¿”回)
     *
     * @param response æ¸²æŸ“对象
     * @param string   å¾…渲染的字符串
@@ -176,37 +227,47 @@
    }
    /**
     * æ˜¯å¦æ˜¯Ajax异步请求
     * åˆ¤æ–­å½“前请求是否为 Ajax å¼‚步请求
     *
     * @param request
     * @param request è¯·æ±‚对象
     * @return æ˜¯å¦ä¸º Ajax è¯·æ±‚
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        // åˆ¤æ–­ Accept å¤´éƒ¨æ˜¯å¦åŒ…含 application/json
        String accept = request.getHeader("accept");
        if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
            return true;
        }
        // åˆ¤æ–­ X-Requested-With å¤´éƒ¨æ˜¯å¦åŒ…含 XMLHttpRequest
        String xRequestedWith = request.getHeader("X-Requested-With");
        if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
            return true;
        }
        // åˆ¤æ–­ URI åŽç¼€æ˜¯å¦ä¸º .json æˆ– .xml
        String uri = request.getRequestURI();
        if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
            return true;
        }
        // åˆ¤æ–­è¯·æ±‚参数 __ajax æ˜¯å¦ä¸º json æˆ– xml
        String ajax = request.getParameter("__ajax");
        return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
    }
    /**
     * èŽ·å–å®¢æˆ·ç«¯ IP åœ°å€
     *
     * @return å®¢æˆ·ç«¯ IP åœ°å€
     */
    public static String getClientIP() {
        return getClientIP(getRequest());
    }
    /**
     * å†…容编码
     * å¯¹å†…容进行 URL ç¼–码
     *
     * @param str å†…容
     * @return ç¼–码后的内容
@@ -216,7 +277,7 @@
    }
    /**
     * å†…容解码
     * å¯¹å†…容进行 URL è§£ç 
     *
     * @param str å†…容
     * @return è§£ç åŽçš„内容
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
@@ -4,8 +4,6 @@
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.AntPathMatcher;
import java.util.*;
@@ -17,12 +15,15 @@
 *
 * @author Lion Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils {
    public static final String SEPARATOR = ",";
    public static final String SLASH = "/";
    @Deprecated
    private StringUtils() {
    }
    /**
     * èŽ·å–å‚æ•°ä¸ä¸ºç©ºå€¼
@@ -317,7 +318,25 @@
            .stream()
            .filter(Objects::nonNull)
            .map(mapper)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
    /**
     * ä¸åŒºåˆ†å¤§å°å†™æ£€æŸ¥ CharSequence æ˜¯å¦ä»¥æŒ‡å®šçš„前缀开头。
     *
     * @param str     è¦æ£€æŸ¥çš„ CharSequence å¯èƒ½ä¸º null
     * @param prefixs è¦æŸ¥æ‰¾çš„前缀可能为 null
     * @return æ˜¯å¦åŒ…含
     */
    public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) {
        // åˆ¤æ–­æ˜¯å¦æ˜¯ä»¥æŒ‡å®šå­—符串开头
        for (CharSequence prefix : prefixs) {
            if (StringUtils.startsWithIgnoreCase(str, prefix)) {
                return true;
            }
        }
        return false;
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java
@@ -14,18 +14,6 @@
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Threads {
    /**
     * sleep等待,单位为毫秒
     */
    public static void sleep(long milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            return;
        }
    }
    /**
     * åœæ­¢çº¿ç¨‹æ± 
     * å…ˆä½¿ç”¨shutdown, åœæ­¢æŽ¥æ”¶æ–°ä»»åŠ¡å¹¶å°è¯•å®Œæˆæ‰€æœ‰å·²å­˜åœ¨ä»»åŠ¡.
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
@@ -5,11 +5,13 @@
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.NodeParser;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * æ‰©å±• hutool TreeUtil å°è£…系统树构建
@@ -24,12 +26,71 @@
     */
    public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
    /**
     * æž„建树形结构
     *
     * @param <T>        è¾“入节点的类型
     * @param <K>        èŠ‚ç‚¹ID的类型
     * @param list       èŠ‚ç‚¹åˆ—è¡¨ï¼Œå…¶ä¸­åŒ…å«äº†è¦æž„å»ºæ ‘å½¢ç»“æž„çš„æ‰€æœ‰èŠ‚ç‚¹
     * @param nodeParser è§£æžå™¨ï¼Œç”¨äºŽå°†è¾“入节点转换为树节点
     * @return æž„建好的树形结构列表
     */
    public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
        if (CollUtil.isEmpty(list)) {
            return null;
            return CollUtil.newArrayList();
        }
        K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
        return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
    }
    /**
     * æž„建树形结构
     *
     * @param <T>        è¾“入节点的类型
     * @param <K>        èŠ‚ç‚¹ID的类型
     * @param parentId   é¡¶çº§èŠ‚ç‚¹
     * @param list       èŠ‚ç‚¹åˆ—è¡¨ï¼Œå…¶ä¸­åŒ…å«äº†è¦æž„å»ºæ ‘å½¢ç»“æž„çš„æ‰€æœ‰èŠ‚ç‚¹
     * @param nodeParser è§£æžå™¨ï¼Œç”¨äºŽå°†è¾“入节点转换为树节点
     * @return æž„建好的树形结构列表
     */
    public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) {
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
    }
    /**
     * èŽ·å–èŠ‚ç‚¹åˆ—è¡¨ä¸­æ‰€æœ‰èŠ‚ç‚¹çš„å¶å­èŠ‚ç‚¹
     *
     * @param <K>   èŠ‚ç‚¹ID的类型
     * @param nodes èŠ‚ç‚¹åˆ—è¡¨
     * @return åŒ…含所有叶子节点的列表
     */
    public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) {
        if (CollUtil.isEmpty(nodes)) {
            return CollUtil.newArrayList();
        }
        return nodes.stream()
            .flatMap(TreeBuildUtils::extractLeafNodes)
            .collect(Collectors.toList());
    }
    /**
     * èŽ·å–æŒ‡å®šèŠ‚ç‚¹ä¸‹çš„æ‰€æœ‰å¶å­èŠ‚ç‚¹
     *
     * @param <K>  èŠ‚ç‚¹ID的类型
     * @param node è¦æŸ¥æ‰¾å¶å­èŠ‚ç‚¹çš„æ ¹èŠ‚ç‚¹
     * @return åŒ…含所有叶子节点的列表
     */
    private static <K> Stream<Tree<K>> extractLeafNodes(Tree<K> node) {
        if (!node.hasChild()) {
            return Stream.of(node);
        } else {
            // é€’归调用,获取所有子节点的叶子节点
            return node.getChildren().stream()
                .flatMap(TreeBuildUtils::extractLeafNodes);
        }
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java
@@ -21,7 +21,8 @@
     */
    public static String extractFromString(String input, String regex, String defaultInput) {
        try {
            return ReUtil.get(regex, input, 1);
            String str = ReUtil.get(regex, input, 1);
            return str == null ? defaultInput : str;
        } catch (Exception e) {
            return defaultInput;
        }
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
@@ -15,7 +15,7 @@
    /**
     * å®šä¹‰å¸¸ç”¨çš„ sql关键字
     */
    public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
    public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
    /**
     * ä»…支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
 * è‡ªå®šä¹‰æžšä¸¾æ ¡éªŒ
 *
 * @author ç§‹è¾žæœªå¯’
 * @date 2024-12-09
 */
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumPattern.List.class) // å…è®¸åœ¨åŒä¸€å…ƒç´ ä¸Šå¤šæ¬¡ä½¿ç”¨è¯¥æ³¨è§£
@Constraint(validatedBy = {EnumPatternValidator.class})
public @interface EnumPattern {
    /**
     * éœ€è¦æ ¡éªŒçš„æžšä¸¾ç±»åž‹
     */
    Class<? extends Enum<?>> type();
    /**
     * æžšä¸¾ç±»åž‹æ ¡éªŒå€¼å­—段名称
     * éœ€ç¡®ä¿è¯¥å­—段实现了 getter æ–¹æ³•
     */
    String fieldName();
    String message() default "输入值不在枚举范围内";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    @Documented
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @interface List {
        EnumPattern[] value();
    }
}
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
 * è‡ªå®šä¹‰æžšä¸¾æ ¡éªŒæ³¨è§£å®žçް
 *
 * @author ç§‹è¾žæœªå¯’
 * @date 2024-12-09
 */
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
    private EnumPattern annotation;;
    @Override
    public void initialize(EnumPattern annotation) {
        ConstraintValidator.super.initialize(annotation);
        this.annotation = annotation;
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (StringUtils.isNotBlank(value)) {
            String fieldName = annotation.fieldName();
            for (Object e : annotation.type().getEnumConstants()) {
                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
                    return true;
                }
            }
        }
        return false;
    }
}
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
@@ -1,11 +1,11 @@
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.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
@@ -17,7 +17,10 @@
import org.springframework.util.ClassUtils;
import java.lang.reflect.Field;
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.stream.Collectors;
@@ -34,7 +37,7 @@
    /**
     * ç¼“存加密器
     */
    Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
    Map<Integer, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
    /**
     * ç±»åŠ å¯†å­—æ®µç¼“å­˜
@@ -55,10 +58,7 @@
     * èŽ·å–ç±»åŠ å¯†å­—æ®µç¼“å­˜
     */
    public Set<Field> getFieldCache(Class<?> sourceClazz) {
        if (ObjectUtil.isNotNull(fieldCache)) {
            return fieldCache.get(sourceClazz);
        }
        return null;
        return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz));
    }
    /**
@@ -67,11 +67,12 @@
     * @param encryptContext åŠ å¯†æ‰§è¡Œè€…éœ€è¦çš„ç›¸å…³é…ç½®å‚æ•°
     */
    public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
        if (encryptorMap.containsKey(encryptContext)) {
            return encryptorMap.get(encryptContext);
        int key = encryptContext.hashCode();
        if (encryptorMap.containsKey(key)) {
            return encryptorMap.get(key);
        }
        IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
        encryptorMap.put(encryptContext, encryptor);
        encryptorMap.put(key, encryptor);
        return encryptor;
    }
@@ -81,7 +82,7 @@
     * @param encryptContext åŠ å¯†æ‰§è¡Œè€…éœ€è¦çš„ç›¸å…³é…ç½®å‚æ•°
     */
    public void removeEncryptor(EncryptContext encryptContext) {
        this.encryptorMap.remove(encryptContext);
        this.encryptorMap.remove(encryptContext.hashCode());
    }
    /**
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -99,7 +99,7 @@
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
            return null;
        }
        return null;
    }
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
@@ -19,10 +19,12 @@
 * @author è€é©¬
 */
public class EncryptUtils {
    /**
     * å…¬é’¥
     */
    public static final String PUBLIC_KEY = "publicKey";
    /**
     * ç§é’¥
     */
@@ -51,7 +53,7 @@
    /**
     * AES加密
     *
     * @param data     å¾…解密数据
     * @param data     å¾…加密数据
     * @param password ç§˜é’¥å­—符串
     * @return åŠ å¯†åŽå­—ç¬¦ä¸², é‡‡ç”¨Base64编码
     */
@@ -70,7 +72,7 @@
    /**
     * AES加密
     *
     * @param data     å¾…解密数据
     * @param data     å¾…加密数据
     * @param password ç§˜é’¥å­—符串
     * @return åŠ å¯†åŽå­—ç¬¦ä¸², é‡‡ç”¨Hex编码
     */
@@ -208,7 +210,7 @@
    /**
     * sm2私钥解密
     *
     * @param data       å¾…加密数据
     * @param data       å¾…解密数据
     * @param privateKey ç§é’¥
     * @return è§£å¯†åŽå­—符串
     */
@@ -266,7 +268,7 @@
    /**
     * rsa私钥解密
     *
     * @param data       å¾…加密数据
     * @param data       å¾…解密数据
     * @param privateKey ç§é’¥
     * @return è§£å¯†åŽå­—符串
     */
ruoyi-common/ruoyi-common-excel/pom.xml
@@ -25,11 +25,6 @@
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
        </dependency>
        <dependency>
            <artifactId>commons-compress</artifactId>
            <groupId>org.apache.commons</groupId>
            <version>1.26.2</version>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package org.dromara.common.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * æ‰¹æ³¨
 * @author guzhouyanyu
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelNotation {
    /**
     * col index
     */
    int index() default -1;
    /**
     * æ‰¹æ³¨å†…容
     */
    String value() default "";
}
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package org.dromara.common.excel.annotation;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * æ˜¯å¦å¿…å¡«
 * @author guzhouyanyu
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRequired {
    /**
     * col index
     */
    int index() default -1;
    /**
     * å­—体颜色
     */
    IndexedColors fontColor() default IndexedColors.RED;
}
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
@@ -55,6 +55,7 @@
     * ä¸‹æ‹‰å¯é€‰é¡¹
     */
    private final List<DropDownOptions> dropDownOptions;
    private final DictService dictService;
    /**
     * å½“前单选进度
     */
@@ -63,7 +64,6 @@
     * å½“前联动选择进度
     */
    private int currentLinkedOptionsSheetIndex;
    private final DictService dictService;
    public ExcelDownHandler(List<DropDownOptions> options) {
        this.dropDownOptions = options;
@@ -139,8 +139,8 @@
            } else if (everyOptions.getOptions().size() > 10) {
                // å½“一级选项参数个数大于10,使用额外表的形式
                dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
            } else if (everyOptions.getOptions().size() != 0) {
                // å½“一级选项个数不为空,使用默认形式
            } else {
                // å¦åˆ™ä½¿ç”¨é»˜è®¤å½¢å¼
                dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
            }
        });
@@ -171,9 +171,23 @@
        Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
        // å°†ä¸‹æ‹‰è¡¨éšè—
        workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
        // å®Œå–„横向的一级选项数据表
        // é€‰é¡¹æ•°æ®
        List<String> firstOptions = options.getOptions();
        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
        // é‡‡ç”¨æŒ‰è¡Œå¡«å……数据的方式,避免EasyExcel出现数据无法写入的问题
        // Attempting to write a row in the range that is already written to disk
        // ä½¿ç”¨ArrayList记载数据,防止乱序
        List<String> columnNames = new ArrayList<>();
        // å†™å…¥ç¬¬ä¸€è¡Œï¼Œå³ç¬¬ä¸€çº§çš„æ•°æ®
        Row firstRow = linkedOptionsDataSheet.createRow(0);
        for (int columnIndex = 0; columnIndex < firstOptions.size(); columnIndex++) {
            String columnName = firstOptions.get(columnIndex);
            firstRow.createCell(columnIndex)
                .setCellValue(columnName);
            columnNames.add(columnName);
        }
        // åˆ›å»ºåç§°ç®¡ç†å™¨
        Name name = workbook.createName();
@@ -190,28 +204,12 @@
        // è®¾ç½®æ•°æ®æ ¡éªŒä¸ºåºåˆ—模式,引用的是名称管理器中的别名
        this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
        for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
            // å…ˆæå–主表中一级下拉的列名
        // åˆ›å»ºäºŒçº§é€‰é¡¹çš„名称管理器
        for (int columIndex = 0; columIndex < columnNames.size(); columIndex++) {
            // åˆ—名
            String firstOptionsColumnName = getExcelColumnName(columIndex);
            // ä¸€æ¬¡å¾ªçŽ¯æ˜¯æ¯ä¸€ä¸ªä¸€çº§é€‰é¡¹
            int finalI = columIndex;
            // æœ¬æ¬¡å¾ªçŽ¯çš„ä¸€çº§é€‰é¡¹å€¼
            String thisFirstOptionsValue = firstOptions.get(columIndex);
            // åˆ›å»ºç¬¬ä¸€è¡Œçš„æ•°æ®
            Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
                // å¦‚果不存在则创建第一行
                .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
                // ç¬¬ä¸€è¡Œå½“前列
                .createCell(columIndex)
                // è®¾ç½®å€¼ä¸ºå½“前一级选项值
                .setCellValue(thisFirstOptionsValue);
            // ç¬¬äºŒè¡Œå¼€å§‹ï¼Œè®¾ç½®ç¬¬äºŒçº§åˆ«é€‰é¡¹å‚æ•°
            List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
            if (CollUtil.isEmpty(secondOptions)) {
                // å¿…须保证至少有一个关联选项,否则将导致Excel解析错误
                secondOptions = Collections.singletonList("暂无_0");
            }
            // å¯¹åº”的一级值
            String thisFirstOptionsValue = columnNames.get(columIndex);
            // ä»¥è¯¥ä¸€çº§é€‰é¡¹å€¼åˆ›å»ºå­åç§°ç®¡ç†å™¨
            Name sonName = workbook.createName();
@@ -222,7 +220,9 @@
                linkedOptionsSheetName,
                firstOptionsColumnName,
                firstOptionsColumnName,
                secondOptions.size() + 1
                // äºŒçº§é€‰é¡¹å­˜åœ¨åˆ™è®¾ç½®ä¸º(选项个数+1)行,否则设置为2行
                Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
                    .orElseGet(ArrayList::new).size(), 1) + 1
            );
            // è®¾ç½®åç§°ç®¡ç†å™¨çš„引用位置
            sonName.setRefersToFormula(sonFunction);
@@ -235,25 +235,51 @@
                // äºŒçº§åªèƒ½ä¸»è¡¨æ¯ä¸€è¡Œçš„æ¯ä¸€åˆ—添加二级校验
                markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
            }
        }
            for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
                // ä»Žç¬¬äºŒè¡Œå¼€å§‹å¡«å……二级选项
                int finalRowIndex = rowIndex + 1;
                int finalColumIndex = columIndex;
                Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
                    // æ²¡æœ‰åˆ™åˆ›å»º
                    .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
                Optional
                    // åœ¨æœ¬çº§ä¸€çº§é€‰é¡¹æ‰€åœ¨çš„列
                    .ofNullable(row.getCell(finalColumIndex))
                    // ä¸å­˜åœ¨åˆ™åˆ›å»º
                    .orElseGet(() -> row.createCell(finalColumIndex))
                    // è®¾ç½®äºŒçº§é€‰é¡¹å€¼
                    .setCellValue(secondOptions.get(rowIndex));
        // å°†äºŒçº§æ•°æ®å¤„理为按行区分
        Map<Integer, List<String>> columnValueMap = new HashMap<>();
        int currentRow = 1;
        while (currentRow >= 0) {
            boolean flag = false;
            List<String> rowData = new ArrayList<>();
            for (String columnName : columnNames) {
                List<String> data = secoundOptionsMap.get(columnName);
                if (CollUtil.isEmpty(data)) {
                    // æ·»åŠ ç©ºå­—ç¬¦ä¸²å¡«å……ä½ç½®
                    rowData.add(" ");
                    continue;
                }
                // å–第一个
                String str = data.get(0);
                rowData.add(str);
                // é€šè¿‡ç§»é™¤çš„æ–¹å¼é¿å…é‡å¤
                data.remove(0);
                // è®¾ç½®å¯ä»¥ç»§ç»­
                flag = true;
            }
            columnValueMap.put(currentRow, rowData);
            // å¯ä»¥ç»§ç»­ï¼Œåˆ™å¢žåŠ è¡Œæ•°ï¼Œå¦åˆ™ç½®ä¸ºè´Ÿæ•°è·³å‡ºå¾ªçŽ¯
            if (flag) {
                currentRow++;
            } else {
                currentRow = -1;
            }
        }
        // å¡«å……第二级选项数据
        columnValueMap.forEach((rowIndex, rowValues) -> {
            Row row = linkedOptionsDataSheet.createRow(rowIndex);
            for (int columnIndex = 0; columnIndex < rowValues.size(); columnIndex++) {
                String rowValue = rowValues.get(columnIndex);
                // å¡«å……位置的部分不渲染
                if (StrUtil.isNotBlank(rowValue)) {
                    row.createCell(columnIndex)
                        .setCellValue(rowValue);
                }
            }
        });
        currentLinkedOptionsSheetIndex++;
    }
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
package org.dromara.common.excel.handler;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
 * æ‰¹æ³¨ã€å¿…å¡«
 *
 * @author guzhouyanyu
 */
public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
    /**
     * æ‰¹æ³¨
     */
    private final Map<Integer, String> notationMap;
    /**
     * å¤´åˆ—字体颜色
     */
    private final Map<Integer, Short> headColumnMap;
    public DataWriteHandler(Class<?> clazz) {
        notationMap = getNotationMap(clazz);
        headColumnMap = getRequiredMap(clazz);
    }
    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
            return;
        }
        WriteCellData<?> cellData = context.getFirstCellData();
        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
        DataFormatData dataFormatData = new DataFormatData();
        // å•元格设置为文本格式
        dataFormatData.setIndex((short) 49);
        writeCellStyle.setDataFormatData(dataFormatData);
        if (context.getHead()) {
            Cell cell = context.getCell();
            WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
            Sheet sheet = writeSheetHolder.getSheet();
            Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
            Drawing<?> drawing = sheet.createDrawingPatriarch();
            // è®¾ç½®æ ‡é¢˜å­—体样式
            WriteFont headWriteFont = new WriteFont();
            // åŠ ç²—
            headWriteFont.setBold(true);
            if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
                // è®¾ç½®å­—体颜色
                headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
            }
            writeCellStyle.setWriteFont(headWriteFont);
            CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
            cell.setCellStyle(cellStyle);
            if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
                // æ‰¹æ³¨å†…容
                String notationContext = notationMap.get(cell.getColumnIndex());
                // åˆ›å»ºç»˜å›¾å¯¹è±¡
                Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
                comment.setString(new XSSFRichTextString(notationContext));
                cell.setCellComment(comment);
            }
        }
    }
    /**
     * èŽ·å–å¿…å¡«åˆ—
     */
    private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
        Map<Integer, Short> requiredMap = new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        // æ£€æŸ¥ fields æ•°ç»„是否为空
        if (fields.length == 0) {
            return requiredMap;
        }
        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
        for (int i = 0; i < filteredFields.length; i++) {
            Field field = filteredFields[i];
            if (!field.isAnnotationPresent(ExcelRequired.class)) {
                continue;
            }
            ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
            int columnIndex =  excelRequired.index() == -1 ? i : excelRequired.index();
            requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
        }
        return requiredMap;
    }
    /**
     * èŽ·å–æ‰¹æ³¨
     */
    private static Map<Integer, String> getNotationMap(Class<?> clazz) {
        Map<Integer, String> notationMap = new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        // æ£€æŸ¥ fields æ•°ç»„是否为空
        if (fields.length == 0) {
            return notationMap;
        }
        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
        for (int i = 0; i < filteredFields.length; i++) {
            Field field = filteredFields[i];
            if (!field.isAnnotationPresent(ExcelNotation.class)) {
                continue;
            }
            ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
            int columnIndex =  excelNotation.index() == -1 ? i : excelNotation.index();
            notationMap.put(columnIndex, excelNotation.value());
        }
        return notationMap;
    }
}
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
@@ -18,6 +18,7 @@
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.*;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.IOException;
import java.io.InputStream;
@@ -191,6 +192,7 @@
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            // å¤§æ•°å€¼è‡ªåŠ¨è½¬æ¢ é˜²æ­¢å¤±çœŸ
            .registerConverter(new ExcelBigNumberConvert())
            .registerWriteHandler(new DataWriteHandler(clazz))
            .sheet(sheetName);
        if (merge) {
            // åˆå¹¶å¤„理器
@@ -211,7 +213,7 @@
     * @param data         æ¨¡æ¿éœ€è¦çš„æ•°æ®
     * @param response     å“åº”体
     */
    public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
    public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
        try {
            resetResponse(filename, response);
            ServletOutputStream os = response.getOutputStream();
@@ -230,20 +232,21 @@
     * @param data         æ¨¡æ¿éœ€è¦çš„æ•°æ®
     * @param os           è¾“出流
     */
    public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
    public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
            .autoCloseStream(false)
            // å¤§æ•°å€¼è‡ªåŠ¨è½¬æ¢ é˜²æ­¢å¤±çœŸ
            .registerConverter(new ExcelBigNumberConvert())
            .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
            .build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        // å•表多数据导出 æ¨¡æ¿æ ¼å¼ä¸º {.属性}
        for (Object d : data) {
        for (T d : data) {
            excelWriter.fill(d, writeSheet);
        }
        excelWriter.finish();
@@ -299,6 +302,9 @@
     * @param os           è¾“出流
     */
    public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
@@ -307,9 +313,6 @@
            .registerConverter(new ExcelBigNumberConvert())
            .build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        for (Map.Entry<String, Object> map : data.entrySet()) {
            // è®¾ç½®åˆ—表后续还有数据
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@@ -333,6 +336,9 @@
     * @param os           è¾“出流
     */
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
@@ -340,9 +346,6 @@
            // å¤§æ•°å€¼è‡ªåŠ¨è½¬æ¢ é˜²æ­¢å¤±çœŸ
            .registerConverter(new ExcelBigNumberConvert())
            .build();
        if (CollUtil.isEmpty(data)) {
            throw new IllegalArgumentException("数据为空");
        }
        for (int i = 0; i < data.size(); i++) {
            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java
@@ -32,7 +32,7 @@
    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // è¶…出范围 åºåˆ—化位字符串
        // è¶…出范围 åºåˆ—化为字符串
        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
            super.serialize(value, gen, provider);
        } else {
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
@@ -100,7 +100,7 @@
            if (e != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800));
            }
            // è®¾ç½®æ–¹æ³•名称
            String className = joinPoint.getTarget().getClass().getName();
@@ -113,13 +113,12 @@
            // è®¾ç½®æ¶ˆè€—æ—¶é—´
            StopWatch stopWatch = KEY_CACHE.get();
            stopWatch.stop();
            operLog.setCostTime(stopWatch.getTime());
            operLog.setCostTime(stopWatch.getDuration().toMillis());
            // å‘布事件保存数据库
            SpringUtils.context().publishEvent(operLog);
        } catch (Exception exp) {
            // è®°å½•本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            KEY_CACHE.remove();
        }
@@ -146,7 +145,7 @@
        }
        // æ˜¯å¦éœ€è¦ä¿å­˜response,参数和值
        if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800));
        }
    }
@@ -159,14 +158,13 @@
    private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
        Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        String requestMethod = operLog.getRequestMethod();
        if (MapUtil.isEmpty(paramsMap)
                && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
        if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
            operLog.setOperParam(StringUtils.substring(params, 0, 3800));
        } else {
            MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
            MapUtil.removeAny(paramsMap, excludeParamNames);
            operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
            operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
        }
    }
ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java
@@ -43,7 +43,13 @@
    private String pass;
    /**
     * å‘送方,遵循RFC-822标准
     * å‘送方,遵循RFC-822标准<br>
     * å‘件人可以是以下形式:
     *
     * <pre>
     * 1. user@xxx.xx
     * 2.  name &lt;user@xxx.xx&gt;
     * </pre>
     */
    private String from;
ruoyi-common/ruoyi-common-mybatis/pom.xml
@@ -37,6 +37,11 @@
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser</artifactId>
        </dependency>
        <!-- sql性能分析插件 -->
        <dependency>
            <groupId>p6spy</groupId>
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
 * æ•°æ®æƒé™å¤„理
 *
 * @author Lion Li
 */
@Slf4j
@Aspect
public class DataPermissionAspect {
    /**
     * å¤„理请求前执行
     */
    @Before(value = "@annotation(dataPermission)")
    public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
        DataPermissionHelper.setPermission(dataPermission);
    }
    /**
     * å¤„理完请求后执行
     *
     * @param joinPoint åˆ‡ç‚¹
     */
    @AfterReturning(pointcut = "@annotation(dataPermission)")
    public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
        DataPermissionHelper.removePermission();
    }
    /**
     * æ‹¦æˆªå¼‚常操作
     *
     * @param joinPoint åˆ‡ç‚¹
     * @param e         å¼‚常
     */
    @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
        DataPermissionHelper.removePermission();
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java
@@ -2,6 +2,7 @@
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -10,8 +11,10 @@
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
@@ -55,6 +58,14 @@
    }
    /**
     * æ•°æ®æƒé™åˆ‡é¢å¤„理器
     */
    @Bean
    public DataPermissionAspect dataPermissionAspect() {
        return new DataPermissionAspect();
    }
    /**
     * åˆ†é¡µæ’件,自动识别数据库类型
     */
    public PaginationInnerInterceptor paginationInnerInterceptor() {
@@ -97,6 +108,14 @@
    }
    /**
     * åˆå§‹åŒ–表对象处理器
     */
    @Bean
    public PostInitTableInfoHandler postInitTableInfoHandler() {
        return new PlusPostInitTableInfoHandler();
    }
    /**
     * PaginationInnerInterceptor åˆ†é¡µæ’件,自动识别数据库类型
     * https://baomidou.com/pages/97710a/
     * OptimisticLockerInnerInterceptor ä¹è§‚锁插件
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -18,9 +18,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * è‡ªå®šä¹‰ Mapper æŽ¥å£, å®žçް è‡ªå®šä¹‰æ‰©å±•
@@ -69,9 +67,7 @@
     * @return æ’入操作是否成功的布尔值
     */
    default boolean insertBatch(Collection<T> entityList) {
        Db.saveBatch(entityList);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.saveBatch(entityList);
    }
    /**
@@ -81,9 +77,7 @@
     * @return æ›´æ–°æ“ä½œæ˜¯å¦æˆåŠŸçš„å¸ƒå°”å€¼
     */
    default boolean updateBatchById(Collection<T> entityList) {
        Db.updateBatchById(entityList);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.updateBatchById(entityList);
    }
    /**
@@ -93,9 +87,7 @@
     * @return æ’入或更新操作是否成功的布尔值
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList) {
        Db.saveOrUpdateBatch(entityList);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.saveOrUpdateBatch(entityList);
    }
    /**
@@ -106,9 +98,7 @@
     * @return æ’入操作是否成功的布尔值
     */
    default boolean insertBatch(Collection<T> entityList, int batchSize) {
        Db.saveBatch(entityList, batchSize);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.saveBatch(entityList, batchSize);
    }
    /**
@@ -119,9 +109,7 @@
     * @return æ›´æ–°æ“ä½œæ˜¯å¦æˆåŠŸçš„å¸ƒå°”å€¼
     */
    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
        Db.updateBatchById(entityList, batchSize);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.updateBatchById(entityList, batchSize);
    }
    /**
@@ -132,9 +120,7 @@
     * @return æ’入或更新操作是否成功的布尔值
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
        Db.saveOrUpdateBatch(entityList, batchSize);
        // ä¸´æ—¶è§£å†³ æ–°ç‰ˆæœ¬ mp æ’入状态判断错误问题
        return true;
        return Db.saveOrUpdateBatch(entityList, batchSize);
    }
    /**
@@ -169,8 +155,8 @@
     * @param idList ä¸»é”®ID集合
     * @return æŸ¥è¯¢åˆ°çš„VO对象列表
     */
    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
        return selectVoBatchIds(idList, this.currentVoClass());
    default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
        return selectVoByIds(idList, this.currentVoClass());
    }
    /**
@@ -181,8 +167,8 @@
     * @param <C>     VO类的类型
     * @return æŸ¥è¯¢åˆ°çš„VO对象列表,经过转换为指定的VO类后返回
     */
    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
        List<T> list = this.selectBatchIds(idList);
    default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
        List<T> list = this.selectByIds(idList);
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -4,6 +4,7 @@
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
@@ -113,8 +114,14 @@
        return list;
    }
    @JsonIgnore
    public Integer getFirstNum() {
        return (pageNum - 1) * pageSize;
    }
    public PageQuery(Integer pageSize, Integer pageNum) {
        this.pageSize = pageSize;
        this.pageNum = pageNum;
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
@@ -50,6 +50,8 @@
    public TableDataInfo(List<T> list, long total) {
        this.rows = list;
        this.total = total;
        this.code = HttpStatus.HTTP_OK;
        this.msg = "查询成功";
    }
    /**
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -48,7 +48,12 @@
    /**
     * ä»…本人数据权限
     */
    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
    /**
     * éƒ¨é—¨åŠä»¥ä¸‹æˆ–本人数据权限
     */
    DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
    private final String code;
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -7,6 +7,7 @@
import org.apache.ibatis.reflection.MetaObject;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -31,8 +32,7 @@
        try {
            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
                // èŽ·å–å½“å‰æ—¶é—´ä½œä¸ºåˆ›å»ºæ—¶é—´å’Œæ›´æ–°æ—¶é—´ï¼Œå¦‚æžœåˆ›å»ºæ—¶é—´ä¸ä¸ºç©ºï¼Œåˆ™ä½¿ç”¨åˆ›å»ºæ—¶é—´ï¼Œå¦åˆ™ä½¿ç”¨å½“å‰æ—¶é—´
                Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
                    ? baseEntity.getCreateTime() : new Date();
                Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date());
                baseEntity.setCreateTime(current);
                baseEntity.setUpdateTime(current);
@@ -44,8 +44,7 @@
                        // å¡«å……创建人、更新人和创建部门信息
                        baseEntity.setCreateBy(userId);
                        baseEntity.setUpdateBy(userId);
                        baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
                            ? baseEntity.getCreateDept() : loginUser.getDeptId());
                        baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
                    }
                }
            } else {
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -3,11 +3,12 @@
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO;
@@ -28,19 +29,13 @@
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.*;
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.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -54,7 +49,7 @@
public class PlusDataPermissionHandler {
    /**
     * æ–¹æ³•或类(名称) ä¸Ž æ³¨è§£çš„æ˜ å°„关系缓存
     * ç±»åç§°ä¸Žæ³¨è§£çš„æ˜ å°„关系缓存(由于aop无法拦截mybatis接口类上的注解 åªèƒ½é€šè¿‡å¯åŠ¨é¢„æ‰«æçš„æ–¹å¼è¿›è¡Œ)
     */
    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
@@ -86,27 +81,27 @@
     * @return æ•°æ®è¿‡æ»¤æ¡ä»¶çš„ SQL ç‰‡æ®µ
     */
    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
        // èŽ·å–æ•°æ®æƒé™é…ç½®
        DataPermission dataPermission = getDataPermission(mappedStatementId);
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
        LoginUser currentUser = DataPermissionHelper.getVariable("user");
        if (ObjectUtil.isNull(currentUser)) {
            currentUser = LoginHelper.getLoginUser();
            DataPermissionHelper.setVariable("user", currentUser);
        }
        // å¦‚果是超级管理员或租户管理员,则不过滤数据
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
            return where;
        }
        // æž„造数据过滤条件的 SQL ç‰‡æ®µ
        String dataFilterSql = buildDataFilter(dataPermission, isSelect);
        if (StringUtils.isBlank(dataFilterSql)) {
            return where;
        }
        try {
            // èŽ·å–æ•°æ®æƒé™é…ç½®
            DataPermission dataPermission = getDataPermission(mappedStatementId);
            // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
            LoginUser currentUser = DataPermissionHelper.getVariable("user");
            if (ObjectUtil.isNull(currentUser)) {
                currentUser = LoginHelper.getLoginUser();
                DataPermissionHelper.setVariable("user", currentUser);
            }
            // å¦‚果是超级管理员或租户管理员,则不过滤数据
            if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
                return where;
            }
            // æž„造数据过滤条件的 SQL ç‰‡æ®µ
            String dataFilterSql = buildDataFilter(dataPermission, isSelect);
            if (StringUtils.isBlank(dataFilterSql)) {
                return where;
            }
            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
            // æ•°æ®æƒé™ä½¿ç”¨å•独的括号 é˜²æ­¢ä¸Žå…¶ä»–条件冲突
            Parenthesis parenthesis = new Parenthesis(expression);
            ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
            if (ObjectUtil.isNotNull(where)) {
                return new AndExpression(where, parenthesis);
            } else {
@@ -114,6 +109,8 @@
            }
        } catch (JSQLParserException e) {
            throw new ServiceException("数据权限解析异常 => " + e.getMessage());
        } finally {
            DataPermissionHelper.removePermission();
        }
    }
@@ -132,10 +129,33 @@
            joinStr = " " + dataPermission.joinStr() + " ";
        }
        LoginUser user = DataPermissionHelper.getVariable("user");
        StandardEvaluationContext context = new StandardEvaluationContext();
        Object defaultValue = "-1";
        NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
        context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
        context.setBeanResolver(beanResolver);
        DataPermissionHelper.getContext().forEach(context::setVariable);
        Set<String> conditions = new HashSet<>();
        // ä¼˜å…ˆè®¾ç½®å˜é‡
        List<String> keys = new ArrayList<>();
        Map<DataColumn, Boolean> ignoreMap = new HashMap<>();
        for (DataColumn dataColumn : dataPermission.value()) {
            if (dataColumn.key().length != dataColumn.value().length) {
                throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
            }
            // åŒ…含权限标识符 è¿™ç›´æŽ¥è·³è¿‡
            if (StringUtils.isNotBlank(dataColumn.permission()) &&
                CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
            ) {
                ignoreMap.put(dataColumn, Boolean.TRUE);
                continue;
            }
            // è®¾ç½®æ³¨è§£å˜é‡ key ä¸ºè¡¨è¾¾å¼å˜é‡ value ä¸ºå˜é‡å€¼
            for (int i = 0; i < dataColumn.key().length; i++) {
                context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
            }
            keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
        }
        for (RoleDTO role : user.getRoles()) {
            user.setRoleId(role.getRoleId());
            // èŽ·å–è§’è‰²æƒé™æ³›åž‹
@@ -145,33 +165,30 @@
            }
            // å…¨éƒ¨æ•°æ®æƒé™ç›´æŽ¥è¿”回
            if (type == DataScopeType.ALL) {
                return "";
                return StringUtils.EMPTY;
            }
            boolean isSuccess = false;
            for (DataColumn dataColumn : dataPermission.value()) {
                if (dataColumn.key().length != dataColumn.value().length) {
                    throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
                }
                // ä¸åŒ…含 key å˜é‡ åˆ™ä¸å¤„理
                if (!StringUtils.containsAny(type.getSqlTemplate(),
                    Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
                )) {
                    continue;
                }
                // åŒ…含权限标识符 è¿™ç›´æŽ¥è·³è¿‡
                if (StringUtils.isNotBlank(dataColumn.permission()) &&
                    CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
                ) {
                if (ignoreMap.containsKey(dataColumn)) {
                    // ä¿®å¤å¤šè§’色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
                    conditions.add(joinStr + " 1 = 1 ");
                    isSuccess = true;
                    continue;
                }
                // è®¾ç½®æ³¨è§£å˜é‡ key ä¸ºè¡¨è¾¾å¼å˜é‡ value ä¸ºå˜é‡å€¼
                for (int i = 0; i < dataColumn.key().length; i++) {
                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
                // ä¸åŒ…含 key å˜é‡ åˆ™ä¸å¤„理
                if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
                    continue;
                }
                // å½“前注解不满足模板 ä¸å¤„理
                if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
                    continue;
                }
                // å¿½ç•¥æ•°æ®æƒé™ é˜²æ­¢spel表达式内有其他sql查询导致死循环调用
                String sql = DataPermissionHelper.ignore(() ->
                    parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
                );
                // è§£æžsql模板并填充
                String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
                conditions.add(joinStr + sql);
                isSuccess = true;
            }
@@ -185,7 +202,7 @@
            String sql = StreamUtils.join(conditions, Function.identity(), "");
            return sql.substring(joinStr.length());
        }
        return "";
        return StringUtils.EMPTY;
    }
    /**
@@ -212,34 +229,14 @@
                    // èŽ·å–èµ„æºå¯¹åº”çš„ç±»å¯¹è±¡
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    // æŸ¥æ‰¾ç±»ä¸­çš„特定注解
                    findAnnotation(clazz);
                    if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
                        DataPermission dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
                        dataPermissionCacheMap.put(clazz.getName(), dataPermission);
                    }
                }
            }
        } catch (Exception e) {
            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
        }
    }
    /**
     * åœ¨æŒ‡å®šçš„类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap ä¸­
     *
     * @param clazz è¦æŸ¥æ‰¾çš„ç±»
     */
    private void findAnnotation(Class<?> clazz) {
        DataPermission dataPermission;
        for (Method method : clazz.getMethods()) {
            if (method.isDefault() || method.isVarArgs()) {
                continue;
            }
            String mappedStatementId = clazz.getName() + "." + method.getName();
            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
            }
        }
        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
        }
    }
@@ -250,9 +247,9 @@
     * @return DataPermission æ³¨è§£å¯¹è±¡ï¼Œå¦‚果不存在则返回 null
     */
    public DataPermission getDataPermission(String mapperId) {
        // æ£€æŸ¥ç¼“存中是否包含映射语句 ID å¯¹åº”çš„ DataPermission æ³¨è§£å¯¹è±¡
        if (dataPermissionCacheMap.containsKey(mapperId)) {
            return dataPermissionCacheMap.get(mapperId);
        // æ£€æŸ¥ä¸Šä¸‹æ–‡ä¸­æ˜¯å¦åŒ…含映射语句 ID å¯¹åº”çš„ DataPermission æ³¨è§£å¯¹è±¡
        if (DataPermissionHelper.getPermission() != null) {
            return DataPermissionHelper.getPermission();
        }
        // å¦‚果缓存中不包含映射语句 ID å¯¹åº”çš„ DataPermission æ³¨è§£å¯¹è±¡ï¼Œåˆ™å°è¯•使用类名作为键查找
        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
@@ -271,4 +268,65 @@
    public boolean invalid(String mapperId) {
        return getDataPermission(mapperId) == null;
    }
    /**
     * å¯¹æ‰€æœ‰null变量找不到的变量返回默认值
     */
    @AllArgsConstructor
    private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
        private final Object defaultValue;
        @Override
        public Object lookupVariable(String name) {
            Object obj = super.lookupVariable(name);
            // å¦‚果读取到的值是 null,则返回默认值
            if (obj == null) {
                return defaultValue;
            }
            return obj;
        }
    }
    /**
     * å¯¹æ‰€æœ‰null变量找不到的变量返回默认值 å§”托模式 å°†ä¸éœ€è¦å¤„理的方法委托给原处理器
     */
    @AllArgsConstructor
    private static class NullSafePropertyAccessor implements PropertyAccessor {
        private final PropertyAccessor delegate;
        private final Object defaultValue;
        @Override
        public Class<?>[] getSpecificTargetClasses() {
            return delegate.getSpecificTargetClasses();
        }
        @Override
        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
            return delegate.canRead(context, target, name);
        }
        @Override
        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
            TypedValue value = delegate.read(context, target, name);
            // å¦‚果读取到的值是 null,则返回默认值
            if (value.getValue() == null) {
                return new TypedValue(defaultValue);
            }
            return value;
        }
        @Override
        public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
            return delegate.canWrite(context, target, name);
        }
        @Override
        public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
            delegate.write(context, target, name, newValue);
        }
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.session.Configuration;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
 * ä¿®æ”¹è¡¨ä¿¡æ¯åˆå§‹åŒ–方式
 * ç›®å‰ç”¨äºŽå…¨å±€ä¿®æ”¹æ˜¯å¦ä½¿ç”¨é€»è¾‘删除
 *
 * @author Lion Li
 */
public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
    @Override
    public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
        String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
        // åªæœ‰å…³é—­æ—¶ ç»Ÿä¸€è®¾ç½®false ä¸ºtrue时mp自动判断不处理
        if (!Convert.toBool(flag)) {
            ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
        }
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
@@ -62,8 +62,8 @@
            // charindex(',100,' , ',0,100,101,') <> 0
            return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
        } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
            // (select position(',100,' in ',0,100,101,')) <> 0
            return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2);
            // (select strpos(',0,100,101,' , ',100,')) <> 0
            return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
        } else if (dataBasyType == DataBaseType.ORACLE) {
            // instr(',0,100,101,' , ',100,') <> 0
            return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -9,6 +9,7 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.annotation.DataPermission;
import java.util.HashMap;
import java.util.Map;
@@ -29,6 +30,33 @@
    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
    private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
    /**
     * èŽ·å–å½“å‰æ‰§è¡Œmapper权限注解
     *
     * @return è¿”回当前执行mapper权限注解
     */
    public static DataPermission getPermission() {
        return PERMISSION_CACHE.get();
    }
    /**
     * è®¾ç½®å½“前执行mapper权限注解
     *
     * @param dataPermission   æ•°æ®æƒé™æ³¨è§£
     */
    public static void setPermission(DataPermission dataPermission) {
        PERMISSION_CACHE.set(dataPermission);
    }
    /**
     * åˆ é™¤å½“前执行mapper权限注解
     */
    public static void removePermission() {
        PERMISSION_CACHE.remove();
    }
    /**
     * ä»Žä¸Šä¸‹æ–‡ä¸­èŽ·å–æŒ‡å®šé”®çš„å˜é‡å€¼ï¼Œå¹¶å°†å…¶è½¬æ¢ä¸ºæŒ‡å®šçš„ç±»åž‹
     *
ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
@@ -24,8 +24,8 @@
      # ä¸»é”®ç±»åž‹
      # AUTO è‡ªå¢ž NONE ç©º INPUT ç”¨æˆ·è¾“å…¥ ASSIGN_ID é›ªèб ASSIGN_UUID å”¯ä¸€ UUID
      idType: ASSIGN_ID
      # é€»è¾‘已删除值(框架表均使用此值 ç¦æ­¢éšæ„ä¿®æ”¹)
      logicDeleteValue: 2
      # é€»è¾‘已删除值(可按需求随意修改)
      logicDeleteValue: 1
      # é€»è¾‘未删除值
      logicNotDeleteValue: 0
      insertStrategy: NOT_NULL
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -9,21 +9,18 @@
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.crt.S3CrtHttpConfiguration;
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.*;
@@ -35,6 +32,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.function.Consumer;
/**
 * S3 å­˜å‚¨åè®® æ‰€æœ‰å…¼å®¹S3协议的云厂商均支持
@@ -83,10 +81,10 @@
            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
            //MinIO ä½¿ç”¨ HTTPS é™åˆ¶ä½¿ç”¨åŸŸåè®¿é—®ï¼Œç«™ç‚¹å¡«åŸŸåã€‚需要启用路径样式访问
            // MinIO ä½¿ç”¨ HTTPS é™åˆ¶ä½¿ç”¨åŸŸåè®¿é—®ï¼Œç«™ç‚¹å¡«åŸŸåã€‚需要启用路径样式访问
            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
            //创建AWS基于 CRT çš„ S3 å®¢æˆ·ç«¯
            // åˆ›å»ºAWS基于 CRT çš„ S3 å®¢æˆ·ç«¯
            this.client = S3AsyncClient.crtBuilder()
                .credentialsProvider(credentialsProvider)
                .endpointOverride(URI.create(getEndpoint()))
@@ -95,6 +93,9 @@
                .minimumPartSizeInBytes(10 * 1025 * 1024L)
                .checksumValidationEnabled(false)
                .forcePathStyle(isStyle)
                .httpConfiguration(S3CrtHttpConfiguration.builder()
                    .connectionTimeout(Duration.ofSeconds(60)) // è®¾ç½®è¿žæŽ¥è¶…æ—¶
                    .build())
                .build();
            //AWS基于 CRT çš„ S3 AsyncClient å®žä¾‹ç”¨ä½œ S3 ä¼ è¾“管理器的底层客户端
@@ -112,50 +113,11 @@
                .serviceConfiguration(config)
                .build();
            // åˆ›å»ºå­˜å‚¨æ¡¶
            createBucket();
        } catch (Exception e) {
            if (e instanceof OssException) {
                throw e;
            }
            throw new OssException("配置错误! è¯·æ£€æŸ¥ç³»ç»Ÿé…ç½®:[" + e.getMessage() + "]");
        }
    }
    /**
     * åŒæ­¥åˆ›å»ºå­˜å‚¨æ¡¶
     * å¦‚果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
     *
     * @throws OssException å½“创建存储桶时发生异常时抛出
     */
    public void createBucket() {
        String bucketName = properties.getBucketName();
        try {
            // å°è¯•获取存储桶的信息
            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() + "]");
            }
        }
    }
@@ -178,7 +140,9 @@
                            .key(key)
                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            // ç”¨äºŽè®¾ç½®å¯¹è±¡çš„访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                            // å› æ­¤æ ¹æ®å…·ä½“的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
                            //.acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .addTransferListener(LoggingTransferListener.create())
                    .source(filePath).build());
@@ -215,7 +179,10 @@
        }
        try {
            // åˆ›å»ºå¼‚步请求体(length如果为空会报错)
            BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
            BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
                .contentLength(length)
                .subscribeTimeout(Duration.ofSeconds(30))
                .build();
            // ä½¿ç”¨ transferManager è¿›è¡Œä¸Šä¼ 
            Upload upload = transferManager.upload(
@@ -224,7 +191,9 @@
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            // ç”¨äºŽè®¾ç½®å¯¹è±¡çš„访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
                            // å› æ­¤æ ¹æ®å…·ä½“的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
                            //.acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .build());
@@ -271,10 +240,11 @@
     *
     * @param key æ–‡ä»¶åœ¨ Amazon S3 ä¸­çš„对象键
     * @param out è¾“出流
     * @param consumer è‡ªå®šä¹‰å¤„理逻辑
     * @return è¾“出流中写入的字节数(长度)
     * @throws OssException å¦‚果下载失败,抛出自定义异常
     */
    public long download(String key, OutputStream out) {
    public void download(String key, OutputStream out, Consumer<Long> consumer) {
        try {
            // æž„建下载请求
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
@@ -290,7 +260,10 @@
            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
                if (consumer != null) {
                    consumer.accept(responseStream.response().contentLength());
                }
                responseStream.transferTo(out); // é˜»å¡žè°ƒç”¨çº¿ç¨‹ blocks the calling thread
            }
        } catch (Exception e) {
            throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
@@ -316,13 +289,13 @@
    /**
     * èŽ·å–ç§æœ‰URL链接
     *
     * @param objectKey å¯¹è±¡KEY
     * @param second    æŽˆæƒæ—¶é—´
     * @param objectKey   å¯¹è±¡KEY
     * @param expiredTime é“¾æŽ¥æŽˆæƒåˆ°æœŸæ—¶é—´
     */
    public String getPrivateUrl(String objectKey, Integer second) {
    public String getPrivateUrl(String objectKey, Duration expiredTime) {
        // ä½¿ç”¨ AWS S3 é¢„签名 URL çš„生成器 èŽ·å–å¯¹è±¡çš„é¢„ç­¾å URL
        URL url = presigner.presignGetObject(
                x -> x.signatureDuration(Duration.ofSeconds(second))
                x -> x.signatureDuration(expiredTime)
                    .getObjectRequest(
                        y -> y.bucket(properties.getBucketName())
                            .key(objectKey)
@@ -340,8 +313,8 @@
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix) {
        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix));
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType);
    }
    /**
@@ -353,8 +326,8 @@
     * @return UploadResult åŒ…含上传后的文件信息
     * @throws OssException å¦‚果上传失败,抛出自定义异常
     */
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix));
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType);
    }
    /**
@@ -517,79 +490,6 @@
     */
    public AccessPolicyType getAccessPolicy() {
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }
    /**
     * ç”Ÿæˆ AWS S3 å­˜å‚¨æ¡¶è®¿é—®ç­–ç•¥
     *
     * @param bucketName å­˜å‚¨æ¡¶
     * @param policyType æ¡¶ç­–略类型
     * @return ç¬¦åˆ AWS S3 å­˜å‚¨æ¡¶è®¿é—®ç­–略格式的字符串
     */
    private static String getPolicy(String bucketName, PolicyType policyType) {
        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/enumd/AccessPolicyType.java
@@ -17,17 +17,17 @@
    /**
     * private
     */
    PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
    PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
    /**
     * public
     */
    PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
    PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
    /**
     * custom
     */
    CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
    CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
    /**
     * æ¡¶ æƒé™ç±»åž‹ï¼ˆæ•°æ®åº“值)
@@ -43,11 +43,6 @@
     * æ–‡ä»¶å¯¹è±¡ æƒé™ç±»åž‹
     */
    private final ObjectCannedACL objectCannedACL;
    /**
     * æ¡¶ç­–略类型
     */
    private final PolicyType policyType;
    public static AccessPolicyType getByType(String type) {
        for (AccessPolicyType value : values()) {
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java
ÎļþÒÑɾ³ý
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java
@@ -38,4 +38,10 @@
     * æç¤ºæ¶ˆæ¯ æ”¯æŒå›½é™…化 æ ¼å¼ä¸º {code}
     */
    String message() default "{rate.limiter.message}";
    /**
     * é™æµç­–略超时时间 é»˜è®¤ä¸€å¤©(策略存活时间 ä¼šæ¸…除已存在的策略数据)
     */
    int timeout() default 86400;
}
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
@@ -54,13 +54,14 @@
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        int timeout = rateLimiter.timeout();
        try {
            String combineKey = getCombineKey(rateLimiter, point);
            RateType rateType = RateType.OVERALL;
            if (rateLimiter.limitType() == LimitType.CLUSTER) {
                rateType = RateType.PER_CLIENT;
            }
            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
            if (number == -1) {
                String message = rateLimiter.message();
                if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
ruoyi-common/ruoyi-common-redis/pom.xml
@@ -42,6 +42,18 @@
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
<!--        &lt;!&ndash; redis序列化替代方案 æ¯”json快无数的跨语言二进制序列化 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.apache.fury</groupId>-->
<!--            <artifactId>fury-core</artifactId>-->
<!--            <version>0.9.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.slf4j</groupId>-->
<!--            <artifactId>slf4j-api</artifactId>-->
<!--        </dependency>-->
    </dependencies>
</project>
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java
@@ -53,6 +53,9 @@
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // æŒ‡å®šåºåˆ—化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
            om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
//            LoggerFactory.useSlf4jLogging(true);
//            FuryCodec furyCodec = new FuryCodec();
//            CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
            TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
            // ç»„合序列化 key ä½¿ç”¨ String å†…容使用通用 json æ ¼å¼
            CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
@@ -44,6 +44,7 @@
    }
    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Class<T> type) {
        Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
        return (T) o;
@@ -55,6 +56,7 @@
        cache.put(key, value);
    }
    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        CAFFEINE.invalidate(getUniqueKey(key));
        return cache.putIfAbsent(key, value);
@@ -65,6 +67,7 @@
        evictIfPresent(key);
    }
    @Override
    public boolean evictIfPresent(Object key) {
        boolean b = cache.evictIfPresent(key);
        if (b) {
@@ -78,6 +81,7 @@
        cache.clear();
    }
    @Override
    public boolean invalidate() {
        return cache.invalidate();
    }
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java
@@ -1,35 +1,21 @@
package org.dromara.common.redis.utils;
import org.dromara.common.core.utils.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RMap;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Set;
/**
 * ç¼“存操作工具类 {@link }
 * ç¼“存操作工具类
 *
 * @author Michelle.Chung
 * @date 2022/8/13
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked"})
public class CacheUtils {
    private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
    /**
     * èŽ·å–ç¼“å­˜ç»„å†…æ‰€æœ‰çš„KEY
     *
     * @param cacheNames ç¼“存组名称
     */
    public static Set<Object> keys(String cacheNames) {
        RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
        return rmap.keySet();
    }
    /**
     * èŽ·å–ç¼“å­˜å€¼
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java
@@ -174,12 +174,12 @@
     *
     * @param queueName é˜Ÿåˆ—名
     * @param capacity  å®¹é‡
     * @param destroy   å·²å­˜åœ¨æ˜¯å¦é”€æ¯
     * @param destroy   æ˜¯å¦é”€æ¯
     */
    public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
        RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
        if (boundedBlockingQueue.isExists() && destroy) {
            destroyQueue(queueName);
        if (destroy) {
            boundedBlockingQueue.delete();
        }
        return boundedBlockingQueue.trySetCapacity(capacity);
    }
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
@@ -4,6 +4,7 @@
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*;
import org.redisson.api.options.KeysScanOptions;
import java.time.Duration;
import java.util.Collection;
@@ -36,8 +37,22 @@
     * @return -1 è¡¨ç¤ºå¤±è´¥
     */
    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
        return rateLimiter(key, rateType, rate, rateInterval, 0);
    }
    /**
     * é™æµ
     *
     * @param key          é™æµkey
     * @param rateType     é™æµç±»åž‹
     * @param rate         é€Ÿçއ
     * @param rateInterval é€ŸçŽ‡é—´éš”
     * @param timeout      è¶…æ—¶æ—¶é—´
     * @return -1 è¡¨ç¤ºå¤±è´¥
     */
    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) {
        RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
        rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout));
        if (rateLimiter.tryAcquire()) {
            return rateLimiter.availablePermits();
        } else {
@@ -517,18 +532,39 @@
    }
    /**
     * èŽ·å¾—ç¼“å­˜çš„åŸºæœ¬å¯¹è±¡åˆ—è¡¨
     *
     * èŽ·å¾—ç¼“å­˜çš„åŸºæœ¬å¯¹è±¡åˆ—è¡¨(全局匹配忽略租户 è‡ªè¡Œæ‹¼æŽ¥ç§Ÿæˆ·id)
     * <P>
     * limit-设置扫描的限制数量(默认为0,查询全部)
     * pattern-设置键的匹配模式(默认为null)
     * chunkSize-设置每次扫描的块大小(默认为0,本方法设置为1000)
     * type-设置键的类型(默认为null,查询全部类型)
     * </P>
     * @see KeysScanOptions
     * @param pattern å­—符串前缀
     * @return å¯¹è±¡åˆ—表
     */
    public static Collection<String> keys(final String pattern) {
        Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
        return stream.collect(Collectors.toList());
        return  keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000));
    }
    /**
     * åˆ é™¤ç¼“存的基本对象列表
     * é€šè¿‡æ‰«æå‚数获取缓存的基本对象列表
     * @param keysScanOptions æ‰«æå‚æ•°
     * <P>
     * limit-设置扫描的限制数量(默认为0,查询全部)
     * pattern-设置键的匹配模式(默认为null)
     * chunkSize-设置每次扫描的块大小(默认为0)
     * type-设置键的类型(默认为null,查询全部类型)
     * </P>
     * @see KeysScanOptions
     */
    public static Collection<String> keys(final KeysScanOptions keysScanOptions) {
        Stream<String> keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions);
        return keysStream.collect(Collectors.toList());
    }
    /**
     * åˆ é™¤ç¼“存的基本对象列表(全局匹配忽略租户 è‡ªè¡Œæ‹¼æŽ¥ç§Ÿæˆ·id)
     *
     * @param pattern å­—符串前缀
     */
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
package org.dromara.common.redis.utils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RIdGenerator;
import org.redisson.api.RedissonClient;
import java.time.Duration;
/**
 * å‘号器工具类
 *
 * @author ç§‹è¾žæœªå¯’
 * @date 2024-12-10
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SequenceUtils {
    /**
     * é»˜è®¤åˆå§‹å€¼
     */
    public static final Long DEFAULT_INIT_VALUE = 1L;
    /**
     * é»˜è®¤æ­¥é•¿
     */
    public static final Long DEFAULT_STEP_VALUE = 1L;
    /**
     * é»˜è®¤è¿‡æœŸæ—¶é—´-天
     */
    public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
    /**
     * é»˜è®¤è¿‡æœŸæ—¶é—´-分钟
     */
    public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
    /**
     * èŽ·å–Redisson客户端实例
     */
    private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
    /**
     * èŽ·å–ID生成器
     *
     * @param key        ä¸šåŠ¡key
     * @param expireTime è¿‡æœŸæ—¶é—´
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return ID生成器
     */
    private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
        if (initValue == null || initValue <= 0) {
            initValue = DEFAULT_INIT_VALUE;
        }
        if (stepValue == null || stepValue <= 0) {
            stepValue = DEFAULT_STEP_VALUE;
        }
        RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
        // è®¾ç½®åˆå§‹å€¼å’Œæ­¥é•¿
        idGenerator.tryInit(initValue, stepValue);
        // è®¾ç½®è¿‡æœŸæ—¶é—´
        idGenerator.expire(expireTime);
        return idGenerator;
    }
    /**
     * èŽ·å–æŒ‡å®šä¸šåŠ¡key的唯一id
     *
     * @param key        ä¸šåŠ¡key
     * @param expireTime è¿‡æœŸæ—¶é—´
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return å”¯ä¸€id
     */
    public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
        return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
    }
    /**
     * èŽ·å–æŒ‡å®šä¸šåŠ¡key的唯一id字符串
     *
     * @param key        ä¸šåŠ¡key
     * @param expireTime è¿‡æœŸæ—¶é—´
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return å”¯ä¸€id
     */
    public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
        return String.valueOf(nextId(key, expireTime, initValue, stepValue));
    }
    /**
     * èŽ·å–æŒ‡å®šä¸šåŠ¡key的唯一id (ID初始值=1,ID步长=1)
     *
     * @param key        ä¸šåŠ¡key
     * @param expireTime è¿‡æœŸæ—¶é—´
     * @return å”¯ä¸€id
     */
    public static long nextId(String key, Duration expireTime) {
        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
    }
    /**
     * èŽ·å–æŒ‡å®šä¸šåŠ¡key的唯一id字符串 (ID初始值=1,ID步长=1)
     *
     * @param key        ä¸šåŠ¡key
     * @param expireTime è¿‡æœŸæ—¶é—´
     * @return å”¯ä¸€id
     */
    public static String nextIdStr(String key, Duration expireTime) {
        return String.valueOf(nextId(key, expireTime));
    }
    /**
     * èŽ·å– yyyyMMdd å¼€å¤´çš„唯一id
     *
     * @return å”¯ä¸€id
     */
    public static String nextIdDate() {
        return nextIdDate("");
    }
    /**
     * èŽ·å– prefix + yyyyMMdd å¼€å¤´çš„唯一id
     *
     * @param prefix ä¸šåŠ¡å‰ç¼€
     * @return å”¯ä¸€id
     */
    public static String nextIdDate(String prefix) {
        // å‰ç¼€+日期 æž„建 prefixKey
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
        // èŽ·å–ä¸‹ä¸€ä¸ªid
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
        // è¿”回完整id
        return StringUtils.format("{}{}", prefixKey, nextId);
    }
    /**
     * èŽ·å– yyyyMMddHHmmss å¼€å¤´çš„唯一id
     *
     * @return å”¯ä¸€id
     */
    public static String nextIdDateTime() {
        return nextIdDateTime("");
    }
    /**
     * èŽ·å– prefix + yyyyMMddHHmmss å¼€å¤´çš„唯一id
     *
     * @param prefix ä¸šåŠ¡å‰ç¼€
     * @return å”¯ä¸€id
     */
    public static String nextIdDateTime(String prefix) {
        // å‰ç¼€+日期时间 æž„建 prefixKey
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
        // èŽ·å–ä¸‹ä¸€ä¸ªid
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
        // è¿”回完整id
        return StringUtils.format("{}{}", prefixKey, nextId);
    }
}
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
@@ -51,7 +51,11 @@
        if (timeout == NEVER_EXPIRE) {
            RedisUtils.setCacheObject(key, value);
        } else {
            RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
            if (RedisUtils.hasKey(key)) {
                RedisUtils.setCacheObject(key, value, true);
            } else {
                RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
            }
        }
        CAFFEINE.invalidate(key);
    }
@@ -114,7 +118,11 @@
        if (timeout == NEVER_EXPIRE) {
            RedisUtils.setCacheObject(key, object);
        } else {
            RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
            if (RedisUtils.hasKey(key)) {
                RedisUtils.setCacheObject(key, object, true);
            } else {
                RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
            }
        }
        CAFFEINE.invalidate(key);
    }
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -8,12 +8,13 @@
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.UserType;
import java.util.Set;
/**
 * ç™»å½•鉴权助手
@@ -89,6 +90,13 @@
    }
    /**
     * èŽ·å–ç”¨æˆ·id
     */
    public static String getUserIdStr() {
        return Convert.toStr(getExtra(USER_KEY));
    }
    /**
     * èŽ·å–ç”¨æˆ·è´¦æˆ·
     */
    public static String getUsername() {
@@ -152,7 +160,7 @@
     * @return ç»“æžœ
     */
    public static boolean isSuperAdmin(Long userId) {
        return UserConstants.SUPER_ADMIN_ID.equals(userId);
        return SystemConstants.SUPER_ADMIN_ID.equals(userId);
    }
    /**
ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
@@ -7,9 +7,11 @@
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -50,11 +52,20 @@
                    .match(allUrlHandler.getUrls())
                    // å¯¹æœªæŽ’除的路径进行检查
                    .check(() -> {
                        HttpServletRequest request = ServletUtils.getRequest();
                        // æ£€æŸ¥æ˜¯å¦ç™»å½• æ˜¯å¦æœ‰token
                        StpUtil.checkLogin();
                        try {
                            StpUtil.checkLogin();
                        } catch (NotLoginException e) {
                            if (request.getRequestURI().contains("sse")) {
                                throw new SseException(e.getMessage(), e.getCode());
                            } else {
                                throw e;
                            }
                        }
                        // æ£€æŸ¥ header ä¸Ž param é‡Œçš„ clientid ä¸Ž token é‡Œçš„æ˜¯å¦ä¸€è‡´
                        String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
                        String headerCid = request.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)) {
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java
@@ -22,7 +22,13 @@
public @interface Sensitive {
    SensitiveStrategy strategy();
    String roleKey() default "";
    /**
     * è§’色标识符 å¤šä¸ªè§’色满足一个即可
     */
    String[] roleKey() default {};
    String perms() default "";
    /**
     * æƒé™æ ‡è¯†ç¬¦ å¤šä¸ªæƒé™æ»¡è¶³ä¸€ä¸ªå³å¯
     */
    String[] perms() default {};
}
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java
@@ -13,6 +13,6 @@
    /**
     * æ˜¯å¦è„±æ•
     */
    boolean isSensitive(String roleKey, String perms);
    boolean isSensitive(String[] roleKey, String[] perms);
}
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
@@ -80,12 +80,12 @@
    FIRST_MASK(DesensitizedUtil::firstMask),
    /**
     * æ¸…空为null
     * æ¸…空为""
     */
    CLEAR(s -> DesensitizedUtil.clear()),
    /**
     * æ¸…空为""
     * æ¸…空为null
     */
    CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
@@ -26,8 +26,8 @@
public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
    private SensitiveStrategy strategy;
    private String roleKey;
    private String perms;
    private String[] roleKey;
    private String[] perms;
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java
@@ -66,7 +66,7 @@
     */
    @Override
    public void clean() {
        RedisUtils.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + "sms:");
        RedisUtils.deleteKeys(GlobalConstants.GLOBAL_REDIS_KEY + "sms:*");
    }
}
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
@@ -30,7 +30,7 @@
    }
    @Override
    protected AuthToken getAccessToken(AuthCallback authCallback) {
    public AuthToken getAccessToken(AuthCallback authCallback) {
        String body = doPostAuthorizationCode(authCallback.getCode());
        Dict object = JsonUtils.parseMap(body);
        // oauth/token éªŒè¯å¼‚常
@@ -51,7 +51,7 @@
    }
    @Override
    protected AuthUser getUserInfo(AuthToken authToken) {
    public AuthUser getUserInfo(AuthToken authToken) {
        String body = doGetUserInfo(authToken);
        Dict object = JsonUtils.parseMap(body);
        // oauth/token éªŒè¯å¼‚常
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java
@@ -1,7 +1,10 @@
package org.dromara.common.social.topiam;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
@@ -16,7 +19,7 @@
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;
import static org.dromara.common.social.topiam.AuthTopiamSource.TOPIAM;
import static org.dromara.common.social.topiam.AuthTopIamSource.TOPIAM;
/**
 * TopIAM è®¤è¯è¯·æ±‚
@@ -41,7 +44,7 @@
    }
    @Override
    protected AuthToken getAccessToken(AuthCallback authCallback) {
    public AuthToken getAccessToken(AuthCallback authCallback) {
        String body = doPostAuthorizationCode(authCallback.getCode());
        Dict object = JsonUtils.parseMap(body);
        checkResponse(object);
@@ -55,7 +58,7 @@
    }
    @Override
    protected AuthUser getUserInfo(AuthToken authToken) {
    public AuthUser getUserInfo(AuthToken authToken) {
        String body = doGetUserInfo(authToken);
        Dict object = JsonUtils.parseMap(body);
        checkResponse(object);
@@ -70,6 +73,16 @@
            .build();
    }
    @Override
    protected String doPostAuthorizationCode(String code) {
        HttpRequest request = HttpRequest.post(source.accessToken())
            .header("Authorization", "Basic " + Base64.encode("%s:%s".formatted(config.getClientId(), config.getClientSecret())))
            .form("grant_type", "authorization_code")
            .form("code", code)
            .form("redirect_uri", config.getRedirectUri());
        HttpResponse response = request.execute();
        return response.body();
    }
    @Override
    protected String doGetUserInfo(AuthToken authToken) {
@@ -86,7 +99,7 @@
            .build();
    }
    public static void checkResponse(Dict object) {
    private static void checkResponse(Dict object) {
        // oauth/token éªŒè¯å¼‚常
        if (object.containsKey("error")) {
            throw new AuthException(object.getStr("error_description"));
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java
ÎļþÃû´Ó ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopiamSource.java ÐÞ¸Ä
@@ -9,7 +9,7 @@
 * @author xlsea
 * @since 2024-01-06
 */
public enum AuthTopiamSource implements AuthSource {
public enum AuthTopIamSource implements AuthSource {
    /**
     * æµ‹è¯•
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -58,9 +58,9 @@
            case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
            case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
            case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
            case "stack_overflow" -> new AuthStackOverflowRequest(builder.build(), STATE_CACHE);
            case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE);
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.build(), STATE_CACHE);
            case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
            case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
            case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
            case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
            case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
@@ -1,14 +1,12 @@
package org.dromara.common.sse.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -67,13 +65,20 @@
     * @param token  ç”¨æˆ·çš„唯一令牌,用于识别具体的连接
     */
    public void disconnect(Long userId, String token) {
        if (userId == null || token == null) {
            return;
        }
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
        if (emitters != null) {
        if (MapUtil.isNotEmpty(emitters)) {
            try {
                emitters.get(token).send(SseEmitter.event().comment("disconnected"));
                SseEmitter sseEmitter = emitters.get(token);
                sseEmitter.send(SseEmitter.event().comment("disconnected"));
                sseEmitter.complete();
            } catch (Exception ignore) {
            }
            emitters.remove(token);
        } else {
            USER_TOKEN_EMITTERS.remove(userId);
        }
    }
@@ -94,7 +99,7 @@
     */
    public void sendMessage(Long userId, String message) {
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
        if (emitters != null) {
        if (MapUtil.isNotEmpty(emitters)) {
            for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
                try {
                    entry.getValue().send(SseEmitter.event()
@@ -104,6 +109,8 @@
                    emitters.remove(entry.getKey());
                }
            }
        } else {
            USER_TOKEN_EMITTERS.remove(userId);
        }
    }
@@ -124,25 +131,13 @@
     * @param sseMessageDto è¦å‘布的SSE消息对象
     */
    public void publishMessage(SseMessageDto sseMessageDto) {
        List<Long> unsentUserIds = new ArrayList<>();
        // å½“前服务内用户,直接发送消息
        for (Long userId : sseMessageDto.getUserIds()) {
            if (USER_TOKEN_EMITTERS.containsKey(userId)) {
                sendMessage(userId, sseMessageDto.getMessage());
                continue;
            }
            unsentUserIds.add(userId);
        }
        // ä¸åœ¨å½“前服务内用户,发布订阅消息
        if (CollUtil.isNotEmpty(unsentUserIds)) {
            SseMessageDto broadcastMessage = new SseMessageDto();
            broadcastMessage.setMessage(sseMessageDto.getMessage());
            broadcastMessage.setUserIds(unsentUserIds);
            RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
                log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}",
                    SSE_TOPIC, unsentUserIds, sseMessageDto.getMessage());
            });
        }
        SseMessageDto broadcastMessage = new SseMessageDto();
        broadcastMessage.setMessage(sseMessageDto.getMessage());
        broadcastMessage.setUserIds(sseMessageDto.getUserIds());
        RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
            log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}",
                SSE_TOPIC, sseMessageDto.getUserIds(), sseMessageDto.getMessage());
        });
    }
    /**
ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java
@@ -16,7 +16,14 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SseMessageUtils {
    private final static SseEmitterManager MANAGER = SpringUtils.getBean(SseEmitterManager.class);
    private final static Boolean SSE_ENABLE = SpringUtils.getProperty("sse.enabled", Boolean.class, true);
    private static SseEmitterManager MANAGER;
    static {
        if (isEnable() && MANAGER == null) {
            MANAGER = SpringUtils.getBean(SseEmitterManager.class);
        }
    }
    /**
     * å‘指定的WebSocket会话发送消息
@@ -25,6 +32,9 @@
     * @param message è¦å‘送的消息内容
     */
    public static void sendMessage(Long userId, String message) {
        if (!isEnable()) {
            return;
        }
        MANAGER.sendMessage(userId, message);
    }
@@ -34,6 +44,9 @@
     * @param message è¦å‘送的消息内容
     */
    public static void sendMessage(String message) {
        if (!isEnable()) {
            return;
        }
        MANAGER.sendMessage(message);
    }
@@ -43,6 +56,9 @@
     * @param sseMessageDto è¦å‘布的SSE消息对象
     */
    public static void publishMessage(SseMessageDto sseMessageDto) {
        if (!isEnable()) {
            return;
        }
        MANAGER.publishMessage(sseMessageDto);
    }
@@ -52,7 +68,17 @@
     * @param message è¦å‘布的消息内容
     */
    public static void publishAll(String message) {
        if (!isEnable()) {
            return;
        }
        MANAGER.publishAll(message);
    }
    /**
     * æ˜¯å¦å¼€å¯
     */
    public static Boolean isEnable() {
        return SSE_ENABLE;
    }
}
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java
@@ -4,7 +4,6 @@
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.config.MybatisPlusConfig;
import org.dromara.common.redis.config.RedisConfig;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.tenant.core.TenantSaTokenDao;
@@ -16,7 +15,7 @@
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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
@@ -33,8 +32,8 @@
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
    @ConditionalOnBean(MybatisPlusConfig.class)
    @AutoConfiguration(after = {MybatisPlusConfig.class})
    @ConditionalOnClass(TenantLineInnerInterceptor.class)
    @AutoConfiguration
    static class MybatisPlusConfiguration {
        /**
@@ -56,14 +55,12 @@
                // ä½¿ç”¨å•机模式
                // è®¾ç½®å¤šç§Ÿæˆ· redis key前缀
                singleServerConfig.setNameMapper(nameMapper);
                ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
            }
            ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
            // é›†ç¾¤é…ç½®æ–¹å¼ å‚考下方注释
            if (ObjectUtil.isNotNull(clusterServersConfig)) {
                // è®¾ç½®å¤šç§Ÿæˆ· redis key前缀
                clusterServersConfig.setNameMapper(nameMapper);
                ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
            }
        };
    }
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
@@ -27,15 +27,20 @@
        if (StringUtils.isBlank(name)) {
            return null;
        }
        if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
            return super.map(name);
        try {
            if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
                return super.map(name);
            }
        } catch (NoClassDefFoundError ignore) {
            // æœ‰äº›æœåŠ¡ä¸éœ€è¦mp导致类不存在 å¿½ç•¥å³å¯
        }
        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
            return super.map(name);
        }
        String tenantId = TenantHelper.getTenantId();
        if (StringUtils.isBlank(tenantId)) {
            log.error("无法获取有效的租户id -> Null");
            log.debug("无法获取有效的租户id -> Null");
            return super.map(name);
        }
        if (StringUtils.startsWith(name, tenantId + "")) {
            // å¦‚果存在则直接返回
@@ -53,15 +58,20 @@
        if (StringUtils.isBlank(unmap)) {
            return null;
        }
        if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
            return super.unmap(name);
        try {
            if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
                return unmap;
            }
        } catch (NoClassDefFoundError ignore) {
            // æœ‰äº›æœåŠ¡ä¸éœ€è¦mp导致类不存在 å¿½ç•¥å³å¯
        }
        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
            return super.unmap(name);
            return unmap;
        }
        String tenantId = TenantHelper.getTenantId();
        if (StringUtils.isBlank(tenantId)) {
            log.error("无法获取有效的租户id -> Null");
            log.debug("无法获取有效的租户id -> Null");
            return unmap;
        }
        if (StringUtils.startsWith(unmap, tenantId + "")) {
            // å¦‚果存在则删除
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
@@ -1,6 +1,7 @@
package org.dromara.common.tenant.helper;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
@@ -130,12 +131,13 @@
        if (!isEnable()) {
            return;
        }
        if (!isLogin() || !global) {
        if (!LoginHelper.isLogin() || !global) {
            TEMP_DYNAMIC_TENANT.set(tenantId);
            return;
        }
        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
        RedisUtils.setCacheObject(cacheKey, tenantId);
        SaHolder.getStorage().set(cacheKey, tenantId);
    }
    /**
@@ -147,7 +149,7 @@
        if (!isEnable()) {
            return null;
        }
        if (!isLogin()) {
        if (!LoginHelper.isLogin()) {
            return TEMP_DYNAMIC_TENANT.get();
        }
        // å¦‚果线程内有值 ä¼˜å…ˆè¿”回
@@ -155,8 +157,15 @@
        if (StringUtils.isNotBlank(tenantId)) {
            return tenantId;
        }
        SaStorage storage = SaHolder.getStorage();
        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
        tenantId = storage.getString(cacheKey);
        // å¦‚果为 -1 è¯´æ˜Žå·²ç»æŸ¥è¿‡redis并且不存在值 åˆ™ç›´æŽ¥è¿”回null
        if (StringUtils.isNotBlank(tenantId)) {
            return tenantId.equals("-1") ? null : tenantId;
        }
        tenantId = RedisUtils.getCacheObject(cacheKey);
        storage.set(cacheKey, StringUtils.isBlank(tenantId) ? "-1" : tenantId);
        return tenantId;
    }
@@ -167,13 +176,14 @@
        if (!isEnable()) {
            return;
        }
        if (!isLogin()) {
        if (!LoginHelper.isLogin()) {
            TEMP_DYNAMIC_TENANT.remove();
            return;
        }
        TEMP_DYNAMIC_TENANT.remove();
        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
        RedisUtils.deleteObject(cacheKey);
        SaHolder.getStorage().delete(cacheKey);
    }
    /**
@@ -216,15 +226,6 @@
            tenantId = LoginHelper.getTenantId();
        }
        return tenantId;
    }
    private static boolean isLogin() {
        try {
            StpUtil.checkLogin();
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
@@ -39,7 +39,7 @@
        if (ObjectUtil.isNotNull(trans)) {
            // å¦‚果映射字段不为空 åˆ™å–映射字段的值
            if (StringUtils.isNotBlank(translation.mapper())) {
                value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper());
                value = ReflectUtils.invokeGetter(gen.currentValue(), translation.mapper());
            }
            // å¦‚果为 null ç›´æŽ¥å†™å‡º
            if (ObjectUtil.isNull(value)) {
ruoyi-common/ruoyi-common-web/pom.xml
@@ -44,19 +44,6 @@
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-servlet</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-websockets-jsr</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java
@@ -1,18 +1,14 @@
package org.dromara.common.web.config;
import org.dromara.common.core.utils.StringUtils;
import jakarta.servlet.DispatcherType;
import org.dromara.common.web.config.properties.XssProperties;
import org.dromara.common.web.filter.RepeatableFilter;
import org.dromara.common.web.filter.XssFilter;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import java.util.HashMap;
import java.util.Map;
/**
 * Filter配置
@@ -23,26 +19,21 @@
@EnableConfigurationProperties(XssProperties.class)
public class FilterConfig {
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
    public FilterRegistrationBean xssFilterRegistration(XssProperties xssProperties) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
    public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setFilter(new XssFilter());
        registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
        registration.addUrlPatterns("/*");
        registration.setName("xssFilter");
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("excludes", xssProperties.getExcludes());
        registration.setInitParameters(initParameters);
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
        return registration;
    }
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
    public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
        FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new RepeatableFilter());
        registration.addUrlPatterns("/*");
        registration.setName("repeatableFilter");
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
@@ -1,6 +1,8 @@
package org.dromara.common.web.config;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.util.HttpString;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -16,18 +18,45 @@
@AutoConfiguration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
    /**
     * è‡ªå®šä¹‰ Undertow é…ç½®
     * <p>
     * ä¸»è¦é…ç½®å†…容包括:
     * 1. é…ç½® WebSocket éƒ¨ç½²ä¿¡æ¯
     * 2. åœ¨è™šæ‹Ÿçº¿ç¨‹æ¨¡å¼ä¸‹ä½¿ç”¨è™šæ‹Ÿçº¿ç¨‹æ± 
     * 3. ç¦ç”¨ä¸å®‰å…¨çš„ HTTP æ–¹æ³•,如 CONNECT、TRACE、TRACK
     * </p>
     *
     * @param factory Undertow çš„ Web æœåŠ¡å™¨å·¥åŽ‚
     */
    @Override
    public void customize(UndertowServletWebServerFactory factory) {
        factory.addDeploymentInfoCustomizers(deploymentInfo -> {
            // é…ç½® WebSocket éƒ¨ç½²ä¿¡æ¯ï¼Œè®¾ç½® WebSocket ä½¿ç”¨çš„缓冲区池
            WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
            deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
            // ä½¿ç”¨è™šæ‹Ÿçº¿ç¨‹
            // å¦‚果启用了虚拟线程,配置 Undertow ä½¿ç”¨è™šæ‹Ÿçº¿ç¨‹æ± 
            if (SpringUtils.isVirtual()) {
                // åˆ›å»ºè™šæ‹Ÿçº¿ç¨‹æ± ï¼Œçº¿ç¨‹æ± å‰ç¼€ä¸º "undertow-"
                VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
                // è®¾ç½®è™šæ‹Ÿçº¿ç¨‹æ± ä¸ºæ‰§è¡Œå™¨å’Œå¼‚步执行器
                deploymentInfo.setExecutor(executor);
                deploymentInfo.setAsyncExecutor(executor);
            }
            // é…ç½®ç¦æ­¢æŸäº›ä¸å®‰å…¨çš„ HTTP æ–¹æ³•(如 CONNECT、TRACE、TRACK)
            deploymentInfo.addInitialHandlerChainWrapper(handler -> {
                // ç¦æ­¢ä¸‰ä¸ªæ–¹æ³• CONNECT/TRACE/TRACK ä¹Ÿæ˜¯ä¸å®‰å…¨çš„ é¿å…çˆ¬è™«éªšæ‰°
                HttpString[] disallowedHttpMethods = {
                    HttpString.tryFromString("CONNECT"),
                    HttpString.tryFromString("TRACE"),
                    HttpString.tryFromString("TRACK")
                };
                // ä½¿ç”¨ DisallowedMethodsHandler æ‹¦æˆªå¹¶æ‹’绝这些方法的请求
                return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
            });
        });
    }
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java
@@ -3,6 +3,9 @@
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
 * xss过滤 é…ç½®å±žæ€§
 *
@@ -13,18 +16,13 @@
public class XssProperties {
    /**
     * è¿‡æ»¤å¼€å…³
     * Xss开关
     */
    private String enabled;
    private Boolean enabled;
    /**
     * æŽ’除链接(多个用逗号分隔)
     * æŽ’除路径
     */
    private String excludes;
    /**
     * åŒ¹é…é“¾æŽ¥
     */
    private String urlPatterns;
    private List<String> excludeUrls = new ArrayList<>();
}
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java
@@ -1,6 +1,8 @@
package org.dromara.common.web.filter;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.web.config.properties.XssProperties;
import org.springframework.http.HttpMethod;
import jakarta.servlet.*;
@@ -23,13 +25,8 @@
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String tempExcludes = filterConfig.getInitParameter("excludes");
        if (StringUtils.isNotEmpty(tempExcludes)) {
            String[] url = tempExcludes.split(StringUtils.SEPARATOR);
            for (int i = 0; url != null && i < url.length; i++) {
                excludes.add(url[i]);
            }
        }
        XssProperties properties = SpringUtils.getBean(XssProperties.class);
        excludes.addAll(properties.getExcludeUrls());
    }
    @Override
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java
@@ -1,19 +1,23 @@
package org.dromara.common.web.filter;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
 * XSS过滤处理
@@ -29,18 +33,51 @@
    }
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (value == null) {
            return null;
        }
        return HtmlUtil.cleanHtmlTag(value).trim();
    }
    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> valueMap = super.getParameterMap();
        if (MapUtil.isEmpty(valueMap)) {
            return valueMap;
        }
        // é¿å…æŸäº›å®¹å™¨ä¸å…è®¸æ”¹å‚数的情况 copy一份重新改
        Map<String, String[]> map = new HashMap<>(valueMap.size());
        map.putAll(valueMap);
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            String[] values = entry.getValue();
            if (values != null) {
                int length = values.length;
                String[] escapseValues = new String[length];
                for (int i = 0; i < length; i++) {
                    // é˜²xss攻击和过滤前后空格
                    escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
                }
                map.put(entry.getKey(), escapseValues);
            }
        }
        return map;
    }
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapseValues = new String[length];
            for (int i = 0; i < length; i++) {
                // é˜²xss攻击和过滤前后空格
                escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
            }
            return escapseValues;
        if (ArrayUtil.isEmpty(values)) {
            return values;
        }
        return super.getParameterValues(name);
        int length = values.length;
        String[] escapseValues = new String[length];
        for (int i = 0; i < length; i++) {
            // é˜²xss攻击和过滤前后空格
            escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
        }
        return escapseValues;
    }
    @Override
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
@@ -2,14 +2,17 @@
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -51,6 +54,27 @@
        log.error(e.getMessage());
        Integer code = e.getCode();
        return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
    }
    /**
     * è®¤è¯å¤±è´¥
     */
    @ResponseStatus(org.springframework.http.HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(SseException.class)
    public String handleNotLoginException(SseException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.debug("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
        return JsonUtils.toJsonString(R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"));
    }
    /**
     * servlet异常
     */
    @ExceptionHandler(ServletException.class)
    public R<Void> handleServletException(ServletException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return R.fail(e.getMessage());
    }
    /**
@@ -152,7 +176,7 @@
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error(e.getMessage());
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        String message = StreamUtils.join(e.getBindingResult().getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
        return R.fail(message);
    }
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
@@ -2,6 +2,7 @@
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@@ -64,9 +65,11 @@
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        StopWatch stopWatch = KEY_CACHE.get();
        stopWatch.stop();
        log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
        KEY_CACHE.remove();
        if (ObjectUtil.isNotNull(stopWatch)) {
            stopWatch.stop();
            log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getDuration().toMillis());
            KEY_CACHE.remove();
        }
    }
    /**
ruoyi-common/ruoyi-common-websocket/pom.xml
@@ -35,6 +35,12 @@
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java
@@ -2,6 +2,7 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import java.util.Map;
@@ -25,6 +26,7 @@
     * @param session    è¦æ·»åŠ çš„WebSocket会话
     */
    public static void addSession(Long sessionKey, WebSocketSession session) {
        removeSession(sessionKey);
        USER_SESSION_MAP.put(sessionKey, session);
    }
@@ -34,8 +36,10 @@
     * @param sessionKey è¦ç§»é™¤çš„会话键
     */
    public static void removeSession(Long sessionKey) {
        if (USER_SESSION_MAP.containsKey(sessionKey)) {
            USER_SESSION_MAP.remove(sessionKey);
        WebSocketSession session = USER_SESSION_MAP.remove(sessionKey);
        try {
            session.close(CloseStatus.BAD_DATA);
        } catch (Exception ignored) {
        }
    }
ruoyi-extend/ruoyi-monitor-admin/Dockerfile
@@ -1,6 +1,6 @@
# è´å°”实验室 Spring å®˜æ–¹æŽ¨èé•œåƒ JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@@ -15,6 +15,8 @@
ADD ./target/ruoyi-monitor-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
           -jar app.jar
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
@@ -13,8 +13,8 @@
spring:
  security:
    user:
      name: ruoyi
      password: 123456
      name: @monitor.username@
      password: @monitor.password@
  boot:
    admin:
      ui:
@@ -44,5 +44,5 @@
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
  username: @monitor.username@
  password: @monitor.password@
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml
@@ -4,7 +4,7 @@
    <contextName>logback</contextName>
    <property name="log.path" value="./logs/ruoyi-monitor-admin"/>
    <property name="console.log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
              value="%cyan(%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">
@@ -31,4 +31,4 @@
        <appender-ref ref="file"/>
    </root>
</configuration>
</configuration>
ruoyi-extend/ruoyi-snailjob-server/Dockerfile
@@ -1,6 +1,6 @@
# è´å°”实验室 Spring å®˜æ–¹æŽ¨èé•œåƒ JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@@ -16,6 +16,8 @@
ADD ./target/ruoyi-snailjob-server.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
           -jar app.jar
ruoyi-extend/ruoyi-snailjob-server/pom.xml
@@ -16,6 +16,18 @@
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-server-starter</artifactId>
            <version>${snailjob.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.scala-lang</groupId>
                    <artifactId>scala-library</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.13.9</version>
        </dependency>
        <dependency>
ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java
@@ -22,7 +22,7 @@
    public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
        FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new ActuatorAuthFilter(username, password));
        registrationBean.addUrlPatterns("/actuator", "/actuator/**");
        registrationBean.addUrlPatterns("/actuator", "/actuator/*");
        return registrationBean;
    }
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
@@ -20,8 +20,8 @@
  retry-pull-page-size: 1000
  # æ‹‰å–重试数据的每批次的大小
  job-pull-page-size: 1000
  # æœåŠ¡ç«¯netty端口
  netty-port: 17888
  # æœåŠ¡å™¨ç«¯å£
  server-port: 17888
  # ä¸€ä¸ªå®¢æˆ·ç«¯æ¯ç§’最多接收的重试数量指令
  limiter: 1000
  # å·æ®µæ¨¡å¼ä¸‹æ­¥é•¿é…ç½®
@@ -34,7 +34,10 @@
    max-count: 288
    #间隔时间
    trigger-interval: 900
  # é‡è¯•每次拉取的次数
  retry-max-pull-count: 10
  # RPC通讯类型: netty,grpc
  rpc-type: grpc
--- # ç›‘控中心配置
spring.boot.admin.client:
@@ -46,5 +49,5 @@
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
  username: @monitor.username@
  password: @monitor.password@
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
@@ -20,8 +20,8 @@
  retry-pull-page-size: 1000
  # æ‹‰å–重试数据的每批次的大小
  job-pull-page-size: 1000
  # æœåŠ¡ç«¯ netty ç«¯å£
  netty-port: 17888
  # æœåŠ¡å™¨ç«¯å£
  server-port: 17888
  # ä¸€ä¸ªå®¢æˆ·ç«¯æ¯ç§’最多接收的重试数量指令
  limiter: 1000
  # å·æ®µæ¨¡å¼ä¸‹æ­¥é•¿é…ç½®
@@ -34,7 +34,10 @@
    max-count: 288
    #间隔时间
    trigger-interval: 900
  # é‡è¯•每次拉取的次数
  retry-max-pull-count: 10
  # RPC通讯类型: netty,grpc
  rpc-type: grpc
--- # ç›‘控中心配置
spring.boot.admin.client:
@@ -46,5 +49,5 @@
    metadata:
      username: ${spring.boot.admin.client.username}
      userpassword: ${spring.boot.admin.client.password}
  username: ruoyi
  password: 123456
  username: @monitor.username@
  password: @monitor.password@
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml
@@ -2,7 +2,7 @@
<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"/>
              value="%cyan(%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"/>
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java
@@ -1,14 +1,16 @@
package org.dromara.demo.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.mail.utils.MailUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.util.Arrays;
/**
@@ -16,6 +18,7 @@
 *
 * @author Michelle.Chung
 */
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@@ -49,4 +52,19 @@
        return R.ok();
    }
    /**
     * å‘送邮件(多附件)
     *
     * @param to       æŽ¥æ”¶äºº
     * @param subject  æ ‡é¢˜
     * @param text     å†…容
     * @param paths    é™„件路径
     */
    @GetMapping("/sendMessageWithAttachments")
    public R<Void> sendMessageWithAttachments(String to, String subject, String text, String[] paths) {
        File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
        MailUtils.sendText(to, subject, text, array);
        return R.ok();
    }
}
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
@@ -1,5 +1,6 @@
package org.dromara.demo.controller;
import cn.hutool.core.thread.ThreadUtil;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.RedisUtils;
@@ -83,11 +84,7 @@
        RedisUtils.setCacheObject(key, value);
        boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10));
        System.out.println("***********" + flag);
        try {
            Thread.sleep(11 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ThreadUtil.sleep(11 * 1000);
        Object obj = RedisUtils.getCacheObject(key);
        return R.ok(value.equals(obj));
    }
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java
@@ -1,5 +1,6 @@
package org.dromara.demo.controller;
import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
@@ -33,13 +34,9 @@
    @Lock4j(keys = {"#key"})
    @GetMapping("/testLock4j")
    public R<String> testLock4j(String key, String value) {
        System.out.println("start:" + key + ",time:" + LocalTime.now().toString());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end :" + key + ",time:" + LocalTime.now().toString());
        System.out.println("start:" + key + ",time:" + LocalTime.now());
        ThreadUtil.sleep(10000);
        System.out.println("end :" + key + ",time:" + LocalTime.now());
        return R.ok("操作成功", value);
    }
@@ -54,11 +51,7 @@
        }
        // èŽ·å–é”æˆåŠŸï¼Œå¤„ç†ä¸šåŠ¡
        try {
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                //
            }
            ThreadUtil.sleep(8000);
            System.out.println("执行简单方法1 , å½“前线程:" + Thread.currentThread().getName());
        } finally {
            //释放锁
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java
@@ -15,6 +15,9 @@
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.common.excel.convert.ExcelEnumConvert;
import java.io.Serial;
import java.io.Serializable;
/**
 * å¸¦æœ‰ä¸‹æ‹‰é€‰çš„Excel导出
 *
@@ -24,8 +27,9 @@
@ExcelIgnoreUnannotated
@AllArgsConstructor
@NoArgsConstructor
public class ExportDemoVo {
public class ExportDemoVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java
@@ -2,6 +2,8 @@
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.demo.domain.TestDemo;
@@ -36,30 +38,35 @@
    /**
     * éƒ¨é—¨id
     */
    @ExcelRequired
    @ExcelProperty(value = "部门id")
    private Long deptId;
    /**
     * ç”¨æˆ·id
     */
    @ExcelRequired
    @ExcelProperty(value = "用户id")
    private Long userId;
    /**
     * æŽ’序号
     */
    @ExcelRequired
    @ExcelProperty(value = "排序号")
    private Integer orderNum;
    /**
     * key键
     */
    @ExcelNotation(value = "测试key")
    @ExcelProperty(value = "key键")
    private String testKey;
    /**
     * å€¼
     */
    @ExcelNotation(value = "测试value")
    @ExcelProperty(value = "值")
    private String value;
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
@@ -34,21 +34,25 @@
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    List<TestDemo> selectList(IPage<TestDemo> page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
    default <P extends IPage<TestDemoVo>> P selectVoPage(IPage<TestDemo> page, Wrapper<TestDemo> wrapper) {
        return selectVoPage(page, wrapper, this.currentVoClass());
    }
    @Override
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
    default List<TestDemoVo> selectVoList(Wrapper<TestDemo> wrapper) {
        return selectVoList(wrapper, this.currentVoClass());
    }
    @Override
    @DataPermission(value = {
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    }, joinStr = "AND")
    List<TestDemo> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
    List<TestDemo> selectByIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
    @Override
    @DataPermission({
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java
@@ -1,10 +1,11 @@
package org.dromara.demo.service.impl;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.excel.core.DropDownOptions;
import org.dromara.common.excel.utils.ExcelUtil;
@@ -34,7 +35,7 @@
            // æ¨¡æ‹Ÿæ•°æ®åº“中的一条数据
            ExportDemoVo everyRowData = new ExportDemoVo();
            everyRowData.setNickName("用户-" + i);
            everyRowData.setUserStatus(UserStatus.OK.getCode());
            everyRowData.setUserStatus(SystemConstants.NORMAL);
            everyRowData.setGender("1");
            everyRowData.setPhoneNumber(String.format("175%08d", i));
            everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
@@ -121,8 +122,9 @@
        List<DemoCityData> provinceList = new ArrayList<>();
        // å®žé™…业务中一般采用数据库读取的形式,这里直接拼接创建
        provinceList.add(new DemoCityData(0, null, "安徽省"));
        provinceList.add(new DemoCityData(1, null, "江苏省"));
        provinceList.add(new DemoCityData(0, null, "P100000"));
        provinceList.add(new DemoCityData(1, null, "P200000"));
        provinceList.add(new DemoCityData(2, null, "P300000"));
        return provinceList;
    }
@@ -137,11 +139,11 @@
        List<DemoCityData> cityList = new ArrayList<>();
        // å®žé™…业务中一般采用数据库读取的形式,这里直接拼接创建
        cityList.add(new DemoCityData(0, 0, "合肥市"));
        cityList.add(new DemoCityData(1, 0, "芜湖市"));
        cityList.add(new DemoCityData(2, 1, "南京市"));
        cityList.add(new DemoCityData(3, 1, "无锡市"));
        cityList.add(new DemoCityData(4, 1, "徐州市"));
        cityList.add(new DemoCityData(0, 0, "C110000"));
        cityList.add(new DemoCityData(1, 0, "C120000"));
        cityList.add(new DemoCityData(2, 1, "C210000"));
        cityList.add(new DemoCityData(3, 1, "C220000"));
        cityList.add(new DemoCityData(4, 1, "C230000"));
        selectParentData(provinceList, cityList);
@@ -157,17 +159,29 @@
    private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
        List<DemoCityData> areaList = new ArrayList<>();
        int minCount = 500;
        int maxCount = 10000;
        // å®žé™…业务中一般采用数据库读取的形式,这里直接拼接创建
        areaList.add(new DemoCityData(0, 0, "瑶海区"));
        areaList.add(new DemoCityData(1, 0, "庐江区"));
        areaList.add(new DemoCityData(2, 1, "南宁县"));
        areaList.add(new DemoCityData(3, 1, "镜湖区"));
        areaList.add(new DemoCityData(4, 2, "玄武区"));
        areaList.add(new DemoCityData(5, 2, "秦淮区"));
        areaList.add(new DemoCityData(6, 3, "宜兴市"));
        areaList.add(new DemoCityData(7, 3, "新吴区"));
        areaList.add(new DemoCityData(8, 4, "鼓楼区"));
        areaList.add(new DemoCityData(9, 4, "丰县"));
        for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
            areaList.add(new DemoCityData(areaList.size(), 0, String.format("A11%04d", i)));
        }
        for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
            areaList.add(new DemoCityData(areaList.size(), 1, String.format("A12%04d", i)));
        }
        for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
            areaList.add(new DemoCityData(areaList.size(), 2, String.format("A21%04d", i)));
        }
        for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
            areaList.add(new DemoCityData(areaList.size(), 3, String.format("A22%04d", i)));
        }
        for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
            areaList.add(new DemoCityData(areaList.size(), 4, String.format("A23%04d", i)));
        }
        selectParentData(cityList, areaList);
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
@@ -101,7 +101,7 @@
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
            List<TestDemo> list = baseMapper.selectBatchIds(ids);
            List<TestDemo> list = baseMapper.selectByIds(ids);
            if (list.size() != ids.size()) {
                throw new ServiceException("您没有删除权限!");
            }
ruoyi-modules/ruoyi-generator/pom.xml
@@ -64,19 +64,19 @@
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--            <version>${anyline.version}</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--            <version>${anyline.version}</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.anyline</groupId>-->
<!--            <artifactId>anyline-data-jdbc-mssql</artifactId>-->
<!--        <version>${anyline.version}</version>-->
<!--            <version>${anyline.version}</version>-->
<!--        </dependency>-->
    </dependencies>
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java
@@ -56,13 +56,13 @@
     * æ•°æ®åº“时间类型
     */
    String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp", "year", "interval",
        "smalldatetime", "datetime2", "datetimeoffset"};
        "smalldatetime", "datetime2", "datetimeoffset", "timestamptz"};
    /**
     * æ•°æ®åº“数字类型
     */
    String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
        "bit", "bigint", "float", "double", "decimal", "numeric", "real", "double precision",
    String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "int2", "int4", "int8", "number", "integer",
        "bit", "bigint", "float", "float4", "float8", "double", "decimal", "numeric", "real", "double precision",
        "smallserial", "serial", "bigserial", "money", "smallmoney"};
    /**
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java
@@ -162,7 +162,7 @@
     * ä¸Šçº§èœå•ID字段
     */
    @TableField(exist = false)
    private String parentMenuId;
    private Long parentMenuId;
    /**
     * ä¸Šçº§èœå•名称字段
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
@@ -159,7 +159,7 @@
    }
    public boolean isEdit() {
        return isInsert(this.isEdit);
        return isEdit(this.isEdit);
    }
    public boolean isEdit(String isEdit) {
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java
@@ -2,10 +2,8 @@
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTable;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -40,6 +38,14 @@
     */
    GenTable selectGenTableByName(String tableName);
    /**
     * æŸ¥è¯¢æŒ‡å®šæ•°æ®æºä¸‹çš„æ‰€æœ‰è¡¨ååˆ—表
     *
     * @param dataName æ•°æ®æºåç§°ï¼Œç”¨äºŽé€‰æ‹©ä¸åŒçš„æ•°æ®æº
     * @return å½“前数据库中的表名列表
     *
     * @DS("") ä½¿ç”¨é»˜è®¤æ•°æ®æºæ‰§è¡ŒæŸ¥è¯¢æ“ä½œ
     */
    @DS("")
    List<String> selectTableNameList(String dataName);
}
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
@@ -29,7 +29,6 @@
import org.dromara.common.json.utils.JsonUtils;
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.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
@@ -64,7 +63,7 @@
    private final GenTableColumnMapper genTableColumnMapper;
    private final IdentifierGenerator identifierGenerator;
    private static final String[] TABLE_IGNORE = new String[]{"sj_", "act_", "flw_", "gen_"};
    private static final String[] TABLE_IGNORE = new String[]{"sj_", "flow_", "gen_"};
    /**
     * æŸ¥è¯¢ä¸šåŠ¡å­—æ®µåˆ—è¡¨
@@ -106,7 +105,8 @@
            .like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
            .like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                "create_time", params.get("beginTime"), params.get("endTime"));
                "create_time", params.get("beginTime"), params.get("endTime"))
            .orderByDesc("update_time");
        return wrapper;
    }
@@ -137,7 +137,7 @@
        }
        // è¿‡æ»¤å¹¶è½¬æ¢è¡¨æ ¼æ•°æ®
        List<GenTable> tables = tablesMap.values().stream()
            .filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> {
                if (CollUtil.isEmpty(tableNames)) {
                    return true;
@@ -162,10 +162,12 @@
                GenTable gen = new GenTable();
                gen.setTableName(x.getName());
                gen.setTableComment(x.getComment());
                gen.setCreateTime(x.getCreateTime());
                // postgresql的表元数据没有创建时间这个东西(好奇葩) åªèƒ½new Date代替
                gen.setCreateTime(ObjectUtil.defaultIfNull(x.getCreateTime(), new Date()));
                gen.setUpdateTime(x.getUpdateTime());
                return gen;
            }).toList();
            }).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
            .toList();
        IPage<GenTable> page = pageQuery.build();
        page.setTotal(tables.size());
@@ -192,7 +194,7 @@
        }
        List<Table<?>> tableList = tablesMap.values().stream()
            .filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
            .filter(x -> tableNameSet.contains(x.getName())).toList();
        if (CollUtil.isEmpty(tableList)) {
@@ -259,11 +261,10 @@
    @DSTransactional
    @Override
    public void importGenTable(List<GenTable> tableList, String dataName) {
        Long operId = LoginHelper.getUserId();
        try {
            for (GenTable table : tableList) {
                String tableName = table.getTableName();
                GenUtils.initTable(table, operId);
                GenUtils.initTable(table);
                table.setDataName(dataName);
                int row = baseMapper.insert(table);
                if (row > 0) {
@@ -294,14 +295,18 @@
    @DS("#dataName")
    @Override
    public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
        LinkedHashMap<String, Column> columns = ServiceProxy.metadata().columns(tableName);
        Table<?> table = ServiceProxy.metadata().table(tableName);
        if (ObjectUtil.isNull(table)) {
            return new ArrayList<>();
        }
        LinkedHashMap<String, Column> columns = table.getColumns();
        List<GenTableColumn> tableColumns = new ArrayList<>();
        columns.forEach((columnName, column) -> {
            GenTableColumn tableColumn = new GenTableColumn();
            tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
            tableColumn.setColumnName(column.getName());
            tableColumn.setColumnComment(column.getComment());
            tableColumn.setColumnType(column.getTypeName().toLowerCase());
            tableColumn.setColumnType(column.getOriginType().toLowerCase());
            tableColumn.setSort(column.getPosition());
            tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
            tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
@@ -548,7 +553,7 @@
            String treeCode = paramsObj.getStr(GenConstants.TREE_CODE);
            String treeParentCode = paramsObj.getStr(GenConstants.TREE_PARENT_CODE);
            String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
            String parentMenuId = paramsObj.getStr(GenConstants.PARENT_MENU_ID);
            Long parentMenuId = paramsObj.getLong(GenConstants.PARENT_MENU_ID);
            String parentMenuName = paramsObj.getStr(GenConstants.PARENT_MENU_NAME);
            genTable.setTreeCode(treeCode);
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java
@@ -22,14 +22,15 @@
    /**
     * åˆå§‹åŒ–表信息
     */
    public static void initTable(GenTable genTable, Long operId) {
    public static void initTable(GenTable genTable) {
        genTable.setClassName(convertClassName(genTable.getTableName()));
        genTable.setPackageName(GenConfig.getPackageName());
        genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
        genTable.setBusinessName(getBusinessName(genTable.getTableName()));
        genTable.setFunctionName(replaceText(genTable.getTableComment()));
        genTable.setFunctionAuthor(GenConfig.getAuthor());
        genTable.setCreateBy(operId);
        genTable.setCreateTime(null);
        genTable.setUpdateTime(null);
    }
    /**
@@ -37,9 +38,11 @@
     */
    public static void initColumnField(GenTableColumn column, GenTable table) {
        String dataType = getDbType(column.getColumnType());
        String columnName = column.getColumnName();
        // ç»Ÿä¸€è½¬å°å†™ é¿å…æœ‰äº›æ•°æ®åº“默认大写问题 å¦‚果需要特别书写方式 è¯·åœ¨å®žä½“类增加注解标注别名
        String columnName = column.getColumnName().toLowerCase();
        column.setTableId(table.getTableId());
        column.setCreateBy(table.getCreateBy());
        column.setCreateTime(null);
        column.setUpdateTime(null);
        // è®¾ç½®java字段名
        column.setJavaField(StringUtils.toCamelCase(columnName));
        // è®¾ç½®é»˜è®¤ç±»åž‹
@@ -56,20 +59,9 @@
            column.setHtmlType(GenConstants.HTML_DATETIME);
        } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
            column.setHtmlType(GenConstants.HTML_INPUT);
            // å¦‚果是浮点型 ç»Ÿä¸€ç”¨BigDecimal
            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), StringUtils.SEPARATOR);
            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
                column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
            }
            // å¦‚果是整形
            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
                column.setJavaType(GenConstants.TYPE_INTEGER);
            }
            // é•¿æ•´å½¢
            else {
                column.setJavaType(GenConstants.TYPE_LONG);
            }
            // æ•°æ®åº“的数字字段与java不匹配 ä¸”很多数据库的数字字段很模糊 ä¾‹å¦‚oracle只有number没有细分
            // æ‰€ä»¥é»˜è®¤æ•°å­—类型全为Long可在界面上自行编辑想要的类型 æœ‰ä»€ä¹ˆç‰¹æ®Šéœ€æ±‚也可以在这里特殊处理
            column.setJavaType(GenConstants.TYPE_LONG);
        }
        // BO对象 é»˜è®¤æ’入勾选
@@ -79,10 +71,6 @@
        // BO对象 é»˜è®¤ç¼–辑勾选
        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName)) {
            column.setIsEdit(GenConstants.REQUIRE);
        }
        // BO对象 é»˜è®¤æ˜¯å¦å¿…填勾选
        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName)) {
            column.setIsRequired(GenConstants.REQUIRE);
        }
        // VO对象 é»˜è®¤è¿”回勾选
        if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName)) {
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
@@ -95,6 +95,10 @@
            ${ClassName}::get$AttrName ,params.get("begin$AttrName"), params.get("end$AttrName"));
#end
#end
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.isPk==1)
        lqw.orderByAsc(${ClassName}::get$AttrName);
#end
#end
        return lqw;
    }
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm
@@ -58,7 +58,7 @@
     * ${column.columnComment}Url
     */
    @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
    private String ${column.javaField}Url";
    private String ${column.javaField}Url;
#end
#end
#end
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java
@@ -19,6 +19,7 @@
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -43,7 +44,7 @@
    @GetMapping("/list")
    public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
        // èŽ·å–æ‰€æœ‰æœªè¿‡æœŸçš„ token
        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
        Collection<String> keys = RedisUtils.keys(CacheConstants.ONLINE_TOKEN_KEY + "*");
        List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
        for (String key : keys) {
            String token = StringUtils.substringAfterLast(key, ":");
@@ -113,7 +114,7 @@
     * @param tokenId token值
     */
    @Log(title = "在线设备", businessType = BusinessType.FORCE)
    @PostMapping("/{tokenId}")
    @DeleteMapping("/myself/{tokenId}")
    public R<Void> remove(@PathVariable("tokenId") String tokenId) {
        try {
            // èŽ·å–æŒ‡å®šè´¦å· id çš„ token é›†åˆ
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.getClientId(), bo.getStatus()));
        return toAjax(sysClientService.updateClientStatus(bo.getClientId(), bo.getStatus()));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
@@ -3,7 +3,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.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.annotation.Log;
@@ -94,7 +94,7 @@
            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())) {
        } else if (StringUtils.equals(SystemConstants.DISABLE, dept.getStatus())) {
            if (deptService.selectNormalChildrenDeptById(deptId) > 0) {
                return R.fail("该部门包含未停用的子部门!");
            } else if (deptService.checkDeptExistUser(deptId)) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java
@@ -4,8 +4,9 @@
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.lang.tree.Tree;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.annotation.Log;
@@ -18,7 +19,6 @@
import org.dromara.system.domain.vo.RouterVo;
import org.dromara.system.domain.vo.SysMenuVo;
import org.dromara.system.service.ISysMenuService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -128,7 +128,7 @@
    public R<Void> add(@Validated @RequestBody SysMenuBo menu) {
        if (!menuService.checkMenuNameUnique(menu)) {
            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
        } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
        return toAjax(menuService.insertMenu(menu));
@@ -144,7 +144,7 @@
    public R<Void> edit(@Validated @RequestBody SysMenuBo menu) {
        if (!menuService.checkMenuNameUnique(menu)) {
            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
        } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        } else if (menu.getMenuId().equals(menu.getParentId())) {
            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java
@@ -53,7 +53,7 @@
     *
     * @param ossIds OSS对象ID串
     */
    @SaCheckPermission("system:oss:list")
    @SaCheckPermission("system:oss:query")
    @GetMapping("/listByIds/{ossIds}")
    public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
                                       @PathVariable Long[] ossIds) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java
@@ -4,7 +4,7 @@
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.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
@@ -91,7 +91,7 @@
            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
        } else if (!postService.checkPostCodeUnique(post)) {
            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
        } else if (UserConstants.POST_DISABLE.equals(post.getStatus())
        } else if (SystemConstants.DISABLE.equals(post.getStatus())
            && postService.countUserPostById(post.getPostId()) > 0) {
            return R.fail("该岗位下存在已分配用户,不能禁用!");
        }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
@@ -98,8 +98,8 @@
        if (BCrypt.checkpw(bo.getNewPassword(), password)) {
            return R.fail("新密码不能与旧密码相同");
        }
        if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())) > 0) {
        int rows = DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())));
        if (rows > 0) {
            return R.ok();
        }
        return R.fail("修改密码异常,请联系管理员");
@@ -121,7 +121,8 @@
            }
            SysOssVo oss = ossService.upload(avatarfile);
            String avatar = oss.getUrl();
            if (userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId())) {
            boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
            if (updateSuccess) {
                AvatarVo avatarVo = new AvatarVo();
                avatarVo.setImgUrl(avatar);
                return R.ok(avatarVo);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java
@@ -59,7 +59,7 @@
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:export")
    @Log(title = "租户", businessType = BusinessType.EXPORT)
    @Log(title = "租户管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(SysTenantBo bo, HttpServletResponse response) {
        List<SysTenantVo> list = tenantService.queryList(bo);
@@ -85,7 +85,7 @@
    @ApiEncrypt
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:add")
    @Log(title = "租户", businessType = BusinessType.INSERT)
    @Log(title = "租户管理", businessType = BusinessType.INSERT)
    @Lock4j
    @RepeatSubmit()
    @PostMapping()
@@ -101,7 +101,7 @@
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:edit")
    @Log(title = "租户", businessType = BusinessType.UPDATE)
    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
@@ -117,7 +117,7 @@
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:edit")
    @Log(title = "租户", businessType = BusinessType.UPDATE)
    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
    @PutMapping("/changeStatus")
    public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
        tenantService.checkTenantAllowed(bo.getTenantId());
@@ -131,7 +131,7 @@
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:remove")
    @Log(title = "租户", businessType = BusinessType.DELETE)
    @Log(title = "租户管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
@@ -169,11 +169,25 @@
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @SaCheckPermission("system:tenant:edit")
    @Log(title = "租户", businessType = BusinessType.UPDATE)
    @Log(title = "租户管理", businessType = BusinessType.UPDATE)
    @GetMapping("/syncTenantPackage")
    public R<Void> syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId,
                                     @NotNull(message = "套餐ID不能为空") Long packageId) {
        return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId)));
    }
    /**
     * åŒæ­¥ç§Ÿæˆ·å­—å…¸
     */
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
    @Log(title = "租户管理", businessType = BusinessType.INSERT)
    @GetMapping("/syncTenantDict")
    public R<Void> syncTenantDict() {
        if (!TenantHelper.isEnable()) {
            return R.fail("当前未开启租户模式");
        }
        tenantService.syncTenantDict();
        return R.ok("同步租户字典成功");
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
@@ -8,7 +8,7 @@
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.StreamUtils;
@@ -128,13 +128,9 @@
    @SaCheckPermission("system:user:query")
    @GetMapping(value = {"/", "/{userId}"})
    public R<SysUserInfoVo> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
        userService.checkUserDataScope(userId);
        SysUserInfoVo userInfoVo = new SysUserInfoVo();
        SysRoleBo roleBo = new SysRoleBo();
        roleBo.setStatus(UserConstants.ROLE_NORMAL);
        List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
        if (ObjectUtil.isNotNull(userId)) {
            userService.checkUserDataScope(userId);
            SysUserVo sysUser = userService.selectUserById(userId);
            userInfoVo.setUser(sysUser);
            userInfoVo.setRoleIds(roleService.selectRoleListByUserId(userId));
@@ -146,6 +142,10 @@
                userInfoVo.setPostIds(postService.selectPostListByUserId(userId));
            }
        }
        SysRoleBo roleBo = new SysRoleBo();
        roleBo.setStatus(SystemConstants.NORMAL);
        List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
        return R.ok(userInfoVo);
    }
@@ -218,7 +218,7 @@
    @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));
        return R.ok(userService.selectUserByIds(ArrayUtil.isEmpty(userIds) ? null : List.of(userIds), deptId));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java
@@ -68,7 +68,7 @@
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java
@@ -70,7 +70,7 @@
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java
@@ -2,10 +2,10 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.tenant.core.TenantEntity;
/**
 * å­—典数据表 sys_dict_data
@@ -65,7 +65,7 @@
    private String remark;
    public boolean getDefault() {
        return UserConstants.YES.equals(this.isDefault);
        return SystemConstants.YES.equals(this.isDefault);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java
@@ -3,12 +3,12 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.util.ArrayList;
import java.util.List;
@@ -134,8 +134,8 @@
            routerPath = innerLinkReplaceEach(routerPath);
        }
        // éžå¤–链并且是一级目录(类型为目录)
        if (0L == getParentId() && UserConstants.TYPE_DIR.equals(getMenuType())
            && UserConstants.NO_FRAME.equals(getIsFrame())) {
        if (0L == getParentId() && SystemConstants.TYPE_DIR.equals(getMenuType())
            && SystemConstants.NO_FRAME.equals(getIsFrame())) {
            routerPath = "/" + this.path;
        }
        // éžå¤–链并且是一级目录(类型为菜单)
@@ -149,13 +149,13 @@
     * èŽ·å–ç»„ä»¶ä¿¡æ¯
     */
    public String getComponentInfo() {
        String component = UserConstants.LAYOUT;
        String component = SystemConstants.LAYOUT;
        if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) {
            component = this.component;
        } else if (StringUtils.isEmpty(this.component) && getParentId() != 0L && isInnerLink()) {
            component = UserConstants.INNER_LINK;
            component = SystemConstants.INNER_LINK;
        } else if (StringUtils.isEmpty(this.component) && isParentView()) {
            component = UserConstants.PARENT_VIEW;
            component = SystemConstants.PARENT_VIEW;
        }
        return component;
    }
@@ -164,21 +164,21 @@
     * æ˜¯å¦ä¸ºèœå•内部跳转
     */
    public boolean isMenuFrame() {
        return getParentId() == 0L && UserConstants.TYPE_MENU.equals(menuType) && isFrame.equals(UserConstants.NO_FRAME);
        return getParentId() == 0L && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
    }
    /**
     * æ˜¯å¦ä¸ºå†…链组件
     */
    public boolean isInnerLink() {
        return isFrame.equals(UserConstants.NO_FRAME) && StringUtils.ishttp(path);
        return isFrame.equals(SystemConstants.NO_FRAME) && StringUtils.ishttp(path);
    }
    /**
     * æ˜¯å¦ä¸ºparent_view组件
     */
    public boolean isParentView() {
        return getParentId() != 0L && UserConstants.TYPE_DIR.equals(menuType);
        return getParentId() != 0L && SystemConstants.TYPE_DIR.equals(menuType);
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java
@@ -62,7 +62,7 @@
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java
@@ -95,7 +95,7 @@
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java
@@ -25,28 +25,34 @@
     */
    @TableId(value = "package_id")
    private Long packageId;
    /**
     * å¥—餐名称
     */
    private String packageName;
    /**
     * å…³è”菜单id
     */
    private String menuIds;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * èœå•树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
     */
    private Boolean menuCheckStrictly;
    /**
     * çŠ¶æ€ï¼ˆ0正常 1停用)
     */
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java
@@ -1,11 +1,11 @@
package org.dromara.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.tenant.core.TenantEntity;
import java.util.Date;
@@ -83,7 +83,7 @@
    private String status;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
@@ -109,7 +109,7 @@
    }
    public boolean isSuperAdmin() {
        return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
        return SystemConstants.SUPER_ADMIN_ID.equals(this.userId);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java
@@ -7,7 +7,7 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysRole;
@@ -88,7 +88,7 @@
    }
    public boolean isSuperAdmin() {
        return UserConstants.SUPER_ADMIN_ID.equals(this.roleId);
        return SystemConstants.SUPER_ADMIN_ID.equals(this.roleId);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
@@ -7,7 +7,7 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.xss.Xss;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysUser;
@@ -113,7 +113,7 @@
    }
    public boolean isSuperAdmin() {
        return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
        return SystemConstants.SUPER_ADMIN_ID.equals(this.userId);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java
@@ -2,12 +2,12 @@
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.core.constant.UserConstants;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.system.domain.SysRole;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@@ -94,7 +94,7 @@
    private boolean flag = false;
    public boolean isSuperAdmin() {
        return UserConstants.SUPER_ADMIN_ID.equals(this.roleId);
        return SystemConstants.SUPER_ADMIN_ID.equals(this.roleId);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java
@@ -3,10 +3,14 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.http.HtmlUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.excel.core.ExcelListener;
import org.dromara.common.excel.core.ExcelResult;
@@ -79,8 +83,12 @@
            }
        } catch (Exception e) {
            failureNum++;
            String msg = "<br/>" + failureNum + "、账号 " + userVo.getUserName() + " å¯¼å…¥å¤±è´¥ï¼š";
            failureMsg.append(msg).append(e.getMessage());
            String msg = "<br/>" + failureNum + "、账号 " + HtmlUtil.cleanHtmlTag(userVo.getUserName()) + " å¯¼å…¥å¤±è´¥ï¼š";
            String message = e.getMessage();
            if (e instanceof ConstraintViolationException cvException) {
                message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
            }
            failureMsg.append(msg).append(message);
            log.error(msg, e);
        }
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java
@@ -1,13 +1,16 @@
package org.dromara.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.vo.SysDeptVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -29,12 +32,41 @@
    })
    List<SysDeptVo> selectDeptList(@Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
    /**
     * åˆ†é¡µæŸ¥è¯¢éƒ¨é—¨ç®¡ç†æ•°æ®
     *
     * @param queryWrapper æŸ¥è¯¢æ¡ä»¶
     * @return éƒ¨é—¨ä¿¡æ¯é›†åˆ
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
    })
    Page<SysDeptVo> selectPageDeptList(@Param("page") Page<SysDeptVo> page, @Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
    /**
     * ç»Ÿè®¡æŒ‡å®šéƒ¨é—¨ID的部门数量
     *
     * @param deptId éƒ¨é—¨ID
     * @return è¯¥éƒ¨é—¨ID的部门数量
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id")
    })
    long countDeptById(Long deptId);
    /**
     * æ ¹æ®çˆ¶éƒ¨é—¨ID查询其所有子部门的列表
     *
     * @param parentId çˆ¶éƒ¨é—¨ID
     * @return åŒ…含子部门的列表
     */
    default List<SysDept> selectListByParentId(Long parentId) {
        return this.selectList(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptId)
            .apply(DataBaseHelper.findInSet(parentId, "ancestors")));
    }
    /**
     * æ ¹æ®è§’色ID查询部门树信息
     *
     * @param roleId            è§’色ID
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java
@@ -14,6 +14,12 @@
 */
public interface SysDictDataMapper extends BaseMapperPlus<SysDictData, SysDictDataVo> {
    /**
     * æ ¹æ®å­—典类型查询字典数据列表
     *
     * @param dictType å­—典类型
     * @return ç¬¦åˆæ¡ä»¶çš„字典数据列表
     */
    default List<SysDictDataVo> selectDictDataByType(String dictType) {
        return selectVoList(
            new LambdaQueryWrapper<SysDictData>()
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java
@@ -3,11 +3,11 @@
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.system.domain.SysMenu;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.vo.SysMenuVo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysMenu;
import org.dromara.system.domain.vo.SysMenuVo;
import java.util.List;
@@ -49,8 +49,8 @@
     */
    default List<SysMenu> selectMenuTreeAll() {
        LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<SysMenu>()
            .in(SysMenu::getMenuType, UserConstants.TYPE_DIR, UserConstants.TYPE_MENU)
            .eq(SysMenu::getStatus, UserConstants.MENU_NORMAL)
            .in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
            .eq(SysMenu::getStatus, SystemConstants.NORMAL)
            .orderByAsc(SysMenu::getParentId)
            .orderByAsc(SysMenu::getOrderNum);
        return this.selectList(lqw);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java
@@ -19,6 +19,13 @@
 */
public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
    /**
     * åˆ†é¡µæŸ¥è¯¢å²—位列表
     *
     * @param page         åˆ†é¡µå¯¹è±¡
     * @param queryWrapper æŸ¥è¯¢æ¡ä»¶
     * @return åŒ…含岗位信息的分页结果
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "create_by")
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java
@@ -19,6 +19,13 @@
 */
public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo> {
    /**
     * åˆ†é¡µæŸ¥è¯¢è§’色列表
     *
     * @param page         åˆ†é¡µå¯¹è±¡
     * @param queryWrapper æŸ¥è¯¢æ¡ä»¶
     * @return åŒ…含角色信息的分页结果
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "d.dept_id"),
        @DataColumn(key = "userName", value = "r.create_by")
@@ -37,6 +44,12 @@
    })
    List<SysRoleVo> selectRoleList(@Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
    /**
     * æ ¹æ®è§’色ID查询角色信息
     *
     * @param roleId è§’色ID
     * @return å¯¹åº”的角色信息
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "d.dept_id"),
        @DataColumn(key = "userName", value = "r.create_by")
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java
@@ -20,12 +20,25 @@
 */
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
    /**
     * åˆ†é¡µæŸ¥è¯¢ç”¨æˆ·åˆ—表,并进行数据权限控制
     *
     * @param page         åˆ†é¡µå‚æ•°
     * @param queryWrapper æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µçš„用户信息
     */
    @DataPermission({
        @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);
    /**
     * æŸ¥è¯¢ç”¨æˆ·åˆ—表,并进行数据权限控制
     *
     * @param queryWrapper æŸ¥è¯¢æ¡ä»¶
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆ
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
@@ -68,12 +81,25 @@
    })
    Page<SysUserVo> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
    /**
     * æ ¹æ®ç”¨æˆ·ID统计用户数量
     *
     * @param userId ç”¨æˆ·ID
     * @return ç”¨æˆ·æ•°é‡
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    long countUserById(Long userId);
    /**
     * æ ¹æ®æ¡ä»¶æ›´æ–°ç”¨æˆ·æ•°æ®
     *
     * @param user          è¦æ›´æ–°çš„用户实体
     * @param updateWrapper æ›´æ–°æ¡ä»¶å°è£…器
     * @return æ›´æ–°æ“ä½œå½±å“çš„行数
     */
    @Override
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
@@ -81,6 +107,12 @@
    })
    int update(@Param(Constants.ENTITY) SysUser user, @Param(Constants.WRAPPER) Wrapper<SysUser> updateWrapper);
    /**
     * æ ¹æ®ç”¨æˆ·ID更新用户数据
     *
     * @param user è¦æ›´æ–°çš„用户实体
     * @return æ›´æ–°æ“ä½œå½±å“çš„行数
     */
    @Override
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java
@@ -12,6 +12,12 @@
 */
public interface SysUserRoleMapper extends BaseMapperPlus<SysUserRole, SysUserRole> {
    /**
     * æ ¹æ®è§’色ID查询关联的用户ID列表
     *
     * @param roleId è§’色ID
     * @return å…³è”到指定角色的用户ID列表
     */
    List<Long> selectUserIdsByRoleId(Long roleId);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
@@ -50,7 +50,7 @@
    /**
     * ä¿®æ”¹çŠ¶æ€
     */
    int updateUserStatus(String clientId, String status);
    int updateClientStatus(String clientId, String status);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤å®¢æˆ·ç«¯ç®¡ç†ä¿¡æ¯
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java
@@ -26,6 +26,14 @@
    List<SysPostVo> selectPostList(SysPostBo post);
    /**
     * æŸ¥è¯¢ç”¨æˆ·æ‰€å±žå²—位组
     *
     * @param userId ç”¨æˆ·ID
     * @return å²—位ID
     */
    List<SysPostVo> selectPostsByUserId(Long userId);
    /**
     * æŸ¥è¯¢æ‰€æœ‰å²—位
     *
     * @return å²—位列表
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java
@@ -197,4 +197,6 @@
    void cleanOnlineUserByRole(Long roleId);
    void cleanOnlineUser(List<Long> userIds);
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java
@@ -79,4 +79,9 @@
     * åŒæ­¥ç§Ÿæˆ·å¥—餐
     */
    Boolean syncTenantPackage(String tenantId, Long packageId);
    /**
     * åŒæ­¥ç§Ÿæˆ·å­—å…¸
     */
    void syncTenantDict();
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
@@ -123,7 +123,7 @@
     */
    @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
    @Override
    public int updateUserStatus(String clientId, String status) {
    public int updateClientStatus(String clientId, String status) {
        return baseMapper.update(null,
            new LambdaUpdateWrapper<SysClient>()
                .set(SysClient::getStatus, status)
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
@@ -7,10 +7,11 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.ConfigService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -72,10 +73,7 @@
    public String selectConfigByKey(String configKey) {
        SysConfig retConfig = baseMapper.selectOne(new LambdaQueryWrapper<SysConfig>()
            .eq(SysConfig::getConfigKey, configKey));
        if (ObjectUtil.isNotNull(retConfig)) {
            return retConfig.getConfigValue();
        }
        return StringUtils.EMPTY;
        return ObjectUtils.notNullGetter(retConfig, SysConfig::getConfigValue, StringUtils.EMPTY);
    }
    /**
@@ -173,7 +171,7 @@
    public void deleteConfigByIds(Long[] configIds) {
        for (Long configId : configIds) {
            SysConfig config = baseMapper.selectById(configId);
            if (StringUtils.equals(UserConstants.YES, config.getConfigType())) {
            if (StringUtils.equals(SystemConstants.YES, config.getConfigType())) {
                throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
            }
            CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
@@ -197,7 +195,7 @@
     */
    @Override
    public boolean checkConfigKeyUnique(SysConfigBo config) {
        long configId = ObjectUtil.isNull(config.getConfigId()) ? -1L : config.getConfigId();
        long configId = ObjectUtils.notNull(config.getConfigId(), -1L);
        SysConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, config.getConfigKey()));
        if (ObjectUtil.isNotNull(info) && info.getConfigId() != configId) {
            return false;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
@@ -5,13 +5,14 @@
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysRoleDept;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysRoleDeptMapper;
import org.dromara.system.service.ISysDataScopeService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -37,6 +38,7 @@
     * @param roleId è§’色Id
     * @return éƒ¨é—¨Id组
     */
    @Cacheable(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId", condition = "#roleId != null")
    @Override
    public String getRoleCustom(Long roleId) {
        if (ObjectUtil.isNull(roleId)) {
@@ -58,14 +60,13 @@
     * @param deptId éƒ¨é—¨Id
     * @return éƒ¨é—¨Id组
     */
    @Cacheable(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId", condition = "#deptId != null")
    @Override
    public String getDeptAndChild(Long deptId) {
        if (ObjectUtil.isNull(deptId)) {
            return "-1";
        }
        List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptId)
            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
        List<SysDept> deptList = deptMapper.selectListByParentId(deptId);
        List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
        ids.add(deptId);
        if (CollUtil.isNotEmpty(ids)) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
@@ -1,5 +1,6 @@
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.lang.tree.Tree;
@@ -9,13 +10,11 @@
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.constant.SystemConstants;
import org.dromara.common.core.domain.dto.DeptDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DeptService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.TreeBuildUtils;
import org.dromara.common.core.utils.*;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -30,7 +29,9 @@
import org.dromara.system.service.ISysDeptService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,8 +70,6 @@
     */
    @Override
    public List<Tree<Long>> selectDeptTreeList(SysDeptBo bo) {
        // åªæŸ¥è¯¢æœªç¦ç”¨éƒ¨é—¨
        bo.setStatus(UserConstants.DEPT_NORMAL);
        LambdaQueryWrapper<SysDept> lqw = buildQueryWrapper(bo);
        List<SysDeptVo> depts = baseMapper.selectDeptList(lqw);
        return buildDeptTreeSelect(depts);
@@ -78,7 +77,7 @@
    private LambdaQueryWrapper<SysDept> buildQueryWrapper(SysDeptBo bo) {
        LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery();
        lqw.eq(SysDept::getDelFlag, UserConstants.DEL_FLAG_NORMAL);
        lqw.eq(SysDept::getDelFlag, SystemConstants.NORMAL);
        lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId());
        lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId());
        lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName());
@@ -102,11 +101,23 @@
        if (CollUtil.isEmpty(depts)) {
            return CollUtil.newArrayList();
        }
        return TreeBuildUtils.build(depts, (dept, tree) ->
            tree.setId(dept.getDeptId())
                .setParentId(dept.getParentId())
                .setName(dept.getDeptName())
                .setWeight(dept.getOrderNum()));
        // èŽ·å–å½“å‰åˆ—è¡¨ä¸­æ¯ä¸€ä¸ªèŠ‚ç‚¹çš„parentId,然后在列表中查找是否有id与其parentId对应,若无对应,则表明此时节点列表中,该节点在当前列表中属于顶级节点
        List<Tree<Long>> treeList = CollUtil.newArrayList();
        for (SysDeptVo d : depts) {
            Long parentId = d.getParentId();
            SysDeptVo sysDeptVo = StreamUtils.findFirst(depts, it -> it.getDeptId().longValue() == parentId);
            if (ObjectUtil.isNull(sysDeptVo)) {
                List<Tree<Long>> trees = TreeBuildUtils.build(depts, parentId, (dept, tree) ->
                    tree.setId(dept.getDeptId())
                        .setParentId(dept.getParentId())
                        .setName(dept.getDeptName())
                        .setWeight(dept.getOrderNum())
                        .putExtra("disabled", SystemConstants.DISABLE.equals(dept.getStatus())));
                Tree<Long> tree = StreamUtils.findFirst(trees, it -> it.getId().longValue() == d.getDeptId());
                treeList.add(tree);
            }
        }
        return treeList;
    }
    /**
@@ -136,7 +147,7 @@
        }
        SysDeptVo parentDept = baseMapper.selectVoOne(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptName).eq(SysDept::getDeptId, dept.getParentId()));
        dept.setParentName(ObjectUtil.isNotNull(parentDept) ? parentDept.getDeptName() : null);
        dept.setParentName(ObjectUtils.notNullGetter(parentDept, SysDeptVo::getDeptName));
        return dept;
    }
@@ -144,7 +155,7 @@
    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)
            .eq(SysDept::getStatus, SystemConstants.NORMAL)
            .in(CollUtil.isNotEmpty(deptIds), SysDept::getDeptId, deptIds));
    }
@@ -167,6 +178,31 @@
    }
    /**
     * æ ¹æ®éƒ¨é—¨ID查询部门负责人
     *
     * @param deptId éƒ¨é—¨ID,用于指定需要查询的部门
     * @return è¿”回该部门的负责人ID
     */
    @Override
    public Long selectDeptLeaderById(Long deptId) {
        SysDeptVo vo = SpringUtils.getAopProxy(this).selectDeptById(deptId);
        return vo.getLeader();
    }
    /**
     * æŸ¥è¯¢éƒ¨é—¨
     *
     * @return éƒ¨é—¨åˆ—表
     */
    @Override
    public List<DeptDTO> selectDeptsByList() {
        List<SysDeptVo> list = baseMapper.selectDeptList(new LambdaQueryWrapper<SysDept>()
            .select(SysDept::getDeptId, SysDept::getDeptName, SysDept::getParentId)
            .eq(SysDept::getStatus, SystemConstants.NORMAL));
        return BeanUtil.copyToList(list, DeptDTO.class);
    }
    /**
     * æ ¹æ®ID查询所有子部门数(正常状态)
     *
     * @param deptId éƒ¨é—¨ID
@@ -175,7 +211,7 @@
    @Override
    public long selectNormalChildrenDeptById(Long deptId) {
        return baseMapper.selectCount(new LambdaQueryWrapper<SysDept>()
            .eq(SysDept::getStatus, UserConstants.DEPT_NORMAL)
            .eq(SysDept::getStatus, SystemConstants.NORMAL)
            .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
    }
@@ -242,11 +278,12 @@
     * @param bo éƒ¨é—¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, allEntries = true)
    @Override
    public int insertDept(SysDeptBo bo) {
        SysDept info = baseMapper.selectById(bo.getParentId());
        // å¦‚果父节点不为正常状态,则不允许新增子节点
        if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) {
        if (!SystemConstants.NORMAL.equals(info.getStatus())) {
            throw new ServiceException("部门停用,不允许新增");
        }
        SysDept dept = MapstructUtils.convert(bo, SysDept.class);
@@ -260,25 +297,34 @@
     * @param bo éƒ¨é—¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#bo.deptId")
    @Caching(evict = {
        @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#bo.deptId"),
        @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, allEntries = true)
    })
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateDept(SysDeptBo bo) {
        SysDept dept = MapstructUtils.convert(bo, SysDept.class);
        SysDept oldDept = baseMapper.selectById(dept.getDeptId());
        if (ObjectUtil.isNull(oldDept)) {
            throw new ServiceException("部门不存在,无法修改");
        }
        if (!oldDept.getParentId().equals(dept.getParentId())) {
            // å¦‚果是新父部门 åˆ™æ ¡éªŒæ˜¯å¦å…·æœ‰æ–°çˆ¶éƒ¨é—¨æƒé™ é¿å…è¶Šæƒ
            this.checkDeptDataScope(dept.getParentId());
            SysDept newParentDept = baseMapper.selectById(dept.getParentId());
            if (ObjectUtil.isNotNull(newParentDept) && ObjectUtil.isNotNull(oldDept)) {
            if (ObjectUtil.isNotNull(newParentDept)) {
                String newAncestors = newParentDept.getAncestors() + StringUtils.SEPARATOR + newParentDept.getDeptId();
                String oldAncestors = oldDept.getAncestors();
                dept.setAncestors(newAncestors);
                updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
            }
        } else {
            dept.setAncestors(oldDept.getAncestors());
        }
        int result = baseMapper.updateById(dept);
        if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
            && !StringUtils.equals(UserConstants.DEPT_NORMAL, dept.getAncestors())) {
        if (SystemConstants.NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
            && !StringUtils.equals(SystemConstants.NORMAL, dept.getAncestors())) {
            // å¦‚果该部门是启用状态,则启用该部门的所有上级部门
            updateParentDeptStatusNormal(dept);
        }
@@ -294,7 +340,7 @@
        String ancestors = dept.getAncestors();
        Long[] deptIds = Convert.toLongArray(ancestors);
        baseMapper.update(null, new LambdaUpdateWrapper<SysDept>()
            .set(SysDept::getStatus, UserConstants.DEPT_NORMAL)
            .set(SysDept::getStatus, SystemConstants.NORMAL)
            .in(SysDept::getDeptId, Arrays.asList(deptIds)));
    }
@@ -328,7 +374,10 @@
     * @param deptId éƒ¨é—¨ID
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#deptId")
    @Caching(evict = {
        @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#deptId"),
        @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId")
    })
    @Override
    public int deleteDeptById(Long deptId) {
        return baseMapper.deleteById(deptId);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.system.domain.SysDictData;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -144,7 +145,7 @@
     */
    @Override
    public boolean checkDictDataUnique(SysDictDataBo dict) {
        Long dictCode = ObjectUtil.isNull(dict.getDictCode()) ? -1L : dict.getDictCode();
        Long dictCode = ObjectUtils.notNull(dict.getDictCode(), -1L);
        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())) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java
@@ -118,8 +118,7 @@
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                SysLogininfor::getLoginTime, params.get("beginTime"), params.get("endTime"));
        if (StringUtils.isBlank(pageQuery.getOrderByColumn())) {
            pageQuery.setOrderByColumn("info_id");
            pageQuery.setIsAsc("desc");
            lqw.orderByDesc(SysLogininfor::getInfoId);
        }
        Page<SysLogininforVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(page);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java
@@ -8,7 +8,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -196,7 +196,7 @@
            router.setQuery(menu.getQueryParam());
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
            if (CollUtil.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
            if (CollUtil.isNotEmpty(cMenus) && SystemConstants.TYPE_DIR.equals(menu.getMenuType())) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
@@ -220,7 +220,7 @@
                String routerPath = SysMenu.innerLinkReplaceEach(menu.getPath());
                String innerLinkName = StringUtils.capitalize(routerPath) + menu.getMenuId();
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setComponent(SystemConstants.INNER_LINK);
                children.setName(innerLinkName);
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
@@ -242,11 +242,14 @@
        if (CollUtil.isEmpty(menus)) {
            return CollUtil.newArrayList();
        }
        return TreeBuildUtils.build(menus, (menu, tree) ->
            tree.setId(menu.getMenuId())
        return TreeBuildUtils.build(menus, (menu, tree) -> {
            Tree<Long> menuTree = tree.setId(menu.getMenuId())
                .setParentId(menu.getParentId())
                .setName(menu.getMenuName())
                .setWeight(menu.getOrderNum()));
                .setWeight(menu.getOrderNum());
            menuTree.put("menuType", menu.getMenuType());
            menuTree.put("icon", menu.getIcon());
        });
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java
@@ -1,10 +1,10 @@
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;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -70,7 +70,7 @@
        lqw.eq(StringUtils.isNotBlank(bo.getNoticeType()), SysNotice::getNoticeType, bo.getNoticeType());
        if (StringUtils.isNotBlank(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.eq(SysNotice::getCreateBy, ObjectUtils.notNullGetter(sysUser, SysUserVo::getUserId));
        }
        lqw.orderByAsc(SysNotice::getNoticeId);
        return lqw;
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java
@@ -3,18 +3,18 @@
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysOperLog;
import org.dromara.system.domain.bo.SysOperLogBo;
import org.dromara.system.domain.vo.SysOperLogVo;
import org.dromara.system.mapper.SysOperLogMapper;
import org.dromara.system.service.ISysOperLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@@ -51,8 +51,17 @@
    @Override
    public TableDataInfo<SysOperLogVo> selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOperLog> lqw = buildQueryWrapper(operLog);
        if (StringUtils.isBlank(pageQuery.getOrderByColumn())) {
            lqw.orderByDesc(SysOperLog::getOperId);
        }
        Page<SysOperLogVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(page);
    }
    private LambdaQueryWrapper<SysOperLog> buildQueryWrapper(SysOperLogBo operLog) {
        Map<String, Object> params = operLog.getParams();
        LambdaQueryWrapper<SysOperLog> lqw = new LambdaQueryWrapper<SysOperLog>()
        return new LambdaQueryWrapper<SysOperLog>()
            .like(StringUtils.isNotBlank(operLog.getOperIp()), SysOperLog::getOperIp, operLog.getOperIp())
            .like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
            .eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
@@ -67,12 +76,6 @@
            .like(StringUtils.isNotBlank(operLog.getOperName()), SysOperLog::getOperName, operLog.getOperName())
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                SysOperLog::getOperTime, params.get("beginTime"), params.get("endTime"));
        if (StringUtils.isBlank(pageQuery.getOrderByColumn())) {
            pageQuery.setOrderByColumn("oper_id");
            pageQuery.setIsAsc("desc");
        }
        Page<SysOperLogVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(page);
    }
    /**
@@ -95,23 +98,8 @@
     */
    @Override
    public List<SysOperLogVo> selectOperLogList(SysOperLogBo operLog) {
        Map<String, Object> params = operLog.getParams();
        return baseMapper.selectVoList(new LambdaQueryWrapper<SysOperLog>()
            .like(StringUtils.isNotBlank(operLog.getOperIp()), SysOperLog::getOperIp, operLog.getOperIp())
            .like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
            .eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
                SysOperLog::getBusinessType, operLog.getBusinessType())
            .func(f -> {
                if (ArrayUtil.isNotEmpty(operLog.getBusinessTypes())) {
                    f.in(SysOperLog::getBusinessType, Arrays.asList(operLog.getBusinessTypes()));
                }
            })
            .eq(operLog.getStatus() != null && operLog.getStatus() > 0,
                SysOperLog::getStatus, operLog.getStatus())
            .like(StringUtils.isNotBlank(operLog.getOperName()), SysOperLog::getOperName, operLog.getOperName())
            .between(params.get("beginTime") != null && params.get("endTime") != null,
                SysOperLog::getOperTime, params.get("beginTime"), params.get("endTime"))
            .orderByDesc(SysOperLog::getOperId));
        LambdaQueryWrapper<SysOperLog> lqw = buildQueryWrapper(operLog);
        return baseMapper.selectVoList(lqw.orderByDesc(SysOperLog::getOperId));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java
@@ -11,6 +11,7 @@
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -147,7 +148,7 @@
     * åˆ¤æ–­configKey是否唯一
     */
    private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) {
        long ossConfigId = ObjectUtil.isNull(sysOssConfig.getOssConfigId()) ? -1L : sysOssConfig.getOssConfigId();
        long ossConfigId = ObjectUtils.notNull(sysOssConfig.getOssConfigId(), -1L);
        SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysOssConfig>()
            .select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey)
            .eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey()));
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -36,6 +36,7 @@
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -177,8 +178,7 @@
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        OssClient storage = OssFactory.instance(sysOss.getService());
        long contentLength = storage.download(sysOss.getFileName(), response.getOutputStream());
        response.setContentLengthLong(contentLength);
        storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong);
    }
    /**
@@ -195,7 +195,7 @@
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        try {
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix);
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
@@ -244,7 +244,7 @@
        if (isValid) {
            // åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        List<SysOss> list = baseMapper.selectBatchIds(ids);
        List<SysOss> list = baseMapper.selectByIds(ids);
        for (SysOss sysOss : list) {
            OssClient storage = OssFactory.instance(sysOss.getService());
            storage.delete(sysOss.getUrl());
@@ -262,7 +262,7 @@
        OssClient storage = OssFactory.instance(oss.getService());
        // ä»…修改桶类型为 private çš„URL,临时URL时长为120s
        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120));
            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
        }
        return oss;
    }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
@@ -6,14 +6,14 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.PostService;
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;
@@ -27,7 +27,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
 * å²—位信息 æœåŠ¡å±‚å¤„ç†
@@ -36,7 +35,7 @@
 */
@RequiredArgsConstructor
@Service
public class SysPostServiceImpl implements ISysPostService {
public class SysPostServiceImpl implements ISysPostService, PostService {
    private final SysPostMapper baseMapper;
    private final SysDeptMapper deptMapper;
@@ -60,6 +59,17 @@
    }
    /**
     * æŸ¥è¯¢ç”¨æˆ·æ‰€å±žå²—位组
     *
     * @param userId ç”¨æˆ·ID
     * @return å²—位ID
     */
    @Override
    public List<SysPostVo> selectPostsByUserId(Long userId) {
        return baseMapper.selectPostsByUserId(userId);
    }
    /**
     * æ ¹æ®æŸ¥è¯¢æ¡ä»¶æž„建查询包装器
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶å¯¹è±¡
@@ -78,12 +88,8 @@
        } 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());
                List<SysDept> deptList = deptMapper.selectListByParentId(bo.getBelongDeptId());
                List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
                deptIds.add(bo.getBelongDeptId());
                x.in(SysPost::getDeptId, deptIds);
            });
@@ -134,7 +140,7 @@
    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)
            .eq(SysPost::getStatus, SystemConstants.NORMAL)
            .in(CollUtil.isNotEmpty(postIds), SysPost::getPostId, postIds));
    }
@@ -239,4 +245,5 @@
        SysPost post = MapstructUtils.convert(bo, SysPost.class);
        return baseMapper.updateById(post);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -12,10 +12,12 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.RoleService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -33,6 +35,7 @@
import org.dromara.system.mapper.SysRoleMenuMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import org.dromara.system.service.ISysRoleService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -45,7 +48,7 @@
 */
@RequiredArgsConstructor
@Service
public class SysRoleServiceImpl implements ISysRoleService {
public class SysRoleServiceImpl implements ISysRoleService, RoleService {
    private final SysRoleMapper baseMapper;
    private final SysRoleMenuMapper roleMenuMapper;
@@ -72,7 +75,7 @@
    private Wrapper<SysRole> buildQueryWrapper(SysRoleBo bo) {
        Map<String, Object> params = bo.getParams();
        QueryWrapper<SysRole> wrapper = Wrappers.query();
        wrapper.eq("r.del_flag", UserConstants.ROLE_NORMAL)
        wrapper.eq("r.del_flag", SystemConstants.NORMAL)
            .eq(ObjectUtil.isNotNull(bo.getRoleId()), "r.role_id", bo.getRoleId())
            .like(StringUtils.isNotBlank(bo.getRoleName()), "r.role_name", bo.getRoleName())
            .eq(StringUtils.isNotBlank(bo.getStatus()), "r.status", bo.getStatus())
@@ -174,7 +177,7 @@
    @Override
    public List<SysRoleVo> selectRoleByIds(List<Long> roleIds) {
        return baseMapper.selectRoleList(new QueryWrapper<SysRole>()
            .eq("r.status", UserConstants.ROLE_NORMAL)
            .eq("r.status", SystemConstants.NORMAL)
            .in(CollUtil.isNotEmpty(roleIds), "r.role_id", roleIds));
    }
@@ -294,7 +297,7 @@
    public int updateRole(SysRoleBo bo) {
        SysRole role = MapstructUtils.convert(bo, SysRole.class);
        if (UserConstants.ROLE_DISABLE.equals(role.getStatus()) && this.countUserRoleByRoleId(role.getRoleId()) > 0) {
        if (SystemConstants.DISABLE.equals(role.getStatus()) && this.countUserRoleByRoleId(role.getRoleId()) > 0) {
            throw new ServiceException("角色已分配,不能禁用!");
        }
        // ä¿®æ”¹è§’色信息
@@ -313,7 +316,7 @@
     */
    @Override
    public int updateRoleStatus(Long roleId, String status) {
        if (UserConstants.ROLE_DISABLE.equals(status) && this.countUserRoleByRoleId(roleId) > 0) {
        if (SystemConstants.DISABLE.equals(status) && this.countUserRoleByRoleId(roleId) > 0) {
            throw new ServiceException("角色已分配,不能禁用!");
        }
        return baseMapper.update(null,
@@ -328,6 +331,7 @@
     * @param bo è§’色信息
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#bo.roleId")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int authDataScope(SysRoleBo bo) {
@@ -348,7 +352,7 @@
    private int insertRoleMenu(SysRoleBo role) {
        int rows = 1;
        // æ–°å¢žç”¨æˆ·ä¸Žè§’色管理
        List<SysRoleMenu> list = new ArrayList<SysRoleMenu>();
        List<SysRoleMenu> list = new ArrayList<>();
        for (Long menuId : role.getMenuIds()) {
            SysRoleMenu rm = new SysRoleMenu();
            rm.setRoleId(role.getRoleId());
@@ -369,7 +373,7 @@
    private int insertRoleDept(SysRoleBo role) {
        int rows = 1;
        // æ–°å¢žè§’色与部门(数据权限)管理
        List<SysRoleDept> list = new ArrayList<SysRoleDept>();
        List<SysRoleDept> list = new ArrayList<>();
        for (Long deptId : role.getDeptIds()) {
            SysRoleDept rd = new SysRoleDept();
            rd.setRoleId(role.getRoleId());
@@ -388,6 +392,7 @@
     * @param roleId è§’色ID
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteRoleById(Long roleId) {
@@ -404,6 +409,7 @@
     * @param roleIds éœ€è¦åˆ é™¤çš„角色ID
     * @return ç»“æžœ
     */
    @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, allEntries = true)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteRoleByIds(Long[] roleIds) {
@@ -435,7 +441,7 @@
            .eq(SysUserRole::getRoleId, userRole.getRoleId())
            .eq(SysUserRole::getUserId, userRole.getUserId()));
        if (rows > 0) {
            cleanOnlineUserByRole(userRole.getRoleId());
            cleanOnlineUser(List.of(userRole.getUserId()));
        }
        return rows;
    }
@@ -449,11 +455,12 @@
     */
    @Override
    public int deleteAuthUsers(Long roleId, Long[] userIds) {
        List<Long> ids = List.of(userIds);
        int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
            .eq(SysUserRole::getRoleId, roleId)
            .in(SysUserRole::getUserId, Arrays.asList(userIds)));
            .in(SysUserRole::getUserId, ids));
        if (rows > 0) {
            cleanOnlineUserByRole(roleId);
            cleanOnlineUser(ids);
        }
        return rows;
    }
@@ -469,7 +476,8 @@
    public int insertAuthUsers(Long roleId, Long[] userIds) {
        // æ–°å¢žç”¨æˆ·ä¸Žè§’色管理
        int rows = 1;
        List<SysUserRole> list = StreamUtils.toList(List.of(userIds), userId -> {
        List<Long> ids = List.of(userIds);
        List<SysUserRole> list = StreamUtils.toList(ids, userId -> {
            SysUserRole ur = new SysUserRole();
            ur.setUserId(userId);
            ur.setRoleId(roleId);
@@ -479,7 +487,7 @@
            rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
        }
        if (rows > 0) {
            cleanOnlineUserByRole(roleId);
            cleanOnlineUser(ids);
        }
        return rows;
    }
@@ -503,6 +511,9 @@
                return;
            }
            LoginUser loginUser = LoginHelper.getLoginUser(token);
            if (ObjectUtil.isNull(loginUser) || CollUtil.isEmpty(loginUser.getRoles())) {
                return;
            }
            if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) {
                try {
                    StpUtil.logoutByTokenValue(token);
@@ -511,4 +522,31 @@
            }
        });
    }
    @Override
    public void cleanOnlineUser(List<Long> userIds) {
        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
        if (CollUtil.isEmpty(keys)) {
            return;
        }
        // è§’色关联的在线用户量过大会导致redis阻塞卡顿 è°¨æ…Žæ“ä½œ
        keys.parallelStream().forEach(key -> {
            String token = StringUtils.substringAfterLast(key, ":");
            // å¦‚果已经过期则跳过
            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
                return;
            }
            LoginUser loginUser = LoginHelper.getLoginUser(token);
            if (ObjectUtil.isNull(loginUser)) {
                return;
            }
            if (userIds.contains(loginUser.getUserId())) {
                try {
                    StpUtil.logoutByTokenValue(token);
                } catch (NotLoginException ignored) {
                }
            }
        });
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java
@@ -1,7 +1,7 @@
package org.dromara.system.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import org.dromara.common.core.utils.StringUtils;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.sensitive.core.SensitiveService;
import org.dromara.common.tenant.helper.TenantHelper;
@@ -22,19 +22,19 @@
     * æ˜¯å¦è„±æ•
     */
    @Override
    public boolean isSensitive(String roleKey, String perms) {
    public boolean isSensitive(String[] roleKey, String[] perms) {
        if (!LoginHelper.isLogin()) {
            return true;
        }
        boolean roleExist = StringUtils.isNotBlank(roleKey);
        boolean permsExist = StringUtils.isNotBlank(perms);
        boolean roleExist = ArrayUtil.isNotEmpty(roleKey);
        boolean permsExist = ArrayUtil.isNotEmpty(perms);
        if (roleExist && permsExist) {
            if (StpUtil.hasRole(roleKey) && StpUtil.hasPermission(perms)) {
            if (StpUtil.hasRoleOr(roleKey) && StpUtil.hasPermissionOr(perms)) {
                return false;
            }
        } else if (roleExist && StpUtil.hasRole(roleKey)) {
        } else if (roleExist && StpUtil.hasRoleOr(roleKey)) {
            return false;
        } else if (permsExist && StpUtil.hasPermission(perms)) {
        } else if (permsExist && StpUtil.hasPermissionOr(perms)) {
            return false;
        }
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
package org.dromara.system.service.impl;
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.SystemConstants;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
import org.dromara.common.core.service.TaskAssigneeService;
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.system.domain.SysDept;
import org.dromara.system.domain.SysPost;
import org.dromara.system.domain.SysRole;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysPostMapper;
import org.dromara.system.mapper.SysRoleMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * å·¥ä½œæµè®¾è®¡å™¨èŽ·å–ä»»åŠ¡æ‰§è¡Œäºº
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Service
public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
    private final SysPostMapper postMapper;
    private final SysDeptMapper deptMapper;
    private final SysUserMapper userMapper;
    private final SysRoleMapper roleMapper;
    /**
     * æŸ¥è¯¢è§’色并返回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    @Override
    public TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery) {
        PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
        QueryWrapper<SysRole> wrapper = Wrappers.query();
        wrapper.eq("r.del_flag", SystemConstants.NORMAL)
            .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), "r.role_name", taskQuery.getHandlerCode())
            .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), "r.role_key", taskQuery.getHandlerName())
            .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
                "r.create_time", taskQuery.getBeginTime(), taskQuery.getEndTime())
            .orderByAsc("r.role_sort").orderByAsc("r.create_time");
        Page<SysRoleVo> page = roleMapper.selectPageRoleList(pageQuery.build(), wrapper);
        // ä½¿ç”¨å°è£…的字段映射方法进行转换
        List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRecords(),
            SysRoleVo::getRoleId, SysRoleVo::getRoleKey, SysRoleVo::getRoleName, null, SysRoleVo::getCreateTime);
        return new TaskAssigneeDTO(page.getTotal(), handlers);
    }
    /**
     * æŸ¥è¯¢å²—位并返回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    @Override
    public TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery) {
        PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
        LambdaQueryWrapper<SysPost> wrapper = Wrappers.<SysPost>lambdaQuery()
            .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), SysPost::getPostCategory, taskQuery.getHandlerCode())
            .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), SysPost::getPostName, taskQuery.getHandlerName())
            .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
                SysPost::getCreateTime, taskQuery.getBeginTime(), taskQuery.getEndTime());
        if (StringUtils.isNotBlank(taskQuery.getGroupId())) {
            Long belongDeptId = Long.valueOf(taskQuery.getGroupId());
            wrapper.and(x -> {
                List<SysDept> deptList = deptMapper.selectListByParentId(belongDeptId);
                List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
                deptIds.add(belongDeptId);
                x.in(SysPost::getDeptId, deptIds);
            });
        }
        Page<SysPostVo> page = postMapper.selectPagePostList(pageQuery.build(), wrapper);
        // ä½¿ç”¨å°è£…的字段映射方法进行转换
        List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRecords(),
            SysPostVo::getPostId, SysPostVo::getPostCategory, SysPostVo::getPostName, SysPostVo::getDeptId, SysPostVo::getCreateTime);
        return new TaskAssigneeDTO(page.getTotal(), handlers);
    }
    /**
     * æŸ¥è¯¢éƒ¨é—¨å¹¶è¿”回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    @Override
    public TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery) {
        PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
        LambdaQueryWrapper<SysDept> wrapper = Wrappers.<SysDept>lambdaQuery()
            .eq(SysDept::getDelFlag, SystemConstants.NORMAL)
            .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), SysDept::getDeptCategory, taskQuery.getHandlerCode())
            .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), SysDept::getDeptName, taskQuery.getHandlerName())
            .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
                SysDept::getCreateTime, taskQuery.getBeginTime(), taskQuery.getEndTime())
            .orderByAsc(SysDept::getAncestors)
            .orderByAsc(SysDept::getParentId)
            .orderByAsc(SysDept::getOrderNum)
            .orderByAsc(SysDept::getDeptId);
        if (StringUtils.isNotBlank(taskQuery.getGroupId())) {
            //部门树搜索
            wrapper.and(x -> {
                Long parentId = Long.valueOf(taskQuery.getGroupId());
                List<SysDept> deptList = deptMapper.selectListByParentId(parentId);
                List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
                deptIds.add(parentId);
                x.in(SysDept::getDeptId, deptIds);
            });
        }
        Page<SysDeptVo> page = deptMapper.selectPageDeptList(pageQuery.build(), wrapper);
        // ä½¿ç”¨å°è£…的字段映射方法进行转换
        List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRecords(),
            SysDeptVo::getDeptId, SysDeptVo::getDeptCategory, SysDeptVo::getDeptName, SysDeptVo::getParentId, SysDeptVo::getCreateTime);
        return new TaskAssigneeDTO(page.getTotal(), handlers);
    }
    /**
     * æŸ¥è¯¢ç”¨æˆ·å¹¶è¿”回任务指派的列表,支持分页
     *
     * @param taskQuery æŸ¥è¯¢æ¡ä»¶
     * @return åŠžç†äºº
     */
    @Override
    public TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery) {
        PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", SystemConstants.NORMAL)
            .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), "u.user_name", taskQuery.getHandlerCode())
            .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), "u.nick_name", taskQuery.getHandlerName())
            .between(taskQuery.getBeginTime() != null && taskQuery.getEndTime() != null,
                "u.create_time", taskQuery.getBeginTime(), taskQuery.getEndTime())
            .orderByAsc("u.user_id");
        if (StringUtils.isNotBlank(taskQuery.getGroupId())) {
            //部门树搜索
            wrapper.and(x -> {
                Long parentId = Long.valueOf(taskQuery.getGroupId());
                List<SysDept> deptList = deptMapper.selectListByParentId(parentId);
                List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
                deptIds.add(parentId);
                x.in("u.dept_id", deptIds);
            });
        }
        Page<SysUserVo> page = userMapper.selectPageUserList(pageQuery.build(), wrapper);
        // ä½¿ç”¨å°è£…的字段映射方法进行转换
        List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRecords(),
            SysUserVo::getUserId, SysUserVo::getUserName, SysUserVo::getNickName, SysUserVo::getDeptId, SysUserVo::getCreateTime);
        return new TaskAssigneeDTO(page.getTotal(), handlers);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java
@@ -6,7 +6,7 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -59,7 +59,7 @@
    @Override
    public List<SysTenantPackageVo> selectList() {
        return baseMapper.selectVoList(new LambdaQueryWrapper<SysTenantPackage>()
                .eq(SysTenantPackage::getStatus, TenantConstants.NORMAL));
                .eq(SysTenantPackage::getStatus, SystemConstants.NORMAL));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
@@ -1,6 +1,8 @@
package org.dromara.system.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
@@ -10,13 +12,19 @@
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.WorkflowService;
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;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.common.tenant.core.TenantEntity;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
@@ -27,10 +35,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.*;
/**
 * ç§Ÿæˆ·Service业务层处理
@@ -117,7 +122,9 @@
        // èŽ·å–æ‰€æœ‰ç§Ÿæˆ·ç¼–å·
        List<String> tenantIds = baseMapper.selectObjs(
            new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId), x -> {return Convert.toStr(x);});
            new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId), x -> {
                return Convert.toStr(x);
            });
        String tenantId = generateTenantId(tenantIds);
        add.setTenantId(tenantId);
        boolean flag = baseMapper.insert(add) > 0;
@@ -187,6 +194,13 @@
            config.setTenantId(tenantId);
        }
        configMapper.insertBatch(sysConfigList);
        // æœªå¼€å¯å·¥ä½œæµä¸æ‰§è¡Œä¸‹æ–¹æ“ä½œ
        if (SpringUtils.getProperty("warm-flow.enabled", Boolean.class, false)) {
            WorkflowService workflowService = SpringUtils.getBean(WorkflowService.class);
            // æ–°å¢žç§Ÿæˆ·æµç¨‹å®šä¹‰
            workflowService.syncDef(tenantId);
        }
        return true;
    }
@@ -201,7 +215,7 @@
        String numbers = RandomUtil.randomNumbers(6);
        // åˆ¤æ–­æ˜¯å¦å­˜åœ¨ï¼Œå¦‚果存在则重新生成
        if (tenantIds.contains(numbers)) {
            generateTenantId(tenantIds);
            return generateTenantId(tenantIds);
        }
        return numbers;
    }
@@ -228,7 +242,7 @@
        role.setRoleName(TenantConstants.TENANT_ADMIN_ROLE_NAME);
        role.setRoleKey(TenantConstants.TENANT_ADMIN_ROLE_KEY);
        role.setRoleSort(1);
        role.setStatus(TenantConstants.NORMAL);
        role.setStatus(SystemConstants.NORMAL);
        roleMapper.insert(role);
        Long roleId = role.getRoleId();
@@ -266,7 +280,9 @@
    @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId")
    @Override
    public int updateTenantStatus(SysTenantBo bo) {
        SysTenant tenant = MapstructUtils.convert(bo, SysTenant.class);
        SysTenant tenant = new SysTenant();
        tenant.setId(bo.getId());
        tenant.setStatus(bo.getStatus());
        return baseMapper.updateById(tenant);
    }
@@ -369,4 +385,93 @@
        }
        return true;
    }
    /**
     * åŒæ­¥ç§Ÿæˆ·å­—å…¸
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void syncTenantDict() {
        // æŸ¥è¯¢è¶…管 æ‰€æœ‰å­—典数据
        List<SysDictType> dictTypeList = new ArrayList<>();
        List<SysDictData> dictDataList = new ArrayList<>();
        TenantHelper.ignore(() -> {
            dictTypeList.addAll(dictTypeMapper.selectList());
            dictDataList.addAll(dictDataMapper.selectList());
        });
        Map<String, List<SysDictType>> typeMap = StreamUtils.groupByKey(dictTypeList, TenantEntity::getTenantId);
        Map<String, Map<String, List<SysDictData>>> typeDataMap = StreamUtils.groupBy2Key(
            dictDataList, TenantEntity::getTenantId, SysDictData::getDictType);
        // ç®¡ç†ç§Ÿæˆ·å­—典数据
        List<SysDictType> defaultTypeMap = typeMap.get(TenantConstants.DEFAULT_TENANT_ID);
        Map<String, List<SysDictData>> defaultTypeDataMap = typeDataMap.get(TenantConstants.DEFAULT_TENANT_ID);
        // èŽ·å–æ‰€æœ‰ç§Ÿæˆ·ç¼–å·
        List<String> tenantIds = baseMapper.selectObjs(
            new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId)
                .eq(SysTenant::getStatus, SystemConstants.NORMAL), x -> {
                return Convert.toStr(x);
            });
        List<SysDictType> saveTypeList = new ArrayList<>();
        List<SysDictData> saveDataList = new ArrayList<>();
        Set<String> set = new HashSet<>();
        for (String tenantId : tenantIds) {
            if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
                continue;
            }
            for (SysDictType dictType : defaultTypeMap) {
                List<String> typeList = StreamUtils.toList(typeMap.get(tenantId), SysDictType::getDictType);
                List<SysDictData> dataList = defaultTypeDataMap.get(dictType.getDictType());
                if (typeList.contains(dictType.getDictType())) {
                    List<SysDictData> dataListTenant = typeDataMap.get(tenantId).get(dictType.getDictType());
                    Map<String, SysDictData> map = StreamUtils.toIdentityMap(dataListTenant, SysDictData::getDictValue);
                    for (SysDictData dictData : dataList) {
                        if (!map.containsKey(dictData.getDictValue())) {
                            SysDictData data = BeanUtil.toBean(dictData, SysDictData.class);
                            // è®¾ç½®å­—典编码为 null
                            data.setDictCode(null);
                            data.setTenantId(tenantId);
                            data.setCreateTime(null);
                            data.setUpdateTime(null);
                            set.add(tenantId);
                            saveDataList.add(data);
                        }
                    }
                } else {
                    SysDictType type = BeanUtil.toBean(dictType, SysDictType.class);
                    type.setDictId(null);
                    type.setTenantId(tenantId);
                    type.setCreateTime(null);
                    type.setUpdateTime(null);
                    set.add(tenantId);
                    saveTypeList.add(type);
                    if (CollUtil.isNotEmpty(dataList)) {
                        // ç­›é€‰å‡º dictType å¯¹åº”çš„ data
                        for (SysDictData dictData : dataList) {
                            SysDictData data = BeanUtil.toBean(dictData, SysDictData.class);
                            // è®¾ç½®å­—典编码为 null
                            data.setDictCode(null);
                            data.setTenantId(tenantId);
                            data.setCreateTime(null);
                            data.setUpdateTime(null);
                            set.add(tenantId);
                            saveDataList.add(data);
                        }
                    }
                }
            }
        }
        TenantHelper.ignore(() -> {
            if (CollUtil.isNotEmpty(saveTypeList)) {
                dictTypeMapper.insertBatch(saveTypeList);
            }
            if (CollUtil.isNotEmpty(saveDataList)) {
                dictDataMapper.insertBatch(saveDataList);
            }
        });
        for (String tenantId : set) {
            TenantHelper.dynamic(tenantId, () -> CacheUtils.clear(CacheNames.SYS_DICT));
        }
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -14,17 +14,13 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.constant.SystemConstants;
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.core.utils.*;
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.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysUserBo;
@@ -42,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * ç”¨æˆ· ä¸šåŠ¡å±‚å¤„ç†
@@ -80,7 +77,7 @@
    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)
        wrapper.eq("u.del_flag", SystemConstants.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())
@@ -88,9 +85,7 @@
            .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<SysDept> deptList = deptMapper.selectListByParentId(user.getDeptId());
                List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
                ids.add(user.getDeptId());
                w.in("u.dept_id", ids);
@@ -110,7 +105,7 @@
    @Override
    public TableDataInfo<SysUserVo> selectAllocatedList(SysUserBo user, PageQuery pageQuery) {
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
        wrapper.eq("u.del_flag", SystemConstants.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())
@@ -130,7 +125,7 @@
    public TableDataInfo<SysUserVo> selectUnallocatedList(SysUserBo user, PageQuery pageQuery) {
        List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(user.getRoleId());
        QueryWrapper<SysUser> wrapper = Wrappers.query();
        wrapper.eq("u.del_flag", UserConstants.USER_NORMAL)
        wrapper.eq("u.del_flag", SystemConstants.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())
@@ -189,7 +184,7 @@
    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(SysUser::getStatus, SystemConstants.NORMAL)
            .eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId)
            .in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
    }
@@ -472,7 +467,7 @@
        if (ArrayUtil.isNotEmpty(roleIds)) {
            List<Long> roleList = new ArrayList<>(List.of(roleIds));
            if (!LoginHelper.isSuperAdmin(userId)) {
                roleList.remove(UserConstants.SUPER_ADMIN_ID);
                roleList.remove(SystemConstants.SUPER_ADMIN_ID);
            }
            // åˆ¤æ–­æ˜¯å¦å…·æœ‰æ­¤è§’色的操作权限
            List<SysRoleVo> roles = roleMapper.selectRoleList(
@@ -567,7 +562,7 @@
    public String selectUserNameById(Long userId) {
        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserName).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserName();
        return ObjectUtils.notNullGetter(sysUser, SysUser::getUserName);
    }
    /**
@@ -581,7 +576,7 @@
    public String selectNicknameById(Long userId) {
        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getNickName).eq(SysUser::getUserId, userId));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getNickName();
        return ObjectUtils.notNullGetter(sysUser, SysUser::getNickName);
    }
    /**
@@ -612,7 +607,7 @@
    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();
        return ObjectUtils.notNullGetter(sysUser, SysUser::getPhonenumber);
    }
    /**
@@ -625,9 +620,15 @@
    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();
        return ObjectUtils.notNullGetter(sysUser, SysUser::getEmail);
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID查询用户列表
     *
     * @param userIds ç”¨æˆ·ids
     * @return ç”¨æˆ·åˆ—表
     */
    @Override
    public List<UserDTO> selectListByIds(List<Long> userIds) {
        if (CollUtil.isEmpty(userIds)) {
@@ -635,29 +636,55 @@
        }
        List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
            .eq(SysUser::getStatus, UserConstants.USER_NORMAL)
            .in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
            .eq(SysUser::getStatus, SystemConstants.NORMAL)
            .in(SysUser::getUserId, userIds));
        return BeanUtil.copyToList(list, UserDTO.class);
    }
    /**
     * é€šè¿‡è§’色ID查询用户ID
     *
     * @param roleIds è§’色ids
     * @return ç”¨æˆ·ids
     */
    @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);
    }
    @Override
    public List<UserDTO> selectUsersByRoleIds(List<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return List.of();
        }
        List<SysUserRole> userRoles = userRoleMapper.selectList(
            new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
        List<Long> userIds = StreamUtils.toList(userRoles, SysUserRole::getUserId);
        return selectListByIds(userIds);
        return StreamUtils.toList(userRoles, SysUserRole::getUserId);
    }
    /**
     * é€šè¿‡è§’色ID查询用户
     *
     * @param roleIds è§’色ids
     * @return ç”¨æˆ·
     */
    @Override
    public List<UserDTO> selectUsersByRoleIds(List<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return List.of();
        }
        // é€šè¿‡è§’色ID获取用户角色信息
        List<SysUserRole> userRoles = userRoleMapper.selectList(
            new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
        // èŽ·å–ç”¨æˆ·ID列表
        Set<Long> userIds = StreamUtils.toSet(userRoles, SysUserRole::getUserId);
        return selectListByIds(new ArrayList<>(userIds));
    }
    /**
     * é€šè¿‡éƒ¨é—¨ID查询用户
     *
     * @param deptIds éƒ¨é—¨ids
     * @return ç”¨æˆ·
     */
    @Override
    public List<UserDTO> selectUsersByDeptIds(List<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
@@ -665,8 +692,31 @@
        }
        List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
            .eq(SysUser::getStatus, UserConstants.USER_NORMAL)
            .in(CollUtil.isNotEmpty(deptIds), SysUser::getDeptId, deptIds));
            .eq(SysUser::getStatus, SystemConstants.NORMAL)
            .in(SysUser::getDeptId, deptIds));
        return BeanUtil.copyToList(list, UserDTO.class);
    }
    /**
     * é€šè¿‡å²—位ID查询用户
     *
     * @param postIds å²—位ids
     * @return ç”¨æˆ·
     */
    @Override
    public List<UserDTO> selectUsersByPostIds(List<Long> postIds) {
        if (CollUtil.isEmpty(postIds)) {
            return List.of();
        }
        // é€šè¿‡å²—位ID获取用户岗位信息
        List<SysUserPost> userPosts = userPostMapper.selectList(
            new LambdaQueryWrapper<SysUserPost>().in(SysUserPost::getPostId, postIds));
        // èŽ·å–ç”¨æˆ·ID列表
        Set<Long> userIds = StreamUtils.toSet(userPosts, SysUserPost::getUserId);
        return selectListByIds(new ArrayList<>(userIds));
    }
}
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -18,6 +18,17 @@
        from sys_dept ${ew.getCustomSqlSegment}
    </select>
    <select id="selectPageDeptList" resultMap="SysDeptResult">
        select
        <if test="ew.getSqlSelect != null">
            ${ew.getSqlSelect}
        </if>
        <if test="ew.getSqlSelect == null">
            *
        </if>
        from sys_dept ${ew.getCustomSqlSegment}
    </select>
    <select id="countDeptById" resultType="Long">
        select count(*) from sys_dept where del_flag = '0' and dept_id = #{deptId}
    </select>
ruoyi-modules/ruoyi-workflow/pom.xml
@@ -18,57 +18,14 @@
    <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.17</version>
            <exclusions>
                <exclusion>
                    <groupId>xalan</groupId>
                    <artifactId>xalan</artifactId>
                </exclusion>
            </exclusions>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-sse</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-websocket</artifactId>
            <artifactId>ruoyi-common-doc</artifactId>
        </dependency>
        <dependency>
@@ -113,6 +70,14 @@
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara.warm</groupId>
            <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.dromara.warm</groupId>
            <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package org.dromara.workflow.common;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true")
public @interface ConditionalOnEnable {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
@@ -8,83 +8,6 @@
 */
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 BUSINESS_INSTANCE_DTO = "businessInstanceDTO";
    /**
     * æµç¨‹å®šä¹‰é…ç½®
     */
    String WF_DEFINITION_CONFIG_VO = "wfDefinitionConfigVo";
    /**
     * èŠ‚ç‚¹é…ç½®
     */
    String WF_NODE_CONFIG_VO = "wfNodeConfigVo";
    /**
     * æµç¨‹å‘起人
     */
@@ -98,40 +21,46 @@
    /**
     * ä¸šåŠ¡id
     */
    String BUSINESS_KEY = "businessKey";
    String BUSINESS_ID = "businessId";
    /**
     * æµç¨‹å®šä¹‰id
     * ä»»åŠ¡id
     */
    String PROCESS_DEFINITION_ID = "processDefinitionId";
    String TASK_ID = "taskId";
    /**
     * å¼€å¯è·³è¿‡è¡¨è¾¾å¼å˜é‡
     * å§”托
     */
    String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
    String DELEGATE_TASK = "delegateTask";
    /**
     * æ¨¡åž‹æ ‡è¯†key命名规范正则表达式
     * è½¬åŠž
     */
    String MODEL_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9_]{0,254}$";
    String TRANSFER_TASK = "transferTask";
    /**
     * ç”¨æˆ·ä»»åŠ¡
     * åŠ ç­¾
     */
    String USER_TASK = "userTask";
    String ADD_SIGNATURE = "addSignature";
    /**
     * ä¼šç­¾
     * å‡ç­¾
     */
    String MULTI_INSTANCE = "multiInstance";
    String REDUCTION_SIGNATURE = "reductionSignature";
    /**
     * æ˜¯
     * æµç¨‹åˆ†ç±»Id转名称
     */
    String TRUE = "0";
    String CATEGORY_ID_TO_NAME = "category_id_to_name";
    /**
     * å¦
     * æµç¨‹åˆ†ç±»åç§°
     */
    String FALSE = "1";
    String FLOW_CATEGORY_NAME = "flow_category_name#30d";
    /**
     * é»˜è®¤ç§Ÿæˆ·OA申请分类id
     */
    Long FLOW_CATEGORY_ID = 100L;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/FormTypeEnum.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
@@ -3,8 +3,10 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * æ¶ˆæ¯ç±»åž‹æžšä¸¾
@@ -14,14 +16,17 @@
@Getter
@AllArgsConstructor
public enum MessageTypeEnum {
    /**
     * ç«™å†…ä¿¡
     */
    SYSTEM_MESSAGE("1", "站内信"),
    /**
     * é‚®ç®±
     */
    EMAIL_MESSAGE("2", "邮箱"),
    /**
     * çŸ­ä¿¡
     */
@@ -31,21 +36,18 @@
    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);
        }
    }
    private static final Map<String, MessageTypeEnum> MESSAGE_TYPE_ENUM_MAP = Arrays.stream(values())
        .collect(Collectors.toConcurrentMap(MessageTypeEnum::getCode, Function.identity()));
    /**
     * æ ¹æ®æ¶ˆæ¯ç±»åž‹ code èŽ·å– MessageTypeEnum
     *
     * @param code æ¶ˆæ¯ç±»åž‹code
     * @return MessageTypeEnum
     */
    public static MessageTypeEnum getByCode(String code) {
        return MESSAGE_TYPE_ENUM_MAP.get(code);
        return MESSAGE_TYPE_ENUM_MAP.getOrDefault(code, null);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
package org.dromara.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.exception.ServiceException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
 * ä»»åŠ¡åˆ†é…äººæžšä¸¾
 *
 * @author AprilWind
 */
@Getter
@AllArgsConstructor
public enum TaskAssigneeEnum {
    /**
     * ç”¨æˆ·
     */
    USER("用户", ""),
    /**
     * è§’色
     */
    ROLE("角色", "role:"),
    /**
     * éƒ¨é—¨
     */
    DEPT("部门", "dept:"),
    /**
     * å²—位
     */
    POST("岗位", "post:");
    private final String desc;
    private final String code;
    /**
     * æ ¹æ®æè¿°èŽ·å–å¯¹åº”çš„æžšä¸¾ç±»åž‹
     * <p>
     * é€šè¿‡ä¼ å…¥æè¿°ï¼ŒæŸ¥æ‰¾å¹¶è¿”回匹配的枚举项。如果未找到匹配项,会抛出 {@link ServiceException}。
     * </p>
     *
     * @param desc æè¿°ï¼Œç”¨äºŽåŒ¹é…å¯¹åº”的枚举项
     * @return TaskAssigneeEnum è¿”回对应的枚举类型
     * @throws ServiceException å¦‚果未找到匹配的枚举项
     */
    public static TaskAssigneeEnum fromDesc(String desc) {
        for (TaskAssigneeEnum type : values()) {
            if (type.getDesc().equals(desc)) {
                return type;
            }
        }
        throw new ServiceException("未知的办理人类型: " + desc);
    }
    /**
     * æ ¹æ®ä»£ç èŽ·å–å¯¹åº”çš„æžšä¸¾ç±»åž‹
     * <p>
     * é€šè¿‡ä¼ å…¥ä»£ç ï¼ŒæŸ¥æ‰¾å¹¶è¿”回匹配的枚举项。如果未找到匹配项,会抛出 {@link ServiceException}。
     * </p>
     *
     * @param code ä»£ç ï¼Œç”¨äºŽåŒ¹é…å¯¹åº”的枚举项
     * @return TaskAssigneeEnum è¿”回对应的枚举类型
     * @throws IllegalArgumentException å¦‚果未找到匹配的枚举项
     */
    public static TaskAssigneeEnum fromCode(String code) {
        for (TaskAssigneeEnum type : values()) {
            if (type.getCode().equals(code)) {
                return type;
            }
        }
        throw new ServiceException("未知的办理人类型代码: " + code);
    }
    /**
     * èŽ·å–æ‰€æœ‰åŠžç†äººç±»åž‹çš„æè¿°åˆ—è¡¨
     * <p>
     * èŽ·å–å½“å‰æžšä¸¾ç±»æ‰€æœ‰é¡¹çš„æè¿°å­—æ®µåˆ—è¡¨ï¼Œé€šå¸¸ç”¨äºŽå±•ç¤ºé€‰æ‹©é¡¹ã€‚
     * </p>
     *
     * @return List<String> è¿”回所有办理人类型的描述列表
     */
    public static List<String> getAssigneeTypeList() {
        return Arrays.stream(values())
            .map(TaskAssigneeEnum::getDesc)
            .collect(Collectors.toList());
    }
    /**
     * èŽ·å–æ‰€æœ‰åŠžç†äººç±»åž‹çš„ä»£ç åˆ—è¡¨
     * <p>
     * èŽ·å–å½“å‰æžšä¸¾ç±»æ‰€æœ‰é¡¹çš„ä»£ç å­—æ®µåˆ—è¡¨ï¼Œé€šå¸¸ç”¨äºŽç¨‹åºå†…éƒ¨é€»è¾‘çš„åˆ¤æ–­ã€‚
     * </p>
     *
     * @return List<String> è¿”回所有办理人类型的代码列表
     */
    public static List<String> getAssigneeCodeList() {
        return Arrays.stream(values())
            .map(TaskAssigneeEnum::getCode)
            .collect(Collectors.toList());
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package org.dromara.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * äººå‘˜ç±»åž‹
 *
 * @author AprilWind
 */
@Getter
@AllArgsConstructor
public enum TaskAssigneeType {
    /**
     * å¾…办任务的审批人权限
     * <p>该权限表示用户是待办任务的审批人,负责审核任务的执行情况。</p>
     */
    APPROVER("1", "待办任务的审批人权限"),
    /**
     * å¾…办任务的转办人权限
     * <p>该权限表示用户是待办任务的转办人,负责将任务分配给其他人员。</p>
     */
    TRANSFER("2", "待办任务的转办人权限"),
    /**
     * å¾…办任务的委托人权限
     * <p>该权限表示用户是待办任务的委托人,能够委托其他人代为处理任务。</p>
     */
    DELEGATE("3", "待办任务的委托人权限"),
    /**
     * å¾…办任务的抄送人权限
     * <p>该权限表示用户是待办任务的抄送人,仅接收任务信息的通知,不参与任务的审批或处理。</p>
     */
    COPY("4", "待办任务的抄送人权限");
    /**
     * ç±»åž‹
     */
    private final String code;
    /**
     * æè¿°
     */
    private final String description;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
@@ -3,9 +3,10 @@
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * ä»»åŠ¡çŠ¶æ€æžšä¸¾
@@ -15,50 +16,62 @@
@Getter
@AllArgsConstructor
public enum TaskStatusEnum {
    /**
     * æ’¤é”€
     */
    CANCEL("cancel", "撤销"),
    /**
     * é€šè¿‡
     */
    PASS("pass", "通过"),
    /**
     * å¾…审核
     */
    WAITING("waiting", "待审核"),
    /**
     * ä½œåºŸ
     */
    INVALID("invalid", "作废"),
    /**
     * é€€å›ž
     */
    BACK("back", "退回"),
    /**
     * ç»ˆæ­¢
     */
    TERMINATION("termination", "终止"),
    /**
     * è½¬åŠž
     */
    TRANSFER("transfer", "转办"),
    /**
     * å§”托
     */
    PENDING("pending", "委托"),
    DEPUTE("depute", "委托"),
    /**
     * æŠ„送
     */
    COPY("copy", "抄送"),
    /**
     * åŠ ç­¾
     */
    SIGN("sign", "加签"),
    /**
     * å‡ç­¾
     */
    SIGN_OFF("sign_off", "减签"),
    /**
     * è¶…æ—¶
     */
@@ -74,21 +87,18 @@
     */
    private final String desc;
    private static final Map<String, String> STATUS_DESC_MAP = Arrays.stream(values())
        .collect(Collectors.toConcurrentMap(TaskStatusEnum::getStatus, TaskStatusEnum::getDesc));
    /**
     * ä»»åŠ¡ä¸šåŠ¡çŠ¶æ€
     *
     * @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);
        // ä»Žç¼“存中直接获取描述
        return STATUS_DESC_MAP.getOrDefault(status, StrUtil.EMPTY);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package org.dromara.workflow.config;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.springframework.context.annotation.Configuration;
/**
 * warmFlow配置
 *
 * @author may
 */
@ConditionalOnEnable
@Configuration
public class WarmFlowConfig {
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
package org.dromara.workflow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
import jakarta.servlet.http.HttpServletResponse;
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.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.FlowCategoryBo;
import org.dromara.workflow.domain.vo.FlowCategoryVo;
import org.dromara.workflow.service.IFlwCategoryService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»
 *
 * @author may
 */
@ConditionalOnEnable
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/category")
public class FlwCategoryController extends BaseController {
    private final IFlwCategoryService flwCategoryService;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
     */
    @SaCheckPermission("workflow:category:list")
    @GetMapping("/list")
    public R<List<FlowCategoryVo>> list(FlowCategoryBo bo) {
        List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
        return R.ok(list);
    }
    /**
     * å¯¼å‡ºæµç¨‹åˆ†ç±»åˆ—表
     */
    @SaCheckPermission("workflow:category:export")
    @Log(title = "流程分类", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(FlowCategoryBo bo, HttpServletResponse response) {
        List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
        ExcelUtil.exportExcel(list, "流程分类", FlowCategoryVo.class, response);
    }
    /**
     * èŽ·å–æµç¨‹åˆ†ç±»è¯¦ç»†ä¿¡æ¯
     *
     * @param categoryId ä¸»é”®
     */
    @SaCheckPermission("workflow:category:query")
    @GetMapping("/{categoryId}")
    public R<FlowCategoryVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long categoryId) {
        flwCategoryService.checkCategoryDataScope(categoryId);
        return R.ok(flwCategoryService.queryById(categoryId));
    }
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     */
    @SaCheckPermission("workflow:category:add")
    @Log(title = "流程分类", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowCategoryBo category) {
        if (!flwCategoryService.checkCategoryNameUnique(category)) {
            return R.fail("新增流程分类'" + category.getCategoryName() + "'失败,流程分类名称已存在");
        }
        return toAjax(flwCategoryService.insertByBo(category));
    }
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     */
    @SaCheckPermission("workflow:category:edit")
    @Log(title = "流程分类", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowCategoryBo category) {
        Long categoryId = category.getCategoryId();
        flwCategoryService.checkCategoryDataScope(categoryId);
        if (!flwCategoryService.checkCategoryNameUnique(category)) {
            return R.fail("修改流程分类'" + category.getCategoryName() + "'失败,流程分类名称已存在");
        } else if (category.getParentId().equals(categoryId)) {
            return R.fail("修改流程分类'" + category.getCategoryName() + "'失败,上级流程分类不能是自己");
        }
        return toAjax(flwCategoryService.updateByBo(category));
    }
    /**
     * åˆ é™¤æµç¨‹åˆ†ç±»
     *
     * @param categoryId ä¸»é”®
     */
    @SaCheckPermission("workflow:category:remove")
    @Log(title = "流程分类", businessType = BusinessType.DELETE)
    @DeleteMapping("/{categoryId}")
    public R<Void> remove(@PathVariable Long categoryId) {
        if (flwCategoryService.hasChildByCategoryId(categoryId)) {
            return R.warn("存在下级流程分类,不允许删除");
        }
        if (flwCategoryService.checkCategoryExistDefinition(categoryId)) {
            return R.warn("流程分类存在流程定义,不允许删除");
        }
        return toAjax(flwCategoryService.deleteWithValidById(categoryId));
    }
    /**
     * èŽ·å–æµç¨‹åˆ†ç±»æ ‘åˆ—è¡¨
     *
     * @param categoryBo æµç¨‹åˆ†ç±»
     */
    @GetMapping("/categoryTree")
    public R<List<Tree<String>>> categoryTree(FlowCategoryBo categoryBo) {
        return R.ok(flwCategoryService.selectCategoryTreeList(categoryBo));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
package org.dromara.workflow.controller;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.warm.flow.core.entity.Definition;
import org.dromara.warm.flow.core.service.DefService;
import org.dromara.warm.flow.orm.entity.FlowDefinition;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.vo.FlowDefinitionVo;
import org.dromara.workflow.service.IFlwDefinitionService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
 * æµç¨‹å®šä¹‰ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@ConditionalOnEnable
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/definition")
public class FlwDefinitionController extends BaseController {
    private final DefService defService;
    private final IFlwDefinitionService flwDefinitionService;
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰åˆ—表
     *
     * @param flowDefinition å‚æ•°
     * @param pageQuery      åˆ†é¡µ
     */
    @GetMapping("/list")
    public TableDataInfo<FlowDefinitionVo> list(FlowDefinition flowDefinition, PageQuery pageQuery) {
        return flwDefinitionService.queryList(flowDefinition, pageQuery);
    }
    /**
     * æŸ¥è¯¢æœªå‘布的流程定义列表
     *
     * @param flowDefinition å‚æ•°
     * @param pageQuery      åˆ†é¡µ
     */
    @GetMapping("/unPublishList")
    public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
        return flwDefinitionService.unPublishList(flowDefinition, pageQuery);
    }
    /**
     * èŽ·å–æµç¨‹å®šä¹‰è¯¦ç»†ä¿¡æ¯
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @GetMapping(value = "/{id}")
    public R<Definition> getInfo(@PathVariable Long id) {
        return R.ok(defService.getById(id));
    }
    /**
     * æ–°å¢žæµç¨‹å®šä¹‰
     *
     * @param flowDefinition å‚æ•°
     */
    @Log(title = "流程定义", businessType = BusinessType.INSERT)
    @PostMapping
    @RepeatSubmit()
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> add(@RequestBody FlowDefinition flowDefinition) {
        return R.ok(defService.checkAndSave(flowDefinition));
    }
    /**
     * ä¿®æ”¹æµç¨‹å®šä¹‰
     *
     * @param flowDefinition å‚æ•°
     */
    @Log(title = "流程定义", businessType = BusinessType.UPDATE)
    @PutMapping
    @RepeatSubmit()
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> edit(@RequestBody FlowDefinition flowDefinition) {
        return R.ok(defService.updateById(flowDefinition));
    }
    /**
     * å‘布流程定义
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义", businessType = BusinessType.INSERT)
    @PutMapping("/publish/{id}")
    @RepeatSubmit()
    public R<Boolean> publish(@PathVariable Long id) {
        return R.ok(flwDefinitionService.publish(id));
    }
    /**
     * å–消发布流程定义
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义", businessType = BusinessType.INSERT)
    @PutMapping("/unPublish/{id}")
    @RepeatSubmit()
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> unPublish(@PathVariable Long id) {
        return R.ok(defService.unPublish(id));
    }
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     */
    @Log(title = "流程定义", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@PathVariable List<Long> ids) {
        return toAjax(flwDefinitionService.removeDef(ids));
    }
    /**
     * å¤åˆ¶æµç¨‹å®šä¹‰
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @Log(title = "流程定义", businessType = BusinessType.INSERT)
    @PostMapping("/copy/{id}")
    @RepeatSubmit()
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> copy(@PathVariable Long id) {
        return R.ok(defService.copyDef(id));
    }
    /**
     * å¯¼å…¥æµç¨‹å®šä¹‰
     *
     * @param file     æ–‡ä»¶
     * @param category åˆ†ç±»
     */
    @Log(title = "流程定义", businessType = BusinessType.IMPORT)
    @PostMapping("/importDef")
    public R<Boolean> importDef(MultipartFile file, String category) {
        return R.ok(flwDefinitionService.importJson(file, category));
    }
    /**
     * å¯¼å‡ºæµç¨‹å®šä¹‰
     *
     * @param id       æµç¨‹å®šä¹‰id
     * @param response å“åº”
     * @throws IOException å¼‚常
     */
    @Log(title = "流程定义", businessType = BusinessType.EXPORT)
    @PostMapping("/exportDef/{id}")
    public void exportDef(@PathVariable Long id, HttpServletResponse response) throws IOException {
        flwDefinitionService.exportDef(id, response);
    }
    /**
     * èŽ·å–æµç¨‹å®šä¹‰JSON字符串
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @GetMapping("/xmlString/{id}")
    public R<String> xmlString(@PathVariable Long id) {
        return R.ok("操作成功", defService.exportJson(id));
    }
    /**
     * æ¿€æ´»/挂起流程定义
     *
     * @param id     æµç¨‹å®šä¹‰id
     * @param active æ¿€æ´»/挂起
     */
    @RepeatSubmit()
    @PutMapping("/active/{id}")
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
        return R.ok(active ? defService.active(id) : defService.unActive(id));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,157 @@
package org.dromara.workflow.controller;
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.warm.flow.core.service.InsService;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.FlowCancelBo;
import org.dromara.workflow.domain.bo.FlowInstanceBo;
import org.dromara.workflow.domain.bo.FlowInvalidBo;
import org.dromara.workflow.domain.vo.FlowInstanceVo;
import org.dromara.workflow.service.IFlwInstanceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹å®žä¾‹ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@ConditionalOnEnable
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/instance")
public class FlwInstanceController extends BaseController {
    private final InsService insService;
    private final IFlwInstanceService flwInstanceService;
    /**
     * æŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹åˆ—表
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     */
    @GetMapping("/pageByRunning")
    public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
        return flwInstanceService.selectRunningInstanceList(flowInstanceBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å·²ç»“束的流程实例列表
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     */
    @GetMapping("/pageByFinish")
    public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
        return flwInstanceService.selectFinishInstanceList(flowInstanceBo, pageQuery);
    }
    /**
     * æ ¹æ®ä¸šåŠ¡id查询流程实例详细信息
     *
     * @param businessId ä¸šåŠ¡id
     */
    @GetMapping("/getInfo/{businessId}")
    public R<FlowInstanceVo> getInfo(@PathVariable Long businessId) {
        return R.ok(flwInstanceService.queryByBusinessId(businessId));
    }
    /**
     * æŒ‰ç…§ä¸šåŠ¡id删除流程实例
     *
     * @param businessIds ä¸šåŠ¡id
     */
    @DeleteMapping("/deleteByBusinessIds/{businessIds}")
    public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
        return toAjax(flwInstanceService.deleteByBusinessIds(businessIds));
    }
    /**
     * æŒ‰ç…§å®žä¾‹id删除流程实例
     *
     * @param instanceIds å®žä¾‹id
     */
    @DeleteMapping("/deleteByInstanceIds/{instanceIds}")
    public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
        return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
    }
    /**
     * æ’¤é”€æµç¨‹
     *
     * @param bo å‚æ•°
     */
    @RepeatSubmit()
    @PutMapping("/cancelProcessApply")
    public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
        return toAjax(flwInstanceService.cancelProcessApply(bo));
    }
    /**
     * æ¿€æ´»/挂起流程实例
     *
     * @param id     æµç¨‹å®žä¾‹id
     * @param active æ¿€æ´»/挂起
     */
    @RepeatSubmit()
    @PutMapping("/active/{id}")
    public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
        return R.ok(active ? insService.active(id) : insService.unActive(id));
    }
    /**
     * èŽ·å–å½“å‰ç™»é™†äººå‘èµ·çš„æµç¨‹å®žä¾‹
     *
     * @param flowInstanceBo å‚æ•°
     * @param pageQuery      åˆ†é¡µ
     */
    @GetMapping("/pageByCurrent")
    public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
        return flwInstanceService.selectCurrentInstanceList(flowInstanceBo, pageQuery);
    }
    /**
     * èŽ·å–æµç¨‹å›¾ï¼Œæµç¨‹è®°å½•
     *
     * @param businessId ä¸šåŠ¡id
     */
    @GetMapping("/flowImage/{businessId}")
    public R<Map<String, Object>> flowImage(@PathVariable String businessId) {
        return R.ok(flwInstanceService.flowImage(businessId));
    }
    /**
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param instanceId æµç¨‹å®žä¾‹id
     */
    @GetMapping("/instanceVariable/{instanceId}")
    public R<Map<String, Object>> instanceVariable(@PathVariable Long instanceId) {
        return R.ok(flwInstanceService.instanceVariable(instanceId));
    }
    /**
     * ä½œåºŸæµç¨‹
     *
     * @param bo å‚æ•°
     */
    @Log(title = "流程实例管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/invalid")
    public R<Boolean> invalid(@Validated @RequestBody FlowInvalidBo bo) {
        return R.ok(flwInstanceService.processInvalid(bo));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,201 @@
package org.dromara.workflow.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import org.dromara.common.core.domain.dto.UserDTO;
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.warm.flow.core.entity.Node;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
import org.dromara.workflow.domain.vo.FlowTaskVo;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * ä»»åŠ¡ç®¡ç† æŽ§åˆ¶å±‚
 *
 * @author may
 */
@ConditionalOnEnable
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/task")
public class FlwTaskController extends BaseController {
    private final IFlwTaskService flwTaskService;
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/startWorkFlow")
    public R<StartProcessReturnDTO> startWorkFlow(@Validated(AddGroup.class) @RequestBody StartProcessBo startProcessBo) {
        StartProcessReturnDTO startProcessReturn = flwTaskService.startWorkFlow(startProcessBo);
        return R.ok("提交成功", startProcessReturn);
    }
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/completeTask")
    public R<Void> completeTask(@Validated(AddGroup.class) @RequestBody CompleteTaskBo completeTaskBo) {
        return toAjax(flwTaskService.completeTask(completeTaskBo));
    }
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @GetMapping("/pageByTaskWait")
    public TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        return flwTaskService.pageByTaskWait(flowTaskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前用户的已办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @GetMapping("/pageByTaskFinish")
    public TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        return flwTaskService.pageByTaskFinish(flowTaskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å¾…办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @GetMapping("/pageByAllTaskWait")
    public TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        return flwTaskService.pageByAllTaskWait(flowTaskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å·²åŠžä»»åŠ¡
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @GetMapping("/pageByAllTaskFinish")
    public TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        return flwTaskService.pageByAllTaskFinish(flowTaskBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @GetMapping("/pageByTaskCopy")
    public TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        return flwTaskService.pageByTaskCopy(flowTaskBo, pageQuery);
    }
    /**
     * æ ¹æ®taskId查询代表任务
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/getTask/{taskId}")
    public R<FlowTaskVo> getTask(@PathVariable Long taskId) {
        return R.ok(flwTaskService.selectById(taskId));
    }
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param bo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/terminationTask")
    public R<Boolean> terminationTask(@RequestBody FlowTerminationBo bo) {
        return R.ok(flwTaskService.terminationTask(bo));
    }
    /**
     * ä»»åŠ¡æ“ä½œ
     *
     * @param bo            å‚æ•°
     * @param taskOperation æ“ä½œç±»åž‹ï¼Œå§”æ´¾ delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
     */
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit
    @PostMapping("/taskOperation/{taskOperation}")
    public R<Void> taskOperation(@Validated @RequestBody TaskOperationBo bo, @PathVariable String taskOperation) {
        return toAjax(flwTaskService.taskOperation(bo, taskOperation));
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
     *
     * @param taskIdList ä»»åŠ¡id
     * @param userId     åŠžç†äººid
     */
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/updateAssignee/{userId}")
    public R<Void> updateAssignee(@RequestBody List<Long> taskIdList, @PathVariable String userId) {
        return toAjax(flwTaskService.updateAssignee(taskIdList, userId));
    }
    /**
     * é©³å›žå®¡æ‰¹
     *
     * @param bo å‚æ•°
     */
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/backProcess")
    public R<Void> backProcess(@Validated({AddGroup.class}) @RequestBody BackProcessBo bo) {
        return toAjax(flwTaskService.backProcess(bo));
    }
    /**
     * èŽ·å–å¯é©³å›žçš„å‰ç½®èŠ‚ç‚¹
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param nowNodeCode  å½“前节点
     */
    @GetMapping("/getBackTaskNode/{definitionId}/{nowNodeCode}")
    public R<List<Node>> getBackTaskNode(@PathVariable Long definitionId, @PathVariable String nowNodeCode) {
        return R.ok(flwTaskService.getBackTaskNode(definitionId, nowNodeCode));
    }
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡çš„æ‰€æœ‰åŠžç†äºº
     *
     * @param taskId ä»»åŠ¡id
     */
    @GetMapping("/currentTaskAllUser/{taskId}")
    public R<List<UserDTO>> currentTaskAllUser(@PathVariable Long taskId) {
        return R.ok(flwTaskService.currentTaskAllUser(taskId));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
@@ -15,6 +15,7 @@
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.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import org.dromara.workflow.service.ITestLeaveService;
@@ -29,6 +30,7 @@
 * @author may
 * @date 2023-07-21
 */
@ConditionalOnEnable
@Validated
@RequiredArgsConstructor
@RestController
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfDefinitionConfigController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfFormManageController.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.dromara.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
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("flow_category")
public class FlowCategory extends TenantEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹åˆ†ç±»ID
     */
    @TableId(value = "category_id")
    private Long categoryId;
    /**
     * çˆ¶æµç¨‹åˆ†ç±»id
     */
    private Long parentId;
    /**
     * ç¥–级列表
     */
    private String ancestors;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    private String categoryName;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    private Long orderNum;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 1代表删除)
     */
    @TableLogic
    private String delFlag;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfDefinitionConfig.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfFormManage.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfNodeConfig.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfTaskBackNode.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
@@ -1,12 +1,16 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
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.List;
import java.util.Map;
import java.util.Objects;
/**
@@ -23,8 +27,13 @@
    /**
     * ä»»åŠ¡ID
     */
    @NotBlank(message = "任务ID不能为空", groups = AddGroup.class)
    private String taskId;
    @NotNull(message = "任务ID不能为空", groups = AddGroup.class)
    private Long taskId;
    /**
     * é™„ä»¶id
     */
    private String fileId;
    /**
     * æ¶ˆæ¯ç±»åž‹
@@ -35,10 +44,28 @@
     * é©³å›žçš„节点id(目前未使用,直接驳回到申请人)
     */
    @NotBlank(message = "驳回的节点不能为空", groups = AddGroup.class)
    private String targetActivityId;
    private String nodeCode;
    /**
     * åŠžç†æ„è§
     */
    private String message;
    /**
     * é€šçŸ¥
     */
    private String notice;
    /**
     * æµç¨‹å˜é‡
     */
    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/CompleteTaskBo.java
@@ -1,9 +1,8 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
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;
@@ -26,8 +25,8 @@
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id不能为空", groups = {AddGroup.class})
    private String taskId;
    @NotNull(message = "任务id不能为空", groups = {AddGroup.class})
    private Long taskId;
    /**
     * é™„ä»¶id
@@ -37,7 +36,7 @@
    /**
     * æŠ„送人员
     */
    private List<WfCopy> wfCopyList;
    private List<FlowCopyBo> flowCopyList;
    /**
     * æ¶ˆæ¯ç±»åž‹
@@ -50,10 +49,21 @@
    private String message;
    /**
     * æ¶ˆæ¯é€šçŸ¥
     */
    private String notice;
    /**
     * æµç¨‹å˜é‡
     */
    private Map<String, Object> variables;
    /**
     * æ‰©å±•变量(此处为逗号分隔的ossId)
     * @return
     */
    private String ext;
    public Map<String, Object> getVariables() {
        if (variables == null) {
            return new HashMap<>(16);
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCancelBo.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 FlowCancelBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ID
     */
    @NotBlank(message = "业务ID不能为空", groups = AddGroup.class)
    private String businessId;
    /**
     * åŠžç†æ„è§
     */
    private String message;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
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.FlowCategory;
/**
 * æµç¨‹åˆ†ç±»ä¸šåŠ¡å¯¹è±¡ wf_category
 *
 * @author may
 * @date 2023-06-27
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = FlowCategory.class, reverseConvertGenerate = false)
public class FlowCategoryBo extends BaseEntity {
    /**
     * æµç¨‹åˆ†ç±»ID
     */
    @NotNull(message = "流程分类ID不能为空", groups = { EditGroup.class })
    private Long categoryId;
    /**
     * çˆ¶æµç¨‹åˆ†ç±»id
     */
    @NotNull(message = "父流程分类id不能为空", groups = {AddGroup.class, EditGroup.class})
    private Long parentId;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @NotBlank(message = "流程分类名称不能为空", groups = {AddGroup.class, EditGroup.class})
    private String categoryName;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    private Long orderNum;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java
ÎļþÃû´Ó ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java ÐÞ¸Ä
@@ -1,9 +1,10 @@
package org.dromara.workflow.domain.vo;
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
 * æŠ„送
@@ -11,7 +12,7 @@
 * @author may
 */
@Data
public class WfCopy implements Serializable {
public class FlowCopyBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
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 FlowInstanceBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * ä»»åŠ¡å‘èµ·äºº
     */
    private String startUserId;
    /**
     * ä¸šåŠ¡id
     */
    private String businessId;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * ä»»åŠ¡åç§°
     */
    private String nodeName;
    /**
     * ç”³è¯·äººIds
     */
    private List<Long> createByIds;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import java.io.Serial;
import java.io.Serializable;
/**
 * ä½œåºŸè¯·æ±‚对象
 *
 * @author may
 */
@Data
public class FlowInvalidBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹id
     */
    @NotNull(message = "流程实例id为空", groups = AddGroup.class)
    private Long id;
    /**
     * å®¡æ‰¹æ„è§
     */
    private String comment;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
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 FlowTaskBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡åç§°
     */
    private String nodeName;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * æµç¨‹å®žä¾‹id
     */
    private Long instanceId;
    /**
     * æƒé™åˆ—表
     */
    private List<String> permissionList;
    /**
     * ç”³è¯·äººIds
     */
    private List<Long> createByIds;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java
ÎļþÃû´Ó ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java ÐÞ¸Ä
@@ -1,6 +1,6 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
@@ -13,7 +13,7 @@
 * @author may
 */
@Data
public class TerminationBo implements Serializable {
public class FlowTerminationBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
@@ -21,8 +21,8 @@
    /**
     * ä»»åŠ¡id
     */
    @NotBlank(message = "任务id为空", groups = AddGroup.class)
    private String taskId;
    @NotNull(message = "任务id为空", groups = AddGroup.class)
    private Long taskId;
    /**
     * å®¡æ‰¹æ„è§
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
@@ -26,13 +26,13 @@
     * ä¸šåС唝䏀值id
     */
    @NotBlank(message = "业务ID不能为空", groups = {AddGroup.class})
    private String businessKey;
    private String businessId;
    /**
     * è¡¨å
     * æµç¨‹å®šä¹‰ç¼–码
     */
    @NotBlank(message = "表名不能为空", groups = {AddGroup.class})
    private String tableName;
    @NotBlank(message = "流程定义编码不能为空", groups = {AddGroup.class})
    private String flowCode;
    /**
     * æµç¨‹å˜é‡ï¼Œå‰ç«¯ä¼šæäº¤ä¸€ä¸ªå…ƒç´ {'entity': {业务详情数据对象}}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskOperationBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.dromara.workflow.domain.bo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * ä»»åŠ¡æ“ä½œä¸šåŠ¡å¯¹è±¡ï¼Œç”¨äºŽæè¿°ä»»åŠ¡å§”æ´¾ã€è½¬åŠžã€åŠ ç­¾ç­‰æ“ä½œçš„å¿…è¦å‚æ•°
 * åŒ…含了用户ID、任务ID、任务相关的消息、以及加签/减签的用户ID
 *
 * @author AprilWind
 */
@Data
public class TaskOperationBo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * å§”æ´¾/转办人的用户ID(必填,准对委派/转办人操作)
     */
    @NotNull(message = "委派/转办人id不能为空", groups = {AddGroup.class})
    private String userId;
    /**
     * åŠ ç­¾/减签人的用户ID列表(必填,针对加签/减签操作)
     */
    @NotNull(message = "加签/减签id不能为空", groups = {EditGroup.class})
    private List<String> userIds;
    /**
     * ä»»åŠ¡ID(必填)
     */
    @NotNull(message = "任务id不能为空")
    private Long taskId;
    /**
     * æ„è§æˆ–备注信息(可选)
     */
    private String message;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
@@ -53,7 +53,6 @@
    /**
     * è¯·å‡å¤©æ•°
     */
    @NotNull(message = "请假天数不能为空", groups = {AddGroup.class, EditGroup.class})
    private Integer leaveDays;
    /**
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfDefinitionConfigBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfFormManageBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfNodeConfigBo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
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.FlowCategory;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * æµç¨‹åˆ†ç±»è§†å›¾å¯¹è±¡ wf_category
 *
 * @author may
 * @date 2023-06-27
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = FlowCategory.class)
public class FlowCategoryVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹åˆ†ç±»ID
     */
    @ExcelProperty(value = "流程分类ID")
    private Long categoryId;
    /**
     * çˆ¶çº§id
     */
    private Long parentId;
    /**
     * çˆ¶ç±»åˆ«åç§°
     */
    private String parentName;
    /**
     * ç¥–级列表
     */
    private String ancestors;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @ExcelProperty(value = "流程分类名称")
    private String categoryName;
    /**
     * æ˜¾ç¤ºé¡ºåº
     */
    @ExcelProperty(value = "显示顺序")
    private Long orderNum;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ExcelProperty(value = "创建时间")
    private Date createTime;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.workflow.common.constant.FlowConstant;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
 * æµç¨‹å®šä¹‰è§†å›¾
 *
 * @author may
 */
@Data
public class FlowDefinitionVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    private Long id;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private Date updateTime;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * åˆ é™¤æ ‡è®°
     */
    private String delFlag;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
    private String categoryName;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    private String version;
    /**
     * æ˜¯å¦å‘布(0未发布 1已发布 9失效)
     */
    private Integer isPublish;
    /**
     * å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
     */
    private String formCustom;
    /**
     * å®¡æ‰¹è¡¨å•路径
     */
    private String formPath;
    /**
     * æµç¨‹æ¿€æ´»çŠ¶æ€ï¼ˆ0挂起 1激活)
     */
    private Integer activityStatus;
    /**
     * ç›‘听器类型
     */
    private String listenerType;
    /**
     * ç›‘听器路径
     */
    private String listenerPath;
    /**
     * æ‰©å±•字段,预留给业务系统使用
     */
    private String ext;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,244 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.warm.flow.core.enums.CooperateType;
import org.dromara.workflow.common.constant.FlowConstant;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
 * åŽ†å²ä»»åŠ¡è§†å›¾
 *
 * @author may
 */
@Data
public class FlowHisTaskVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    private Long id;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private Date updateTime;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * åˆ é™¤æ ‡è®°
     */
    private String delFlag;
    /**
     * å¯¹åº”flow_definition表的id
     */
    private Long definitionId;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * æµç¨‹å®žä¾‹è¡¨id
     */
    private Long instanceId;
    /**
     * ä»»åŠ¡è¡¨id
     */
    private Long taskId;
    /**
     * åä½œæ–¹å¼(1审批 2转办 3委派 4会签 5票签 6加签 7减签)
     */
    private Integer cooperateType;
    /**
     * åä½œæ–¹å¼(1审批 2转办 3委派 4会签 5票签 6加签 7减签)
     */
    private String cooperateTypeName;
    /**
     * ä¸šåŠ¡id
     */
    private String businessId;
    /**
     * å¼€å§‹èŠ‚ç‚¹ç¼–ç 
     */
    private String nodeCode;
    /**
     * å¼€å§‹èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * å¼€å§‹èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
     */
    private Integer nodeType;
    /**
     * ç›®æ ‡èŠ‚ç‚¹ç¼–ç 
     */
    private String targetNodeCode;
    /**
     * ç»“束节点名称
     */
    private String targetNodeName;
    /**
     * å®¡æ‰¹è€…
     */
    private String approver;
    /**
     * å®¡æ‰¹è€…
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "approver")
    private String approveName;
    /**
     * åä½œäºº(只有转办、会签、票签、委派)
     */
    private String collaborator;
    /**
     * æƒé™æ ‡è¯† permissionFlag的list形式
     */
    private List<String> permissionList;
    /**
     * è·³è½¬ç±»åž‹ï¼ˆPASS通过 REJECT退回 NONE无动作)
     */
    private String skipType;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String flowStatus;
    /**
     * ä»»åŠ¡çŠ¶æ€
     */
    private String flowTaskStatus;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String flowStatusName;
    /**
     * å®¡æ‰¹æ„è§
     */
    private String message;
    /**
     * ä¸šåŠ¡è¯¦æƒ… å­˜ä¸šåŠ¡ç±»çš„json
     */
    private String ext;
    /**
     * åˆ›å»ºè€…
     */
    private String createBy;
    /**
     * ç”³è¯·äºº
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
    private String createByName;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
    private String categoryName;
    /**
     * å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
     */
    private String formCustom;
    /**
     * å®¡æ‰¹è¡¨å•路径
     */
    private String formPath;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * æµç¨‹ç‰ˆæœ¬å·
     */
    private String version;
    /**
     * è¿è¡Œæ—¶é•¿
     */
    private String runDuration;
    /**
     * è®¾ç½®åˆ›å»ºæ—¶é—´å¹¶è®¡ç®—任务运行时长
     *
     * @param createTime åˆ›å»ºæ—¶é—´
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
        updateRunDuration();
    }
    /**
     * è®¾ç½®æ›´æ–°æ—¶é—´å¹¶è®¡ç®—任务运行时长
     *
     * @param updateTime æ›´æ–°æ—¶é—´
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
        updateRunDuration();
    }
    /**
     * æ›´æ–°è¿è¡Œæ—¶é•¿
     */
    private void updateRunDuration() {
        // å¦‚果创建时间和更新时间均不为空,计算它们之间的时长
        if (this.updateTime != null && this.createTime != null) {
            this.runDuration = DateUtils.getTimeDifference(this.updateTime, this.createTime);
        }
    }
    /**
     * è®¾ç½®åä½œæ–¹å¼ï¼Œå¹¶é€šè¿‡åä½œæ–¹å¼èŽ·å–åç§°
     */
    public void setCooperateType(Integer cooperateType) {
        this.cooperateType = cooperateType;
        this.cooperateTypeName = CooperateType.getValueByKey(cooperateType);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,137 @@
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.dromara.workflow.common.constant.FlowConstant;
import java.util.Date;
/**
 * æµç¨‹å®žä¾‹è§†å›¾
 *
 * @author may
 */
@Data
public class FlowInstanceVo {
    private Long id;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private Date updateTime;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * åˆ é™¤æ ‡è®°
     */
    private String delFlag;
    /**
     * å¯¹åº”flow_definition表的id
     */
    private Long definitionId;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * ä¸šåŠ¡id
     */
    private String businessId;
    /**
     * èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
     */
    private Integer nodeType;
    /**
     * æµç¨‹èŠ‚ç‚¹ç¼–ç    æ¯ä¸ªæµç¨‹çš„nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
     */
    private String nodeCode;
    /**
     * æµç¨‹èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * æµç¨‹å˜é‡
     */
    private String variable;
    /**
     * æµç¨‹çŠ¶æ€ï¼ˆ0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 3自动通过 8已完成 9已退回 10失效)
     */
    private String flowStatus;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String flowStatusName;
    /**
     * æµç¨‹æ¿€æ´»çŠ¶æ€ï¼ˆ0挂起 1激活)
     */
    private Integer activityStatus;
    /**
     * å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
     */
    private String formCustom;
    /**
     * å®¡æ‰¹è¡¨å•路径
     */
    private String formPath;
    /**
     * æ‰©å±•字段,预留给业务系统使用
     */
    private String ext;
    /**
     * æµç¨‹å®šä¹‰ç‰ˆæœ¬
     */
    private String version;
    /**
     * åˆ›å»ºè€…
     */
    private String createBy;
    /**
     * ç”³è¯·äºº
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
    private String createByName;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
    private String categoryName;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,176 @@
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.dromara.warm.flow.core.entity.User;
import org.dromara.workflow.common.constant.FlowConstant;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * ä»»åŠ¡è§†å›¾
 *
 * @author may
 */
@Data
public class FlowTaskVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    private Long id;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private Date updateTime;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * åˆ é™¤æ ‡è®°
     */
    private String delFlag;
    /**
     * å¯¹åº”flow_definition表的id
     */
    private Long definitionId;
    /**
     * æµç¨‹å®žä¾‹è¡¨id
     */
    private Long instanceId;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String flowName;
    /**
     * ä¸šåŠ¡id
     */
    private String businessId;
    /**
     * èŠ‚ç‚¹ç¼–ç 
     */
    private String nodeCode;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
     */
    private Integer nodeType;
    /**
     * æƒé™æ ‡è¯† permissionFlag的list形式
     */
    private List<String> permissionList;
    /**
     * æµç¨‹ç”¨æˆ·åˆ—表
     */
    private List<User> userList;
    /**
     * å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
     */
    private String formCustom;
    /**
     * å®¡æ‰¹è¡¨å•
     */
    private String formPath;
    /**
     * æµç¨‹å®šä¹‰ç¼–码
     */
    private String flowCode;
    /**
     * æµç¨‹ç‰ˆæœ¬å·
     */
    private String version;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String flowStatus;
    /**
     * æµç¨‹åˆ†ç±»id
     */
    private String category;
    /**
     * æµç¨‹åˆ†ç±»åç§°
     */
    @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
    private String categoryName;
    /**
     * æµç¨‹çŠ¶æ€
     */
    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "flowStatus", other = "wf_business_status")
    private String flowStatusName;
    /**
     * åŠžç†äººç±»åž‹
     */
    private String type;
    /**
     * åŠžç†äººids
     */
    private String assigneeIds;
    /**
     * åŠžç†äººåç§°
     */
    private String assigneeNames;
    /**
     * æŠ„送人id
     */
    private String processedBy;
    /**
     * æŠ„送人名称
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "processedBy")
    private String processedByName;
    /**
     * æµç¨‹ç­¾ç½²æ¯”例值 å¤§äºŽ0为票签,会签
     */
    private BigDecimal nodeRatio;
    /**
     * ç”³è¯·äººid
     */
    private String createBy;
    /**
     * ç”³è¯·äººåç§°
     */
    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
    private String createByName;
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowVariableVo.java
ÎļþÃû´Ó ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/VariableVo.java ÐÞ¸Ä
@@ -11,7 +11,7 @@
 * @author may
 */
@Data
public class VariableVo implements Serializable {
public class FlowVariableVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfDefinitionConfigVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfFormManageVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfNodeConfigVo.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/handler/FlowProcessEventHandler.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/handler/TaskTimeoutJobHandler.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package org.dromara.workflow.handler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.event.ProcessDeleteEvent;
import org.dromara.common.core.domain.event.ProcessEvent;
import org.dromara.common.core.domain.event.ProcessTaskEvent;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * æµç¨‹ç›‘听服务
 *
 * @author may
 * @date 2024-06-02
 */
@ConditionalOnEnable
@Slf4j
@Component
public class FlowProcessEventHandler {
    /**
     * æ€»ä½“流程监听(例如: è‰ç¨¿ï¼Œæ’¤é”€ï¼Œé€€å›žï¼Œä½œåºŸï¼Œç»ˆæ­¢ï¼Œå·²å®Œæˆç­‰)
     *
     * @param flowCode   æµç¨‹å®šä¹‰ç¼–码
     * @param businessId ä¸šåŠ¡id
     * @param status     çŠ¶æ€
     * @param submit     å½“为true时为申请人节点办理
     */
    public void processHandler(String flowCode, String businessId, String status, Map<String, Object> params, boolean submit) {
        String tenantId = TenantHelper.getTenantId();
        log.info("发布流程事件,租户ID: {}, æµç¨‹çŠ¶æ€: {}, æµç¨‹ç¼–码: {}, ä¸šåŠ¡ID: {}, æ˜¯å¦ç”³è¯·äººèŠ‚ç‚¹åŠžç†: {}", tenantId, status, flowCode, businessId, submit);
        ProcessEvent processEvent = new ProcessEvent();
        processEvent.setTenantId(tenantId);
        processEvent.setFlowCode(flowCode);
        processEvent.setBusinessId(businessId);
        processEvent.setStatus(status);
        processEvent.setParams(params);
        processEvent.setSubmit(submit);
        SpringUtils.context().publishEvent(processEvent);
    }
    /**
     * æ‰§è¡ŒåŠžç†ä»»åŠ¡ç›‘å¬
     *
     * @param flowCode   æµç¨‹å®šä¹‰ç¼–码
     * @param nodeCode   å®¡æ‰¹èŠ‚ç‚¹ç¼–ç 
     * @param taskId     ä»»åŠ¡id
     * @param businessId ä¸šåŠ¡id
     */
    public void processTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) {
        String tenantId = TenantHelper.getTenantId();
        log.info("发布流程任务事件, ç§Ÿæˆ·ID: {}, æµç¨‹ç¼–码: {}, èŠ‚ç‚¹ç¼–ç : {}, ä»»åŠ¡ID: {}, ä¸šåŠ¡ID: {}", tenantId, flowCode, nodeCode, taskId, businessId);
        ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
        processTaskEvent.setTenantId(tenantId);
        processTaskEvent.setFlowCode(flowCode);
        processTaskEvent.setNodeCode(nodeCode);
        processTaskEvent.setTaskId(taskId);
        processTaskEvent.setBusinessId(businessId);
        SpringUtils.context().publishEvent(processTaskEvent);
    }
    /**
     * åˆ é™¤æµç¨‹ç›‘听
     *
     * @param flowCode    æµç¨‹å®šä¹‰ç¼–码
     * @param businessId  ä¸šåŠ¡ID
     */
    public void processDeleteHandler(String flowCode, String businessId) {
        String tenantId = TenantHelper.getTenantId();
        log.info("发布删除流程事件, ç§Ÿæˆ·ID: {}, æµç¨‹ç¼–码: {}, ä¸šåŠ¡ID: {}", tenantId, flowCode, businessId);
        ProcessDeleteEvent processDeleteEvent = new ProcessDeleteEvent();
        processDeleteEvent.setTenantId(tenantId);
        processDeleteEvent.setFlowCode(flowCode);
        processDeleteEvent.setBusinessId(businessId);
        SpringUtils.context().publishEvent(processDeleteEvent);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package org.dromara.workflow.handler;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.TaskAssigneeEnum;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.handler.PermissionHandler;
import org.dromara.warm.flow.core.service.impl.TaskServiceImpl;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * åŠžç†äººæƒé™å¤„ç†å™¨
 *
 * @author AprilWind
 */
@ConditionalOnEnable
@RequiredArgsConstructor
@Component
@Slf4j
public class WorkflowPermissionHandler implements PermissionHandler {
    /**
     * å®¡æ‰¹å‰èŽ·å–å½“å‰åŠžç†äººï¼ŒåŠžç†æ—¶ä¼šæ ¡éªŒçš„è¯¥æƒé™é›†åˆ
     * åŽç»­åœ¨{@link TaskServiceImpl#checkAuth(Task, FlowParams)} ä¸­è°ƒç”¨
     * è¿”回当前用户权限集合
     */
    @Override
    public List<String> permissions() {
        LoginUser loginUser = LoginHelper.getLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            return new ArrayList<>();
        }
        // ä½¿ç”¨ä¸€ä¸ªæµæ¥æž„建权限列表
        return Stream.of(
                // è§’色权限前缀
                loginUser.getRoles().stream()
                    .map(role -> TaskAssigneeEnum.ROLE.getCode() + role.getRoleId()),
                // å²—位权限前缀
                Stream.ofNullable(loginUser.getPosts())
                    .flatMap(Collection::stream)
                    .map(post -> TaskAssigneeEnum.POST.getCode() + post.getPostId()),
                // ç”¨æˆ·å’Œéƒ¨é—¨æƒé™
                Stream.of(String.valueOf(loginUser.getUserId()),
                    TaskAssigneeEnum.DEPT.getCode() + loginUser.getDeptId()
                )
            )
            .flatMap(stream -> stream)
            .collect(Collectors.toList());
    }
    /**
     * èŽ·å–å½“å‰åŠžç†äºº
     *
     * @return å½“前办理人
     */
    @Override
    public String getHandler() {
        return LoginHelper.getUserIdStr();
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
package org.dromara.workflow.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.Definition;
import org.dromara.warm.flow.core.entity.Instance;
import org.dromara.warm.flow.core.entity.Task;
import org.dromara.warm.flow.core.listener.GlobalListener;
import org.dromara.warm.flow.core.listener.ListenerVariable;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.handler.FlowProcessEventHandler;
import org.dromara.workflow.service.IFlwInstanceService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * å…¨å±€ä»»åŠ¡åŠžç†ç›‘å¬
 *
 * @author may
 */
@ConditionalOnEnable
@Component
@Slf4j
@RequiredArgsConstructor
public class WorkflowGlobalListener implements GlobalListener {
    private final IFlwTaskService taskService;
    private final IFlwInstanceService instanceService;
    private final FlowProcessEventHandler flowProcessEventHandler;
    /**
     * åˆ›å»ºç›‘听器,任务创建时执行
     *
     * @param listenerVariable ç›‘听器变量
     */
    @Override
    public void create(ListenerVariable listenerVariable) {
        Instance instance = listenerVariable.getInstance();
        Definition definition = listenerVariable.getDefinition();
        String businessId = instance.getBusinessId();
        String flowStatus = instance.getFlowStatus();
        Task task = listenerVariable.getTask();
        if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) {
            // åˆ¤æ–­æµç¨‹çŠ¶æ€ï¼ˆå‘å¸ƒå®¡æ‰¹ä¸­äº‹ä»¶ï¼‰
            flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId);
        }
    }
    /**
     * å¼€å§‹ç›‘听器,任务开始办理时执行
     *
     * @param listenerVariable ç›‘听器变量
     */
    @Override
    public void start(ListenerVariable listenerVariable) {
    }
    /**
     * åˆ†æ´¾ç›‘听器,动态修改代办任务信息
     *
     * @param listenerVariable ç›‘听器变量
     */
    @Override
    public void assignment(ListenerVariable listenerVariable) {
    }
    /**
     * å®Œæˆç›‘听器,当前任务完成后执行
     *
     * @param listenerVariable ç›‘听器变量
     */
    @Override
    public void finish(ListenerVariable listenerVariable) {
        Instance instance = listenerVariable.getInstance();
        Definition definition = listenerVariable.getDefinition();
        String businessId = instance.getBusinessId();
        String flowStatus = instance.getFlowStatus();
        Map<String, Object> params = new HashMap<>();
        FlowParams flowParams = listenerVariable.getFlowParams();
        if (ObjectUtil.isNotNull(flowParams)) {
            // åŽ†å²ä»»åŠ¡æ‰©å±•(通常为附件)
            params.put("hisTaskExt", flowParams.getHisTaskExt());
            // åŠžç†äºº
            params.put("handler", flowParams.getHandler());
            // åŠžç†æ„è§
            params.put("message", flowParams.getMessage());
        }
        // åˆ¤æ–­æµç¨‹çŠ¶æ€ï¼ˆå‘å¸ƒï¼šæ’¤é”€ï¼Œé€€å›žï¼Œä½œåºŸï¼Œç»ˆæ­¢ï¼Œå·²å®Œæˆäº‹ä»¶ï¼‰
        String status = determineFlowStatus(instance, flowStatus);
        if (StringUtils.isNotBlank(status)) {
            flowProcessEventHandler.processHandler(definition.getFlowCode(), businessId, status, params, false);
        }
    }
    /**
     * æ ¹æ®æµç¨‹å®žä¾‹å’Œå½“前流程状态确定最终状态
     *
     * @param instance   æµç¨‹å®žä¾‹
     * @param flowStatus æµç¨‹å®žä¾‹å½“前状态
     * @return æµç¨‹æœ€ç»ˆçŠ¶æ€
     */
    private String determineFlowStatus(Instance instance, String flowStatus) {
        if (StringUtils.isNotBlank(flowStatus) && BusinessStatusEnum.initialState(flowStatus)) {
            log.info("流程实例当前状态: {}", flowStatus);
            return flowStatus;
        } else {
            Long instanceId = instance.getId();
            List<FlowTask> flowTasks = taskService.selectByInstId(instanceId);
            if (CollUtil.isEmpty(flowTasks)) {
                String status = BusinessStatusEnum.FINISH.getStatus();
                // æ›´æ–°æµç¨‹çŠ¶æ€ä¸ºå·²å®Œæˆ
                instanceService.updateStatus(instanceId, status);
                log.info("流程已结束,状态更新为: {}", status);
                return status;
            }
            return null;
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package org.dromara.workflow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.common.mybatis.helper.DataBaseHelper;
import org.dromara.workflow.domain.FlowCategory;
import org.dromara.workflow.domain.vo.FlowCategoryVo;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * æµç¨‹åˆ†ç±»Mapper接口
 *
 * @author may
 * @date 2023-06-27
 */
public interface FlwCategoryMapper extends BaseMapperPlus<FlowCategory, FlowCategoryVo> {
    /**
     * ç»Ÿè®¡æŒ‡å®šæµç¨‹åˆ†ç±»ID的分类数量
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return è¯¥æµç¨‹åˆ†ç±»ID的分类数量
     */
    @DataPermission({
        @DataColumn(key = "deptName", value = "createDept")
    })
    long countCategoryById(Long categoryId);
    /**
     * æ ¹æ®çˆ¶æµç¨‹åˆ†ç±»ID查询其所有子流程分类的列表
     *
     * @param parentId çˆ¶æµç¨‹åˆ†ç±»ID
     * @return åŒ…含子流程分类的列表
     */
    default List<FlowCategory> selectListByParentId(Long parentId) {
        return this.selectList(new LambdaQueryWrapper<FlowCategory>()
            .select(FlowCategory::getCategoryId)
            .apply(DataBaseHelper.findInSet(parentId, "ancestors")));
    }
    /**
     * æ ¹æ®çˆ¶æµç¨‹åˆ†ç±»ID查询包括父ID及其所有子流程分类ID的列表
     *
     * @param parentId çˆ¶æµç¨‹åˆ†ç±»ID
     * @return åŒ…含父ID和子流程分类ID的列表
     */
    default List<Long> selectCategoryIdsByParentId(Long parentId) {
        return Stream.concat(
            this.selectListByParentId(parentId).stream()
                .map(FlowCategory::getCategoryId),
            Stream.of(parentId)
        ).collect(Collectors.toList());
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.dromara.workflow.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.workflow.domain.bo.FlowInstanceBo;
import org.dromara.workflow.domain.vo.FlowInstanceVo;
/**
 * å®žä¾‹ä¿¡æ¯Mapper接口
 *
 * @author may
 * @date 2024-03-02
 */
public interface FlwInstanceMapper {
    /**
     * æµç¨‹å®žä¾‹ä¿¡æ¯
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<FlowInstanceVo> selectInstanceList(@Param("page") Page<FlowInstanceVo> page, @Param(Constants.WRAPPER) Wrapper<FlowInstanceBo> queryWrapper);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package org.dromara.workflow.mapper;
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.workflow.domain.bo.FlowTaskBo;
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
import org.dromara.workflow.domain.vo.FlowTaskVo;
import java.util.List;
/**
 * ä»»åŠ¡ä¿¡æ¯Mapper接口
 *
 * @author may
 * @date 2024-03-02
 */
public interface FlwTaskMapper {
    /**
     * èŽ·å–å¾…åŠžä¿¡æ¯
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<FlowTaskVo> getListRunTask(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) Wrapper<FlowTaskBo> queryWrapper);
    /**
     * èŽ·å–å¾…åŠžä¿¡æ¯
     *
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    List<FlowTaskVo> getListRunTask(@Param(Constants.WRAPPER) Wrapper<FlowTaskBo> queryWrapper);
    /**
     * èŽ·å–å·²åŠž
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<FlowHisTaskVo> getListFinishTask(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) Wrapper<FlowTaskBo> queryWrapper);
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param page         åˆ†é¡µ
     * @param queryWrapper æ¡ä»¶
     * @return ç»“æžœ
     */
    Page<FlowTaskVo> getTaskCopyByPage(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) QueryWrapper<FlowTaskBo> queryWrapper);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfDefinitionConfigMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfFormManageMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfNodeConfigMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfTaskBackNodeMapper.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
package org.dromara.workflow.service;
import cn.hutool.core.lang.tree.Tree;
import org.dromara.workflow.domain.bo.FlowCategoryBo;
import org.dromara.workflow.domain.vo.FlowCategoryVo;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»Service接口
 *
 * @author may
 */
public interface IFlwCategoryService {
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»
     *
     * @param categoryId ä¸»é”®
     * @return æµç¨‹åˆ†ç±»
     */
    FlowCategoryVo queryById(Long categoryId);
    /**
     * æ ¹æ®æµç¨‹åˆ†ç±»ID查询流程分类名称
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return æµç¨‹åˆ†ç±»åç§°
     */
    String selectCategoryNameById(Long categoryId);
    /**
     * æŸ¥è¯¢ç¬¦åˆæ¡ä»¶çš„æµç¨‹åˆ†ç±»åˆ—表
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶
     * @return æµç¨‹åˆ†ç±»åˆ—表
     */
    List<FlowCategoryVo> queryList(FlowCategoryBo bo);
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»æ ‘结构信息
     *
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return æµç¨‹åˆ†ç±»æ ‘信息集合
     */
    List<Tree<String>> selectCategoryTreeList(FlowCategoryBo category);
    /**
     * æ ¡éªŒæµç¨‹åˆ†ç±»æ˜¯å¦æœ‰æ•°æ®æƒé™
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     */
    void checkCategoryDataScope(Long categoryId);
    /**
     * æ ¡éªŒæµç¨‹åˆ†ç±»åç§°æ˜¯å¦å”¯ä¸€
     *
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return ç»“æžœ
     */
    boolean checkCategoryNameUnique(FlowCategoryBo category);
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»æ˜¯å¦å­˜åœ¨æµç¨‹å®šä¹‰
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return ç»“æžœ true å­˜åœ¨ false ä¸å­˜åœ¨
     */
    boolean checkCategoryExistDefinition(Long categoryId);
    /**
     * æ˜¯å¦å­˜åœ¨æµç¨‹åˆ†ç±»å­èŠ‚ç‚¹
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return ç»“æžœ
     */
    boolean hasChildByCategoryId(Long categoryId);
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     *
     * @param bo æµç¨‹åˆ†ç±»
     * @return æ˜¯å¦æ–°å¢žæˆåŠŸ
     */
    int insertByBo(FlowCategoryBo bo);
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     *
     * @param bo æµç¨‹åˆ†ç±»
     * @return æ˜¯å¦ä¿®æ”¹æˆåŠŸ
     */
    int updateByBo(FlowCategoryBo bo);
    /**
     * åˆ é™¤æµç¨‹åˆ†ç±»ä¿¡æ¯
     *
     * @param categoryId ä¸»é”®
     * @return æ˜¯å¦åˆ é™¤æˆåŠŸ
     */
    int deleteWithValidById(Long categoryId);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
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.warm.flow.orm.entity.FlowDefinition;
import org.dromara.workflow.domain.vo.FlowDefinitionVo;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
 * æµç¨‹å®šä¹‰ æœåС层
 *
 * @author may
 */
public interface IFlwDefinitionService {
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰åˆ—表
     *
     * @param flowDefinition å‚æ•°
     * @param pageQuery      åˆ†é¡µ
     * @return è¿”回分页列表
     */
    TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æœªå‘布的流程定义列表
     *
     * @param flowDefinition å‚æ•°
     * @param pageQuery      åˆ†é¡µ
     * @return è¿”回分页列表
     */
    TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery);
    /**
     * å‘布流程定义
     *
     * @param id æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean publish(Long id);
    /**
     * å¯¼å‡ºæµç¨‹å®šä¹‰
     *
     * @param id       æµç¨‹å®šä¹‰id
     * @param response å“åº”
     * @throws IOException å¼‚常
     */
    void exportDef(Long id, HttpServletResponse response) throws IOException;
    /**
     * å¯¼å…¥æµç¨‹å®šä¹‰
     *
     * @param file     æ–‡ä»¶
     * @param category åˆ†ç±»
     * @return ç»“æžœ
     */
    boolean importJson(MultipartFile file, String category);
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     *
     * @param ids æµç¨‹å®šä¹‰id
     * @return ç»“æžœ
     */
    boolean removeDef(List<Long> ids);
    /**
     * æ–°å¢žç§Ÿæˆ·æµç¨‹å®šä¹‰
     *
     * @param tenantId ç§Ÿæˆ·id
     */
    void syncDef(String tenantId);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
package org.dromara.workflow.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.workflow.domain.bo.FlowCancelBo;
import org.dromara.workflow.domain.bo.FlowInstanceBo;
import org.dromara.workflow.domain.bo.FlowInvalidBo;
import org.dromara.workflow.domain.vo.FlowInstanceVo;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹å®žä¾‹ æœåС层
 *
 * @author may
 */
public interface IFlwInstanceService {
    /**
     * åˆ†é¡µæŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery);
    /**
     * åˆ†é¡µæŸ¥è¯¢å·²ç»“束的流程实例
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery);
    /**
     * æ ¹æ®ä¸šåŠ¡id查询流程实例详细信息
     *
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    FlowInstanceVo queryByBusinessId(Long businessId);
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询流程实例
     *
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    FlowInstance selectInstByBusinessId(String businessId);
    /**
     * æŒ‰ç…§å®žä¾‹id查询流程实例
     *
     * @param instanceId å®žä¾‹id
     * @return ç»“æžœ
     */
    FlowInstance selectInstById(Long instanceId);
    /**
     * æŒ‰ç…§å®žä¾‹id查询流程实例
     *
     * @param instanceIds å®žä¾‹id
     * @return ç»“æžœ
     */
    List<FlowInstance> selectInstListByIdList(List<Long> instanceIds);
    /**
     * æŒ‰ç…§ä¸šåŠ¡id删除流程实例
     *
     * @param businessIds ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    boolean deleteByBusinessIds(List<Long> businessIds);
    /**
     * æŒ‰ç…§å®žä¾‹id删除流程实例
     *
     * @param instanceIds å®žä¾‹id
     * @return ç»“æžœ
     */
    boolean deleteByInstanceIds(List<Long> instanceIds);
    /**
     * æ’¤é”€æµç¨‹
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    boolean cancelProcessApply(FlowCancelBo bo);
    /**
     * èŽ·å–å½“å‰ç™»é™†äººå‘èµ·çš„æµç¨‹å®žä¾‹
     *
     * @param instanceBo æµç¨‹å®žä¾‹
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo instanceBo, PageQuery pageQuery);
    /**
     * èŽ·å–æµç¨‹å›¾,流程记录
     *
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    Map<String, Object> flowImage(String businessId);
    /**
     * æŒ‰ç…§å®žä¾‹id更新状态
     *
     * @param instanceId å®žä¾‹id
     * @param status     çŠ¶æ€
     */
    void updateStatus(Long instanceId, String status);
    /**
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param instanceId å®žä¾‹id
     * @return ç»“æžœ
     */
    Map<String, Object> instanceVariable(Long instanceId);
    /**
     * è®¾ç½®æµç¨‹å˜é‡
     *
     * @param instanceId å®žä¾‹id
     * @param variable   æµç¨‹å˜é‡
     */
    void setVariable(Long instanceId, Map<String, Object> variable);
    /**
     * æŒ‰ä»»åŠ¡id查询实例
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    FlowInstance selectByTaskId(Long taskId);
    /**
     * æŒ‰ä»»åŠ¡id查询实例
     *
     * @param taskIdList ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<FlowInstance> selectByTaskIdList(List<Long> taskIdList);
    /**
     * ä½œåºŸæµç¨‹
     *
     * @param bo æµç¨‹å®žä¾‹
     * @return ç»“æžœ
     */
    boolean processInvalid(FlowInvalidBo bo);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package org.dromara.workflow.service;
import org.dromara.common.core.domain.dto.UserDTO;
import java.util.List;
/**
 * æµç¨‹è®¾è®¡å™¨-获取办理人
 *
 * @author AprilWind
 */
public interface IFlwTaskAssigneeService {
    /**
     * æ ¹æ®å­˜å‚¨æ ‡è¯†ç¬¦ï¼ˆstorageId)解析分配类型和ID,并获取对应的用户列表
     *
     * @param storageId åŒ…含分配类型和ID的字符串(例如 "user:123" æˆ– "role:456")
     * @return ä¸Žåˆ†é…ç±»åž‹å’ŒID匹配的用户列表,如果格式无效则返回空列表
     */
    List<UserDTO> fetchUsersByStorageId(String storageId);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,191 @@
package org.dromara.workflow.service;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.warm.flow.core.entity.Node;
import org.dromara.warm.flow.orm.entity.FlowHisTask;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
import org.dromara.workflow.domain.vo.FlowTaskVo;
import java.util.List;
import java.util.Map;
/**
 * ä»»åŠ¡ æœåС层
 *
 * @author may
 */
public interface IFlwTaskService {
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     * @return ç»“æžœ
     */
    StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo);
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     * @return ç»“æžœ
     */
    boolean completeTask(CompleteTaskBo completeTaskBo);
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前租户所有待办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å¾…办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å·²åŠžä»»åŠ¡
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     * @return ç»“æžœ
     */
    TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery);
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
     *
     * @param taskIdList ä»»åŠ¡id
     * @param userId     ç”¨æˆ·id
     * @return ç»“æžœ
     */
    boolean updateAssignee(List<Long> taskIdList, String userId);
    /**
     * é©³å›žå®¡æ‰¹
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    boolean backProcess(BackProcessBo bo);
    /**
     * èŽ·å–å¯é©³å›žçš„å‰ç½®èŠ‚ç‚¹
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param nowNodeCode  å½“前节点
     * @return ç»“æžœ
     */
    List<Node> getBackTaskNode(Long definitionId, String nowNodeCode);
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param bo å‚æ•°
     * @return ç»“æžœ
     */
    boolean terminationTask(FlowTerminationBo bo);
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskIdList ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<FlowTask> selectByIdList(List<Long> taskIdList);
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    FlowTaskVo selectById(Long taskId);
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskIdList ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<FlowHisTask> selectHisTaskByIdList(List<Long> taskIdList);
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    FlowHisTask selectHisTaskById(Long taskId);
    /**
     * æŒ‰ç…§å®žä¾‹id查询任务
     *
     * @param instanceIdList æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    List<FlowTask> selectByInstIdList(List<Long> instanceIdList);
    /**
     * æŒ‰ç…§å®žä¾‹id查询任务
     *
     * @param instanceId æµç¨‹å®žä¾‹id
     * @return ç»“æžœ
     */
    List<FlowTask> selectByInstId(Long instanceId);
    /**
     * ä»»åŠ¡æ“ä½œ
     *
     * @param bo            å‚æ•°
     * @param taskOperation æ“ä½œç±»åž‹ï¼Œå§”æ´¾ delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
     * @return ç»“æžœ
     */
    boolean taskOperation(TaskOperationBo bo, String taskOperation);
    /**
     * èŽ·å–ä»»åŠ¡æ‰€æœ‰åŠžç†äºº
     *
     * @param taskIdList ä»»åŠ¡id
     * @return ç»“æžœ
     */
    Map<Long, List<UserDTO>> currentTaskAllUser(List<Long> taskIdList);
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡çš„æ‰€æœ‰åŠžç†äºº
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    List<UserDTO> currentTaskAllUser(Long taskId);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
@@ -5,7 +5,6 @@
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import java.util.Collection;
import java.util.List;
/**
@@ -44,5 +43,5 @@
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤è¯·å‡ä¿¡æ¯
     */
    Boolean deleteWithValidByIds(Collection<Long> ids);
    Boolean deleteWithValidByIds(List<Long> ids);
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfDefinitionConfigService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfFormManageService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfNodeConfigService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfTaskBackNodeService.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.translation.annotation.TranslationType;
import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.service.IFlwCategoryService;
import org.springframework.stereotype.Service;
/**
 * æµç¨‹åˆ†ç±»åç§°ç¿»è¯‘实现
 *
 * @author AprilWind
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
@TranslationType(type = FlowConstant.CATEGORY_ID_TO_NAME)
public class CategoryNameTranslationImpl implements TranslationInterface<String> {
    private final IFlwCategoryService flwCategoryService;
    @Override
    public String translation(Object key, String other) {
        Long id = null;
        if (key instanceof String categoryId) {
            id = Convert.toLong(categoryId);
        } else if (key instanceof Long categoryId) {
            id = categoryId;
        }
        return flwCategoryService.selectCategoryNameById(id);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,269 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.*;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.warm.flow.core.service.DefService;
import org.dromara.warm.flow.orm.entity.FlowDefinition;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.FlowCategory;
import org.dromara.workflow.domain.bo.FlowCategoryBo;
import org.dromara.workflow.domain.vo.FlowCategoryVo;
import org.dromara.workflow.mapper.FlwCategoryMapper;
import org.dromara.workflow.service.IFlwCategoryService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»Service业务层处理
 *
 * @author may
 */
@ConditionalOnEnable
@RequiredArgsConstructor
@Service
public class FlwCategoryServiceImpl implements IFlwCategoryService {
    private final DefService defService;
    private final FlwCategoryMapper baseMapper;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»
     *
     * @param categoryId ä¸»é”®
     * @return æµç¨‹åˆ†ç±»
     */
    @Override
    public FlowCategoryVo queryById(Long categoryId) {
        FlowCategoryVo category = baseMapper.selectVoById(categoryId);
        if (ObjectUtil.isNull(category)) {
            return null;
        }
        FlowCategoryVo parentCategory = baseMapper.selectVoOne(new LambdaQueryWrapper<FlowCategory>()
            .select(FlowCategory::getCategoryName).eq(FlowCategory::getCategoryId, category.getParentId()));
        category.setParentName(ObjectUtils.notNullGetter(parentCategory, FlowCategoryVo::getCategoryName));
        return category;
    }
    /**
     * æ ¹æ®æµç¨‹åˆ†ç±»ID查询流程分类名称
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return æµç¨‹åˆ†ç±»åç§°
     */
    @Cacheable(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#categoryId")
    @Override
    public String selectCategoryNameById(Long categoryId) {
        if (ObjectUtil.isNull(categoryId)) {
            return null;
        }
        FlowCategory category = baseMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
            .select(FlowCategory::getCategoryName).eq(FlowCategory::getCategoryId, categoryId));
        return ObjectUtils.notNullGetter(category, FlowCategory::getCategoryName);
    }
    /**
     * æŸ¥è¯¢ç¬¦åˆæ¡ä»¶çš„æµç¨‹åˆ†ç±»åˆ—表
     *
     * @param bo æŸ¥è¯¢æ¡ä»¶
     * @return æµç¨‹åˆ†ç±»åˆ—表
     */
    @Override
    public List<FlowCategoryVo> queryList(FlowCategoryBo bo) {
        LambdaQueryWrapper<FlowCategory> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»æ ‘结构信息
     *
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return æµç¨‹åˆ†ç±»æ ‘信息集合
     */
    @Override
    public List<Tree<String>> selectCategoryTreeList(FlowCategoryBo category) {
        LambdaQueryWrapper<FlowCategory> lqw = buildQueryWrapper(category);
        List<FlowCategoryVo> categorys = baseMapper.selectVoList(lqw);
        if (CollUtil.isEmpty(categorys)) {
            return CollUtil.newArrayList();
        }
        // èŽ·å–å½“å‰åˆ—è¡¨ä¸­æ¯ä¸€ä¸ªèŠ‚ç‚¹çš„parentId,然后在列表中查找是否有id与其parentId对应,若无对应,则表明此时节点列表中,该节点在当前列表中属于顶级节点
        List<Tree<String>> treeList = CollUtil.newArrayList();
        for (FlowCategoryVo d : categorys) {
            String parentId = d.getParentId().toString();
            FlowCategoryVo categoryVo = StreamUtils.findFirst(categorys, it -> it.getCategoryId().toString().equals(parentId));
            if (ObjectUtil.isNull(categoryVo)) {
                List<Tree<String>> trees = TreeBuildUtils.build(categorys, parentId, (dept, tree) ->
                    tree.setId(dept.getCategoryId().toString())
                        .setParentId(dept.getParentId().toString())
                        .setName(dept.getCategoryName())
                        .setWeight(dept.getOrderNum()));
                Tree<String> tree = StreamUtils.findFirst(trees, it -> it.getId().equals(d.getCategoryId().toString()));
                treeList.add(tree);
            }
        }
        return treeList;
    }
    /**
     * æ ¡éªŒæµç¨‹åˆ†ç±»æ˜¯å¦æœ‰æ•°æ®æƒé™
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     */
    @Override
    public void checkCategoryDataScope(Long categoryId) {
        if (ObjectUtil.isNull(categoryId)) {
            return;
        }
        if (LoginHelper.isSuperAdmin()) {
            return;
        }
        if (baseMapper.countCategoryById(categoryId) == 0) {
            throw new ServiceException("没有权限访问流程分类数据!");
        }
    }
    /**
     * æ ¡éªŒæµç¨‹åˆ†ç±»åç§°æ˜¯å¦å”¯ä¸€
     *
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public boolean checkCategoryNameUnique(FlowCategoryBo category) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<FlowCategory>()
            .eq(FlowCategory::getCategoryName, category.getCategoryName())
            .eq(FlowCategory::getParentId, category.getParentId())
            .ne(ObjectUtil.isNotNull(category.getCategoryId()), FlowCategory::getCategoryId, category.getCategoryId()));
        return !exist;
    }
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»æ˜¯å¦å­˜åœ¨æµç¨‹å®šä¹‰
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return ç»“æžœ true å­˜åœ¨ false ä¸å­˜åœ¨
     */
    @Override
    public boolean checkCategoryExistDefinition(Long categoryId) {
        FlowDefinition definition = new FlowDefinition();
        definition.setCategory(categoryId.toString());
        return defService.exists(definition);
    }
    /**
     * æ˜¯å¦å­˜åœ¨æµç¨‹åˆ†ç±»å­èŠ‚ç‚¹
     *
     * @param categoryId æµç¨‹åˆ†ç±»ID
     * @return ç»“æžœ
     */
    @Override
    public boolean hasChildByCategoryId(Long categoryId) {
        return baseMapper.exists(new LambdaQueryWrapper<FlowCategory>()
            .eq(FlowCategory::getParentId, categoryId));
    }
    private LambdaQueryWrapper<FlowCategory> buildQueryWrapper(FlowCategoryBo bo) {
        LambdaQueryWrapper<FlowCategory> lqw = Wrappers.lambdaQuery();
        lqw.eq(FlowCategory::getDelFlag, SystemConstants.NORMAL);
        lqw.eq(ObjectUtil.isNotNull(bo.getCategoryId()), FlowCategory::getCategoryId, bo.getCategoryId());
        lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), FlowCategory::getParentId, bo.getParentId());
        lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), FlowCategory::getCategoryName, bo.getCategoryName());
        lqw.orderByAsc(FlowCategory::getAncestors);
        lqw.orderByAsc(FlowCategory::getParentId);
        lqw.orderByAsc(FlowCategory::getOrderNum);
        lqw.orderByAsc(FlowCategory::getCategoryId);
        return lqw;
    }
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     *
     * @param bo æµç¨‹åˆ†ç±»
     * @return æ˜¯å¦æ–°å¢žæˆåŠŸ
     */
    @Override
    public int insertByBo(FlowCategoryBo bo) {
        FlowCategory info = baseMapper.selectById(bo.getParentId());
        FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class);
        category.setAncestors(info.getAncestors() + StringUtils.SEPARATOR + category.getParentId());
        return baseMapper.insert(category);
    }
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     *
     * @param bo æµç¨‹åˆ†ç±»
     * @return æ˜¯å¦ä¿®æ”¹æˆåŠŸ
     */
    @CacheEvict(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#bo.categoryId")
    @Override
    public int updateByBo(FlowCategoryBo bo) {
        FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class);
        FlowCategory oldCategory = baseMapper.selectById(category.getCategoryId());
        if (ObjectUtil.isNull(oldCategory)) {
            throw new ServiceException("流程分类不存在,无法修改");
        }
        if (!oldCategory.getParentId().equals(category.getParentId())) {
            // å¦‚果是新父流程分类 åˆ™æ ¡éªŒæ˜¯å¦å…·æœ‰æ–°çˆ¶æµç¨‹åˆ†ç±»æƒé™ é¿å…è¶Šæƒ
            this.checkCategoryDataScope(category.getParentId());
            FlowCategory newParentCategory = baseMapper.selectById(category.getParentId());
            if (ObjectUtil.isNotNull(newParentCategory)) {
                String newAncestors = newParentCategory.getAncestors() + StringUtils.SEPARATOR + newParentCategory.getCategoryId();
                String oldAncestors = oldCategory.getAncestors();
                category.setAncestors(newAncestors);
                updateCategoryChildren(category.getCategoryId(), newAncestors, oldAncestors);
            }
        } else {
            category.setAncestors(oldCategory.getAncestors());
        }
        return baseMapper.updateById(category);
    }
    /**
     * ä¿®æ”¹å­å…ƒç´ å…³ç³»
     *
     * @param categoryId   è¢«ä¿®æ”¹çš„æµç¨‹åˆ†ç±»ID
     * @param newAncestors æ–°çš„父ID集合
     * @param oldAncestors æ—§çš„父ID集合
     */
    private void updateCategoryChildren(Long categoryId, String newAncestors, String oldAncestors) {
        List<FlowCategory> children = baseMapper.selectList(new LambdaQueryWrapper<FlowCategory>()
            .apply(DataBaseHelper.findInSet(categoryId, "ancestors")));
        List<FlowCategory> list = new ArrayList<>();
        for (FlowCategory child : children) {
            FlowCategory category = new FlowCategory();
            category.setCategoryId(child.getCategoryId());
            category.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
            list.add(category);
        }
        if (CollUtil.isNotEmpty(list)) {
            baseMapper.updateBatchById(list);
        }
    }
    /**
     * åˆ é™¤æµç¨‹åˆ†ç±»ä¿¡æ¯
     *
     * @param categoryId ä¸»é”®
     * @return æ˜¯å¦åˆ é™¤æˆåŠŸ
     */
    @CacheEvict(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#categoryId")
    @Override
    public int deleteWithValidById(Long categoryId) {
        return baseMapper.deleteById(categoryId);
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,266 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
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 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.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.warm.flow.core.dto.DefJson;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.enums.PublishStatus;
import org.dromara.warm.flow.core.service.DefService;
import org.dromara.warm.flow.orm.entity.FlowDefinition;
import org.dromara.warm.flow.orm.entity.FlowHisTask;
import org.dromara.warm.flow.orm.entity.FlowNode;
import org.dromara.warm.flow.orm.entity.FlowSkip;
import org.dromara.warm.flow.orm.mapper.FlowDefinitionMapper;
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
import org.dromara.warm.flow.orm.mapper.FlowSkipMapper;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.FlowCategory;
import org.dromara.workflow.domain.vo.FlowDefinitionVo;
import org.dromara.workflow.mapper.FlwCategoryMapper;
import org.dromara.workflow.service.IFlwDefinitionService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.dromara.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
/**
 * æµç¨‹å®šä¹‰ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
    private final DefService defService;
    private final FlowDefinitionMapper flowDefinitionMapper;
    private final FlowHisTaskMapper flowHisTaskMapper;
    private final FlowNodeMapper flowNodeMapper;
    private final FlowSkipMapper flowSkipMapper;
    private final FlwCategoryMapper flwCategoryMapper;
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰åˆ—表
     *
     * @param flowDefinition æµç¨‹å®šä¹‰ä¿¡æ¯
     * @param pageQuery      åˆ†é¡µ
     * @return è¿”回分页列表
     */
    @Override
    public TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery) {
        LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
        Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
        TableDataInfo<FlowDefinitionVo> build = TableDataInfo.build();
        build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class));
        build.setTotal(page.getTotal());
        return build;
    }
    /**
     * æŸ¥è¯¢æœªå‘布的流程定义列表
     *
     * @param flowDefinition æµç¨‹å®šä¹‰ä¿¡æ¯
     * @param pageQuery      åˆ†é¡µ
     * @return è¿”回分页列表
     */
    @Override
    public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
        LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
        Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
        TableDataInfo<FlowDefinitionVo> build = TableDataInfo.build();
        build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class));
        build.setTotal(page.getTotal());
        return build;
    }
    private LambdaQueryWrapper<FlowDefinition> buildQueryWrapper(FlowDefinition flowDefinition) {
        LambdaQueryWrapper<FlowDefinition> wrapper = Wrappers.lambdaQuery();
        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowCode()), FlowDefinition::getFlowCode, flowDefinition.getFlowCode());
        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowName()), FlowDefinition::getFlowName, flowDefinition.getFlowName());
        if (StringUtils.isNotBlank(flowDefinition.getCategory())) {
            List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
            wrapper.in(FlowDefinition::getCategory, StreamUtils.toList(categoryIds, Convert::toStr));
        }
        wrapper.orderByDesc(FlowDefinition::getCreateTime);
        return wrapper;
    }
    /**
     * å‘布流程定义
     *
     * @param id æµç¨‹å®šä¹‰id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean publish(Long id) {
        List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, id));
        List<String> errorMsg = new ArrayList<>();
        if (CollUtil.isNotEmpty(flowNodes)) {
            for (FlowNode flowNode : flowNodes) {
                String applyNodeCode = WorkflowUtils.applyNodeCode(id);
                if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
                    errorMsg.add(flowNode.getNodeName());
                }
            }
            if (CollUtil.isNotEmpty(errorMsg)) {
                throw new ServiceException("节点【" + StringUtils.join(errorMsg, ",") + "】未配置办理人!");
            }
        }
        return defService.publish(id);
    }
    /**
     * å¯¼å…¥æµç¨‹å®šä¹‰
     *
     * @param file æ–‡ä»¶
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean importJson(MultipartFile file, String category) {
        try {
            DefJson defJson = JsonUtils.parseObject(file.getBytes(), DefJson.class);
            defJson.setCategory(category);
            defService.importDef(defJson);
        } catch (IOException e) {
            log.error("读取文件流错误: {}", e.getMessage(), e);
            throw new IllegalStateException("文件读取失败,请检查文件内容", e);
        }
        return true;
    }
    /**
     * å¯¼å‡ºæµç¨‹å®šä¹‰
     *
     * @param id       æµç¨‹å®šä¹‰id
     * @param response å“åº”
     * @throws IOException å¼‚常
     */
    @Override
    public void exportDef(Long id, HttpServletResponse response) throws IOException {
        byte[] data = defService.exportJson(id).getBytes(StandardCharsets.UTF_8);
        // è®¾ç½®å“åº”头和内容类型
        response.reset();
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType("application/text");
        response.setHeader("Content-Disposition", "attachment;");
        response.addHeader("Content-Length", "" + data.length);
        IoUtil.write(response.getOutputStream(), false, data);
    }
    /**
     * åˆ é™¤æµç¨‹å®šä¹‰
     *
     * @param ids æµç¨‹å®šä¹‰id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeDef(List<Long> ids) {
        LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
        wrapper.in(FlowHisTask::getDefinitionId, ids);
        List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
        if (CollUtil.isNotEmpty(flowHisTasks)) {
            List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId));
            if (CollUtil.isNotEmpty(flowDefinitions)) {
                String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
                log.error("流程定义【{}】已被使用不可被删除!", join);
                throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!");
            }
        }
        try {
            defService.removeDef(ids);
        } catch (Exception e) {
            log.error("Error removing flow definitions: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to remove flow definitions", e);
        }
        return true;
    }
    /**
     * æ–°å¢žç§Ÿæˆ·æµç¨‹å®šä¹‰
     *
     * @param tenantId ç§Ÿæˆ·id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncDef(String tenantId) {
        List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
        if (CollUtil.isEmpty(flowDefinitions)) {
            return;
        }
        FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
            .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
        flowCategory.setCategoryId(null);
        flowCategory.setTenantId(tenantId);
        flwCategoryMapper.insert(flowCategory);
        List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
        List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
        List<FlowSkip> flowSkips = flowSkipMapper.selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));
        for (FlowDefinition definition : flowDefinitions) {
            FlowDefinition flowDefinition = BeanUtil.toBean(definition, FlowDefinition.class);
            flowDefinition.setId(null);
            flowDefinition.setTenantId(tenantId);
            flowDefinition.setIsPublish(0);
            flowDefinition.setCategory(String.valueOf(flowCategory.getCategoryId()));
            int insert = flowDefinitionMapper.insert(flowDefinition);
            if (insert <= 0) {
                log.info("同步流程定义【{}】失败!", definition.getFlowCode());
                continue;
            }
            log.info("同步流程定义【{}】成功!", definition.getFlowCode());
            Long definitionId = flowDefinition.getId();
            if (CollUtil.isNotEmpty(flowNodes)) {
                List<FlowNode> nodes = StreamUtils.filter(flowNodes, node -> node.getDefinitionId().equals(definition.getId()));
                if (CollUtil.isNotEmpty(nodes)) {
                    List<FlowNode> flowNodeList = BeanUtil.copyToList(nodes, FlowNode.class);
                    flowNodeList.forEach(e -> {
                        e.setId(null);
                        e.setDefinitionId(definitionId);
                        e.setTenantId(tenantId);
                        e.setPermissionFlag(null);
                    });
                    flowNodeMapper.insertOrUpdate(flowNodeList);
                }
            }
            if (CollUtil.isNotEmpty(flowSkips)) {
                List<FlowSkip> skips = StreamUtils.filter(flowSkips, skip -> skip.getDefinitionId().equals(definition.getId()));
                if (CollUtil.isNotEmpty(skips)) {
                    List<FlowSkip> flowSkipList = BeanUtil.copyToList(skips, FlowSkip.class);
                    flowSkipList.forEach(e -> {
                        e.setId(null);
                        e.setDefinitionId(definitionId);
                        e.setTenantId(tenantId);
                    });
                    flowSkipMapper.insertOrUpdate(flowSkipList);
                }
            }
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,451 @@
package org.dromara.workflow.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.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.enums.BusinessStatusEnum;
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.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.constant.ExceptionCons;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.Definition;
import org.dromara.warm.flow.core.entity.Instance;
import org.dromara.warm.flow.core.entity.Task;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.service.ChartService;
import org.dromara.warm.flow.core.service.DefService;
import org.dromara.warm.flow.core.service.InsService;
import org.dromara.warm.flow.core.service.TaskService;
import org.dromara.warm.flow.orm.entity.FlowHisTask;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.bo.FlowCancelBo;
import org.dromara.workflow.domain.bo.FlowInstanceBo;
import org.dromara.workflow.domain.bo.FlowInvalidBo;
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
import org.dromara.workflow.domain.vo.FlowInstanceVo;
import org.dromara.workflow.domain.vo.FlowVariableVo;
import org.dromara.workflow.handler.FlowProcessEventHandler;
import org.dromara.workflow.mapper.FlwCategoryMapper;
import org.dromara.workflow.mapper.FlwInstanceMapper;
import org.dromara.workflow.service.IFlwInstanceService;
import org.dromara.workflow.service.IFlwTaskService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
 * æµç¨‹å®žä¾‹ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwInstanceServiceImpl implements IFlwInstanceService {
    private final InsService insService;
    private final DefService defService;
    private final ChartService chartService;
    private final TaskService taskService;
    private final FlowHisTaskMapper flowHisTaskMapper;
    private final FlowInstanceMapper flowInstanceMapper;
    private final FlowProcessEventHandler flowProcessEventHandler;
    private final IFlwTaskService flwTaskService;
    private final FlwInstanceMapper flwInstanceMapper;
    private final FlwCategoryMapper flwCategoryMapper;
    /**
     * åˆ†é¡µæŸ¥è¯¢æ­£åœ¨è¿è¡Œçš„æµç¨‹å®žä¾‹
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
        QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(flowInstanceBo);
        queryWrapper.in("fi.flow_status", BusinessStatusEnum.runningStatus());
        Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å·²ç»“束的流程实例
     *
     * @param flowInstanceBo æµç¨‹å®žä¾‹
     * @param pageQuery      åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
        QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(flowInstanceBo);
        queryWrapper.in("fi.flow_status", BusinessStatusEnum.finishStatus());
        Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * æ ¹æ®ä¸šåŠ¡id查询流程实例详细信息
     *
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    @Override
    public FlowInstanceVo queryByBusinessId(Long businessId) {
        FlowInstance instance = this.selectInstByBusinessId(String.valueOf(businessId));
        FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
        Definition definition = defService.getById(instanceVo.getDefinitionId());
        instanceVo.setFlowName(definition.getFlowName());
        instanceVo.setFlowCode(definition.getFlowCode());
        instanceVo.setVersion(definition.getVersion());
        instanceVo.setFormCustom(definition.getFormCustom());
        instanceVo.setFormPath(definition.getFormPath());
        instanceVo.setCategory(definition.getCategory());
        return instanceVo;
    }
    /**
     * é€šç”¨æŸ¥è¯¢æ¡ä»¶
     *
     * @param flowInstanceBo æŸ¥è¯¢æ¡ä»¶
     * @return æŸ¥è¯¢æ¡ä»¶æž„造方法
     */
    private QueryWrapper<FlowInstanceBo> buildQueryWrapper(FlowInstanceBo flowInstanceBo) {
        QueryWrapper<FlowInstanceBo> queryWrapper = Wrappers.query();
        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name", flowInstanceBo.getNodeName());
        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name", flowInstanceBo.getFlowName());
        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code", flowInstanceBo.getFlowCode());
        if (StringUtils.isNotBlank(flowInstanceBo.getCategory())) {
            List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
            queryWrapper.in("fd.category", StreamUtils.toList(categoryIds, Convert::toStr));
        }
        queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id", flowInstanceBo.getBusinessId());
        queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by", flowInstanceBo.getCreateByIds());
        queryWrapper.eq("fi.del_flag", "0");
        queryWrapper.orderByDesc("fi.create_time");
        return queryWrapper;
    }
    /**
     * æ ¹æ®ä¸šåŠ¡id查询流程实例
     *
     * @param businessId ä¸šåŠ¡id
     */
    @Override
    public FlowInstance selectInstByBusinessId(String businessId) {
        return flowInstanceMapper.selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
    }
    /**
     * æŒ‰ç…§å®žä¾‹id查询流程实例
     *
     * @param instanceId å®žä¾‹id
     */
    @Override
    public FlowInstance selectInstById(Long instanceId) {
        return flowInstanceMapper.selectById(instanceId);
    }
    /**
     * æŒ‰ç…§å®žä¾‹id查询流程实例
     *
     * @param instanceIds å®žä¾‹id
     */
    @Override
    public List<FlowInstance> selectInstListByIdList(List<Long> instanceIds) {
        return flowInstanceMapper.selectByIds(instanceIds);
    }
    /**
     * æŒ‰ç…§ä¸šåŠ¡id删除流程实例
     *
     * @param businessIds ä¸šåŠ¡id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByBusinessIds(List<Long> businessIds) {
        List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, businessIds));
        if (CollUtil.isEmpty(flowInstances)) {
            log.warn("未找到对应的流程实例信息,无法执行删除操作。");
            return false;
        }
        return insService.remove(StreamUtils.toList(flowInstances, FlowInstance::getId));
    }
    /**
     * æŒ‰ç…§å®žä¾‹id删除流程实例
     *
     * @param instanceIds å®žä¾‹id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByInstanceIds(List<Long> instanceIds) {
        // èŽ·å–å®žä¾‹ä¿¡æ¯
        List<Instance> instances = insService.getByIds(instanceIds);
        if (CollUtil.isEmpty(instances)) {
            log.warn("未找到对应的流程实例信息,无法执行删除操作。");
            return false;
        }
        // èŽ·å–å®šä¹‰ä¿¡æ¯
        Map<Long, Definition> definitionMap = defService.getByIds(
            StreamUtils.toList(instances, Instance::getDefinitionId)
        ).stream().collect(Collectors.toMap(Definition::getId, definition -> definition));
        // é€ä¸€è§¦å‘删除事件
        instances.forEach(instance -> {
            Definition definition = definitionMap.get(instance.getDefinitionId());
            if (ObjectUtil.isNull(definition)) {
                log.warn("实例 ID: {} å¯¹åº”的流程定义信息未找到,跳过删除事件触发。", instance.getId());
                return;
            }
            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
        });
        // åˆ é™¤å®žä¾‹
        return insService.remove(instanceIds);
    }
    /**
     * æ’¤é”€æµç¨‹
     *
     * @param bo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancelProcessApply(FlowCancelBo bo) {
        try {
            Instance instance = selectInstByBusinessId(bo.getBusinessId());
            if (instance == null) {
                throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
            }
            Definition definition = defService.getById(instance.getDefinitionId());
            if (definition == null) {
                throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
            }
            String message = bo.getMessage();
            BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
            String applyNodeCode = WorkflowUtils.applyNodeCode(definition.getId());
            //撤销
            WorkflowUtils.backTask(message, instance.getId(), applyNodeCode, BusinessStatusEnum.CANCEL.getStatus(), BusinessStatusEnum.CANCEL.getStatus());
            //判断或签节点是否有多个,只保留一个
            List<Task> currentTaskList = taskService.list(FlowEngine.newTask().setInstanceId(instance.getId()));
            if (CollUtil.isNotEmpty(currentTaskList)) {
                if (currentTaskList.size() > 1) {
                    currentTaskList.remove(0);
                    WorkflowUtils.deleteRunTask(StreamUtils.toList(currentTaskList, Task::getId));
                }
            }
        } catch (Exception e) {
            log.error("撤销失败: {}", e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
        return true;
    }
    /**
     * èŽ·å–å½“å‰ç™»é™†äººå‘èµ·çš„æµç¨‹å®žä¾‹
     *
     * @param instanceBo æµç¨‹å®žä¾‹
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo instanceBo, PageQuery pageQuery) {
        QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(instanceBo);
        queryWrapper.eq("fi.create_by", LoginHelper.getUserIdStr());
        Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * èŽ·å–æµç¨‹å›¾,流程记录
     *
     * @param businessId ä¸šåŠ¡id
     */
    @Override
    public Map<String, Object> flowImage(String businessId) {
        FlowInstance flowInstance = this.selectInstByBusinessId(businessId);
        if (ObjectUtil.isNull(flowInstance)) {
            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
        }
        Long instanceId = flowInstance.getId();
        //运行中的任务
        List<FlowHisTaskVo> list = new ArrayList<>();
        List<FlowTask> flowTaskList = flwTaskService.selectByInstId(instanceId);
        if (CollUtil.isNotEmpty(flowTaskList)) {
            List<FlowHisTaskVo> flowHisTaskVos = BeanUtil.copyToList(flowTaskList, FlowHisTaskVo.class);
            for (FlowHisTaskVo flowHisTaskVo : flowHisTaskVos) {
                flowHisTaskVo.setFlowStatus(TaskStatusEnum.WAITING.getStatus());
                flowHisTaskVo.setUpdateTime(null);
                flowHisTaskVo.setRunDuration(null);
                List<UserDTO> allUser = flwTaskService.currentTaskAllUser(flowHisTaskVo.getId());
                if (CollUtil.isNotEmpty(allUser)) {
                    String join = StreamUtils.join(allUser, e -> String.valueOf(e.getUserId()));
                    flowHisTaskVo.setApprover(join);
                }
                if (BusinessStatusEnum.isDraftOrCancelOrBack(flowInstance.getFlowStatus())) {
                    flowHisTaskVo.setApprover(LoginHelper.getUserIdStr());
                    flowHisTaskVo.setApproveName(LoginHelper.getLoginUser().getNickname());
                }
            }
            list.addAll(flowHisTaskVos);
        }
        //历史任务
        LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(FlowHisTask::getInstanceId, instanceId);
        wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey());
        wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime);
        List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
        if (CollUtil.isNotEmpty(flowHisTasks)) {
            list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class));
        }
        String flowChart = chartService.chartIns(instanceId);
        return Map.of("list", list, "image", flowChart);
    }
    /**
     * æŒ‰ç…§å®žä¾‹id更新状态
     *
     * @param instanceId å®žä¾‹id
     * @param status     çŠ¶æ€
     */
    @Override
    public void updateStatus(Long instanceId, String status) {
        LambdaUpdateWrapper<FlowInstance> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(FlowInstance::getFlowStatus, status);
        wrapper.eq(FlowInstance::getId, instanceId);
        flowInstanceMapper.update(wrapper);
    }
    /**
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param instanceId å®žä¾‹id
     */
    @Override
    public Map<String, Object> instanceVariable(Long instanceId) {
        Map<String, Object> map = new HashMap<>();
        FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
        Map<String, Object> variableMap = flowInstance.getVariableMap();
        List<FlowVariableVo> list = new ArrayList<>();
        if (CollUtil.isNotEmpty(variableMap)) {
            for (Map.Entry<String, Object> entry : variableMap.entrySet()) {
                FlowVariableVo flowVariableVo = new FlowVariableVo();
                flowVariableVo.setKey(entry.getKey());
                flowVariableVo.setValue(entry.getValue().toString());
                list.add(flowVariableVo);
            }
        }
        map.put("variableList", list);
        map.put("variable", flowInstance.getVariable());
        return map;
    }
    /**
     * è®¾ç½®æµç¨‹å˜é‡
     *
     * @param instanceId å®žä¾‹id
     * @param variable   æµç¨‹å˜é‡
     */
    @Override
    public void setVariable(Long instanceId, Map<String, Object> variable) {
        Instance instance = insService.getById(instanceId);
        if (instance != null) {
            taskService.mergeVariable(instance, variable);
        }
    }
    /**
     * æŒ‰ä»»åŠ¡id查询实例
     *
     * @param taskId ä»»åŠ¡id
     */
    @Override
    public FlowInstance selectByTaskId(Long taskId) {
        Task task = taskService.getById(taskId);
        if (task == null) {
            FlowHisTask flowHisTask = flwTaskService.selectHisTaskById(taskId);
            if (flowHisTask != null) {
                return this.selectInstById(flowHisTask.getInstanceId());
            }
        } else {
            return this.selectInstById(task.getInstanceId());
        }
        return null;
    }
    /**
     * æŒ‰ä»»åŠ¡id查询实例
     *
     * @param taskIdList ä»»åŠ¡id
     */
    @Override
    public List<FlowInstance> selectByTaskIdList(List<Long> taskIdList) {
        if (CollUtil.isEmpty(taskIdList)) {
            return Collections.emptyList();
        }
        Set<Long> instanceIds = new HashSet<>();
        List<FlowTask> flowTaskList = flwTaskService.selectByIdList(taskIdList);
        for (FlowTask flowTask : flowTaskList) {
            instanceIds.add(flowTask.getInstanceId());
        }
        List<FlowHisTask> flowHisTaskList = flwTaskService.selectHisTaskByIdList(taskIdList);
        for (FlowHisTask flowHisTask : flowHisTaskList) {
            instanceIds.add(flowHisTask.getInstanceId());
        }
        if (!instanceIds.isEmpty()) {
            return this.selectInstListByIdList(new ArrayList<>(instanceIds));
        }
        return Collections.emptyList();
    }
    /**
     * ä½œåºŸæµç¨‹
     *
     * @param bo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean processInvalid(FlowInvalidBo bo) {
        try {
            Instance instance = insService.getById(bo.getId());
            if (instance != null) {
                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
            }
            List<FlowTask> flowTaskList = flwTaskService.selectByInstId(bo.getId());
            for (FlowTask flowTask : flowTaskList) {
                FlowParams flowParams = new FlowParams();
                flowParams.message(bo.getComment());
                flowParams.flowStatus(BusinessStatusEnum.INVALID.getStatus())
                    .hisStatus(TaskStatusEnum.INVALID.getStatus());
                flowParams.ignore(true);
                taskService.termination(flowTask.getId(), flowParams);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.DeptDTO;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DeptService;
import org.dromara.common.core.service.TaskAssigneeService;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.warm.flow.ui.dto.HandlerFunDto;
import org.dromara.warm.flow.ui.dto.HandlerQuery;
import org.dromara.warm.flow.ui.dto.TreeFunDto;
import org.dromara.warm.flow.ui.service.HandlerSelectService;
import org.dromara.warm.flow.ui.vo.HandlerSelectVo;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.TaskAssigneeEnum;
import org.dromara.workflow.service.IFlwTaskAssigneeService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * æµç¨‹è®¾è®¡å™¨-获取办理人权限设置列表
 *
 * @author AprilWind
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, HandlerSelectService {
    private static final String DEFAULT_GROUP_NAME = "默认分组";
    private final TaskAssigneeService taskAssigneeService;
    private final UserService userService;
    private final DeptService deptService;
    /**
     * èŽ·å–åŠžç†äººæƒé™è®¾ç½®åˆ—è¡¨tabs页签
     *
     * @return tabs页签
     */
    @Override
    public List<String> getHandlerType() {
        return TaskAssigneeEnum.getAssigneeTypeList();
    }
    /**
     * èŽ·å–åŠžç†åˆ—è¡¨, åŒæ—¶æž„建左侧部门树状结构
     *
     * @param query æŸ¥è¯¢æ¡ä»¶
     * @return HandlerSelectVo
     */
    @Override
    public HandlerSelectVo getHandlerSelect(HandlerQuery query) {
        // èŽ·å–ä»»åŠ¡åŠžç†ç±»åž‹
        TaskAssigneeEnum type = TaskAssigneeEnum.fromDesc(query.getHandlerType());
        // è½¬æ¢æŸ¥è¯¢æ¡ä»¶ä¸º TaskAssigneeBody
        TaskAssigneeBody taskQuery = BeanUtil.toBean(query, TaskAssigneeBody.class);
        // ç»Ÿä¸€æŸ¥è¯¢å¹¶æž„建业务数据
        TaskAssigneeDTO dto = fetchTaskAssigneeData(type, taskQuery);
        List<DeptDTO> depts = fetchDeptData(type);
        return getHandlerSelectVo(buildHandlerData(dto, type), buildDeptTree(depts));
    }
    /**
     * æ ¹æ®ä»»åŠ¡åŠžç†ç±»åž‹æŸ¥è¯¢å¯¹åº”çš„æ•°æ®
     */
    private TaskAssigneeDTO fetchTaskAssigneeData(TaskAssigneeEnum type, TaskAssigneeBody taskQuery) {
        return switch (type) {
            case USER -> taskAssigneeService.selectUsersByTaskAssigneeList(taskQuery);
            case ROLE -> taskAssigneeService.selectRolesByTaskAssigneeList(taskQuery);
            case DEPT -> taskAssigneeService.selectDeptsByTaskAssigneeList(taskQuery);
            case POST -> taskAssigneeService.selectPostsByTaskAssigneeList(taskQuery);
            default -> throw new ServiceException("Unsupported handler type");
        };
    }
    /**
     * æ ¹æ®ä»»åŠ¡åŠžç†ç±»åž‹èŽ·å–éƒ¨é—¨æ•°æ®
     */
    private List<DeptDTO> fetchDeptData(TaskAssigneeEnum type) {
        if (type == TaskAssigneeEnum.USER || type == TaskAssigneeEnum.DEPT || type == TaskAssigneeEnum.POST) {
            return deptService.selectDeptsByList();
        }
        return new ArrayList<>();
    }
    /**
     * æž„建部门树状结构
     */
    private TreeFunDto<DeptDTO> buildDeptTree(List<DeptDTO> depts) {
        return new TreeFunDto<>(depts)
            .setId(dept -> String.valueOf(dept.getDeptId()))
            .setName(DeptDTO::getDeptName)
            .setParentId(dept -> String.valueOf(dept.getParentId()));
    }
    /**
     * æž„建任务办理人数据
     */
    private HandlerFunDto<TaskAssigneeDTO.TaskHandler> buildHandlerData(TaskAssigneeDTO dto, TaskAssigneeEnum type) {
        return new HandlerFunDto<>(dto.getList(), dto.getTotal())
            .setStorageId(assignee -> type.getCode() + assignee.getStorageId())
            .setHandlerCode(assignee -> StringUtils.blankToDefault(assignee.getHandlerCode(), "无"))
            .setHandlerName(assignee -> StringUtils.blankToDefault(assignee.getHandlerName(), "无"))
            .setGroupName(assignee -> StringUtils.defaultIfBlank(
                Optional.ofNullable(assignee.getGroupName())
                    .map(deptService::selectDeptNameByIds)
                    .orElse(DEFAULT_GROUP_NAME), DEFAULT_GROUP_NAME))
            .setCreateTime(assignee -> DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, assignee.getCreateTime()));
    }
    /**
     * æ ¹æ®å­˜å‚¨æ ‡è¯†ç¬¦ï¼ˆstorageId)解析分配类型和ID,并获取对应的用户列表
     *
     * @param storageId åŒ…含分配类型和ID的字符串(例如 "user:123" æˆ– "role:456")
     * @return ä¸Žåˆ†é…ç±»åž‹å’ŒID匹配的用户列表,如果格式无效则返回空列表
     */
    @Override
    public List<UserDTO> fetchUsersByStorageId(String storageId) {
        List<UserDTO> list = new ArrayList<>();
        for (String str : storageId.split(StrUtil.COMMA)) {
            String[] parts = str.split(StrUtil.COLON, 2);
            if (parts.length < 2) {
                list.addAll(getUsersByType(TaskAssigneeEnum.USER, List.of(Long.valueOf(parts[0]))));
            } else {
                list.addAll(getUsersByType(TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON), List.of(Long.valueOf(parts[1]))));
            }
        }
        return list;
    }
    /**
     * æ ¹æ®æŒ‡å®šçš„任务分配类型(TaskAssigneeEnum)和 ID åˆ—表,获取对应的用户信息列表
     *
     * @param type ä»»åŠ¡åˆ†é…ç±»åž‹ï¼Œè¡¨ç¤ºç”¨æˆ·ã€è§’è‰²ã€éƒ¨é—¨æˆ–å…¶ä»–ï¼ˆTaskAssigneeEnum æžšä¸¾å€¼ï¼‰
     * @param ids  ä¸ŽæŒ‡å®šåˆ†é…ç±»åž‹å…³è”çš„ ID åˆ—表(例如用户ID、角色ID、部门ID等)
     * @return è¿”回包含用户信息的列表。如果类型为用户(USER),则通过用户ID列表查询;
     * å¦‚果类型为角色(ROLE),则通过角色ID列表查询;
     * å¦‚果类型为部门(DEPT),则通过部门ID列表查询;
     * å¦‚果类型为岗位(POST)或无法识别的类型,则返回空列表
     */
    private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<Long> ids) {
        return switch (type) {
            case USER -> userService.selectListByIds(ids);
            case ROLE -> userService.selectUsersByRoleIds(ids);
            case DEPT -> userService.selectUsersByDeptIds(ids);
            case POST -> userService.selectUsersByPostIds(ids);
        };
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,687 @@
package org.dromara.workflow.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.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
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.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.*;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.enums.SkipType;
import org.dromara.warm.flow.core.service.*;
import org.dromara.warm.flow.orm.entity.*;
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.TaskAssigneeType;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
import org.dromara.workflow.domain.vo.FlowTaskVo;
import org.dromara.workflow.handler.FlowProcessEventHandler;
import org.dromara.workflow.handler.WorkflowPermissionHandler;
import org.dromara.workflow.mapper.FlwCategoryMapper;
import org.dromara.workflow.mapper.FlwTaskMapper;
import org.dromara.workflow.service.IFlwTaskService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import static org.dromara.workflow.common.constant.FlowConstant.*;
/**
 * ä»»åŠ¡ æœåŠ¡å±‚å®žçŽ°
 *
 * @author may
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwTaskServiceImpl implements IFlwTaskService {
    private final TaskService taskService;
    private final InsService insService;
    private final DefService defService;
    private final HisTaskService hisTaskService;
    private final NodeService nodeService;
    private final FlowInstanceMapper flowInstanceMapper;
    private final FlowTaskMapper flowTaskMapper;
    private final FlowHisTaskMapper flowHisTaskMapper;
    private final IdentifierGenerator identifierGenerator;
    private final FlowProcessEventHandler flowProcessEventHandler;
    private final UserService userService;
    private final FlwTaskMapper flwTaskMapper;
    private final FlwCategoryMapper flwCategoryMapper;
    /**
     * å¯åŠ¨ä»»åŠ¡
     *
     * @param startProcessBo å¯åŠ¨æµç¨‹å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo) {
        String businessId = startProcessBo.getBusinessId();
        if (StringUtils.isBlank(businessId)) {
            throw new ServiceException("启动工作流时必须包含业务ID");
        }
        // å¯åŠ¨æµç¨‹å®žä¾‹ï¼ˆæäº¤ç”³è¯·ï¼‰
        Map<String, Object> variables = startProcessBo.getVariables();
        // æµç¨‹å‘起人
        variables.put(INITIATOR, LoginHelper.getUserIdStr());
        // ä¸šåŠ¡id
        variables.put(BUSINESS_ID, businessId);
        FlowInstance flowInstance = flowInstanceMapper.selectOne(new LambdaQueryWrapper<>(FlowInstance.class)
            .eq(FlowInstance::getBusinessId, businessId));
        if (ObjectUtil.isNotNull(flowInstance)) {
            BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
            List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
            StartProcessReturnDTO dto = new StartProcessReturnDTO();
            dto.setProcessInstanceId(taskList.get(0).getInstanceId());
            dto.setTaskId(taskList.get(0).getId());
            return dto;
        }
        FlowParams flowParams = new FlowParams();
        flowParams.flowCode(startProcessBo.getFlowCode());
        flowParams.variable(startProcessBo.getVariables());
        flowParams.flowStatus(BusinessStatusEnum.DRAFT.getStatus());
        Instance instance;
        try {
            instance = insService.start(businessId, flowParams);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
        // ç”³è¯·äººæ‰§è¡Œæµç¨‹
        List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
        if (taskList.size() > 1) {
            throw new ServiceException("请检查流程第一个环节是否为申请人!");
        }
        StartProcessReturnDTO dto = new StartProcessReturnDTO();
        dto.setProcessInstanceId(instance.getId());
        dto.setTaskId(taskList.get(0).getId());
        return dto;
    }
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTaskBo åŠžç†ä»»åŠ¡å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean completeTask(CompleteTaskBo completeTaskBo) {
        try {
            // èŽ·å–ä»»åŠ¡ID并查询对应的流程任务和实例信息
            Long taskId = completeTaskBo.getTaskId();
            List<String> messageType = completeTaskBo.getMessageType();
            String notice = completeTaskBo.getNotice();
            // èŽ·å–æŠ„é€äºº
            List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
            FlowTask flowTask = flowTaskMapper.selectById(taskId);
            if (ObjectUtil.isNull(flowTask)) {
                throw new ServiceException("流程任务不存在或任务已审批!");
            }
            Instance ins = insService.getById(flowTask.getInstanceId());
            // èŽ·å–æµç¨‹å®šä¹‰ä¿¡æ¯
            Definition definition = defService.getById(flowTask.getDefinitionId());
            // æ£€æŸ¥æµç¨‹çŠ¶æ€æ˜¯å¦ä¸ºè‰ç¨¿ã€å·²æ’¤é”€æˆ–å·²é€€å›žçŠ¶æ€ï¼Œè‹¥æ˜¯åˆ™æ‰§è¡Œæµç¨‹æäº¤ç›‘å¬
            if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
                flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true);
            }
            // æž„建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
            FlowParams flowParams = new FlowParams();
            flowParams.variable(completeTaskBo.getVariables());
            flowParams.skipType(SkipType.PASS.getKey());
            flowParams.message(completeTaskBo.getMessage());
            flowParams.flowStatus(BusinessStatusEnum.WAITING.getStatus()).hisStatus(TaskStatusEnum.PASS.getStatus());
            flowParams.hisTaskExt(completeTaskBo.getFileId());
            // æ‰§è¡Œä»»åŠ¡è·³è½¬ï¼Œå¹¶æ ¹æ®è¿”å›žçš„å¤„ç†äººè®¾ç½®ä¸‹ä¸€æ­¥å¤„ç†äºº
            Instance instance = taskService.skip(taskId, flowParams);
            this.setHandler(instance, flowTask, flowCopyList);
            // æ¶ˆæ¯é€šçŸ¥
            WorkflowUtils.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * è®¾ç½®åŠžç†äºº
     *
     * @param instance     å®žä¾‹
     * @param task         (当前任务)未办理的任务
     * @param flowCopyList æŠ„送人
     */
    private void setHandler(Instance instance, FlowTask task, List<FlowCopyBo> flowCopyList) {
        if (ObjectUtil.isNull(instance)) {
            return;
        }
        // æ·»åŠ æŠ„é€äºº
        this.setCopy(task, flowCopyList);
        // æ ¹æ®æµç¨‹å®žä¾‹ID查询所有关联的任务
        List<FlowTask> flowTasks = this.selectByInstId(instance.getId());
        if (CollUtil.isEmpty(flowTasks)) {
            return;
        }
        List<Long> taskIdList = StreamUtils.toList(flowTasks, FlowTask::getId);
        // èŽ·å–ä¸Žå½“å‰ä»»åŠ¡å…³è”çš„ç”¨æˆ·åˆ—è¡¨
        List<User> associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList);
        if (CollUtil.isEmpty(associatedUsers)) {
            return;
        }
        List<User> userList = new ArrayList<>();
        // éåŽ†ä»»åŠ¡åˆ—è¡¨ï¼Œå¤„ç†æ¯ä¸ªä»»åŠ¡çš„åŠžç†äºº
        for (FlowTask flowTask : flowTasks) {
            List<User> users = StreamUtils.filter(associatedUsers, user -> Objects.equals(user.getAssociated(), flowTask.getId()));
            if (CollUtil.isNotEmpty(users)) {
                userList.addAll(WorkflowUtils.buildUser(users, flowTask.getId()));
            }
        }
        // æ‰¹é‡åˆ é™¤çŽ°æœ‰ä»»åŠ¡çš„åŠžç†äººè®°å½•
        WorkflowUtils.getFlowUserService().deleteByTaskIds(taskIdList);
        // ç¡®ä¿è¦ä¿å­˜çš„ userList ä¸ä¸ºç©º
        if (CollUtil.isEmpty(userList)) {
            return;
        }
        WorkflowUtils.getFlowUserService().saveBatch(userList);
    }
    /**
     * æ·»åŠ æŠ„é€äºº
     *
     * @param task         ä»»åŠ¡ä¿¡æ¯
     * @param flowCopyList æŠ„送人
     */
    public void setCopy(FlowTask task, List<FlowCopyBo> flowCopyList) {
        if (CollUtil.isEmpty(flowCopyList)) {
            return;
        }
        // æ·»åŠ æŠ„é€äººè®°å½•
        FlowHisTask flowHisTask = flowHisTaskMapper.selectList(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getTaskId, task.getId())).get(0);
        FlowNode flowNode = new FlowNode();
        flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
        flowNode.setNodeName(flowHisTask.getTargetNodeName());
        //生成新的任务id
        long taskId = identifierGenerator.nextId(null).longValue();
        task.setId(taskId);
        task.setNodeName("【抄送】" + task.getNodeName());
        Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000);
        FlowParams flowParams = FlowParams.build();
        flowParams.skipType(SkipType.NONE.getKey());
        flowParams.hisStatus(TaskStatusEnum.COPY.getStatus());
        flowParams.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
        HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
        hisTask.setCreateTime(updateTime);
        hisTask.setUpdateTime(updateTime);
        hisTaskService.save(hisTask);
        List<User> userList = flowCopyList.stream()
            .map(flowCopy -> {
                FlowUser flowUser = new FlowUser();
                flowUser.setType(TaskAssigneeType.COPY.getCode());
                flowUser.setProcessedBy(String.valueOf(flowCopy.getUserId()));
                flowUser.setAssociated(taskId);
                return flowUser;
            }).collect(Collectors.toList());
        // æ‰¹é‡ä¿å­˜æŠ„送人员
        WorkflowUtils.getFlowUserService().saveBatch(userList);
    }
    /**
     * æŸ¥è¯¢å½“前用户的待办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
        queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
        queryWrapper.in("t.processed_by", SpringUtils.getBean(WorkflowPermissionHandler.class).permissions());
        queryWrapper.in("t.flow_status", BusinessStatusEnum.WAITING.getStatus());
        Page<FlowTaskVo> page = this.getFlowTaskVoPage(pageQuery, queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * æŸ¥è¯¢å½“前用户的已办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
        queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
        queryWrapper.in("t.approver", LoginHelper.getUserIdStr());
        queryWrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
        Page<FlowHisTaskVo> page = flwTaskMapper.getListFinishTask(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * æŸ¥è¯¢å¾…办任务
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
        queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
        Page<FlowTaskVo> page = getFlowTaskVoPage(pageQuery, queryWrapper);
        return TableDataInfo.build(page);
    }
    private Page<FlowTaskVo> getFlowTaskVoPage(PageQuery pageQuery, QueryWrapper<FlowTaskBo> queryWrapper) {
        Page<FlowTaskVo> page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper);
        List<FlowTaskVo> records = page.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            List<Long> taskIds = StreamUtils.toList(records, FlowTaskVo::getId);
            Map<Long, List<UserDTO>> listMap = currentTaskAllUser(taskIds);
            records.forEach(t -> {
                List<UserDTO> userList = listMap.getOrDefault(t.getId(), Collections.emptyList());
                if (CollUtil.isNotEmpty(userList)) {
                    t.setAssigneeIds(StreamUtils.join(userList, e -> String.valueOf(e.getUserId())));
                    t.setAssigneeNames(StreamUtils.join(userList, UserDTO::getNickName));
                }
            });
        }
        return page;
    }
    /**
     * æŸ¥è¯¢å·²åŠžä»»åŠ¡
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
        Page<FlowHisTaskVo> page = flwTaskMapper.getListFinishTask(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    /**
     * æŸ¥è¯¢å½“前用户的抄送
     *
     * @param flowTaskBo å‚æ•°
     * @param pageQuery  åˆ†é¡µ
     */
    @Override
    public TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
        QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
        queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr());
        Page<FlowTaskVo> page = flwTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper);
        return TableDataInfo.build(page);
    }
    private QueryWrapper<FlowTaskBo> buildQueryWrapper(FlowTaskBo flowTaskBo) {
        QueryWrapper<FlowTaskBo> wrapper = Wrappers.query();
        wrapper.like(StringUtils.isNotBlank(flowTaskBo.getNodeName()), "t.node_name", flowTaskBo.getNodeName());
        wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowName()), "t.flow_name", flowTaskBo.getFlowName());
        wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowCode()), "t.flow_code", flowTaskBo.getFlowCode());
        wrapper.in(CollUtil.isNotEmpty(flowTaskBo.getCreateByIds()), "t.create_by", flowTaskBo.getCreateByIds());
        if (StringUtils.isNotBlank(flowTaskBo.getCategory())) {
            List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowTaskBo.getCategory()));
            wrapper.in("t.category", StreamUtils.toList(categoryIds, Convert::toStr));
        }
        wrapper.orderByDesc("t.create_time");
        return wrapper;
    }
    /**
     * é©³å›žä»»åŠ¡
     *
     * @param bo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean backProcess(BackProcessBo bo) {
        try {
            Long taskId = bo.getTaskId();
            String notice = bo.getNotice();
            List<String> messageType = bo.getMessageType();
            String message = bo.getMessage();
            FlowTask task = flowTaskMapper.selectById(taskId);
            if (ObjectUtil.isNull(task)) {
                throw new ServiceException("任务不存在!");
            }
            Instance inst = insService.getById(task.getInstanceId());
            BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
            Long definitionId = task.getDefinitionId();
            Definition definition = defService.getById(definitionId);
            String applyNodeCode = WorkflowUtils.applyNodeCode(definitionId);
            FlowParams flowParams = FlowParams.build();
            flowParams.nodeCode(bo.getNodeCode());
            flowParams.message(message);
            flowParams.skipType(SkipType.REJECT.getKey());
            flowParams.flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
                .hisStatus(TaskStatusEnum.BACK.getStatus());
            flowParams.hisTaskExt(bo.getFileId());
            taskService.skip(task.getId(), flowParams);
            Instance instance = insService.getById(inst.getId());
            this.setHandler(instance, task, null);
            // æ¶ˆæ¯é€šçŸ¥
            WorkflowUtils.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * èŽ·å–å¯é©³å›žçš„å‰ç½®èŠ‚ç‚¹
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param nowNodeCode  å½“前节点
     */
    @Override
    public List<Node> getBackTaskNode(Long definitionId, String nowNodeCode) {
        List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), definitionId);
        if (!CollUtil.isNotEmpty(nodeCodes)) {
            return nodeCodes;
        }
        //判断是否配置了固定驳回节点
        Node node = nodeCodes.get(0);
        if (StringUtils.isNotBlank(node.getAnyNodeSkip())) {
            return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), definitionId);
        }
        //获取可驳回的前置节点
        List<Node> nodes = nodeService.previousNodeList(definitionId, nowNodeCode);
        if (CollUtil.isNotEmpty(nodes)) {
            return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
        }
        return nodes;
    }
    /**
     * ç»ˆæ­¢ä»»åŠ¡
     *
     * @param bo å‚æ•°
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean terminationTask(FlowTerminationBo bo) {
        try {
            Long taskId = bo.getTaskId();
            Task task = taskService.getById(taskId);
            if (task == null) {
                throw new ServiceException("任务不存在!");
            }
            Instance instance = insService.getById(task.getInstanceId());
            if (ObjectUtil.isNotNull(instance)) {
                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
            }
            FlowParams flowParams = new FlowParams();
            flowParams.message(bo.getComment());
            flowParams.flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
                .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
            taskService.termination(taskId, flowParams);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskIdList ä»»åŠ¡id
     */
    @Override
    public List<FlowTask> selectByIdList(List<Long> taskIdList) {
        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
            .in(FlowTask::getId, taskIdList));
    }
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskId ä»»åŠ¡id
     */
    @Override
    public FlowTaskVo selectById(Long taskId) {
        Task task = taskService.getById(taskId);
        if (ObjectUtil.isNull(task)) {
            return null;
        }
        FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
        Instance instance = insService.getById(task.getInstanceId());
        Definition definition = defService.getById(task.getDefinitionId());
        flowTaskVo.setFlowStatus(instance.getFlowStatus());
        flowTaskVo.setVersion(definition.getVersion());
        flowTaskVo.setFlowCode(definition.getFlowCode());
        flowTaskVo.setFlowName(definition.getFlowName());
        flowTaskVo.setBusinessId(instance.getBusinessId());
        List<Node> nodeList = nodeService.getByNodeCodes(Collections.singletonList(flowTaskVo.getNodeCode()), instance.getDefinitionId());
        if (CollUtil.isNotEmpty(nodeList)) {
            Node node = nodeList.get(0);
            flowTaskVo.setNodeRatio(node.getNodeRatio());
        }
        return flowTaskVo;
    }
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskIdList ä»»åŠ¡id
     * @return ç»“æžœ
     */
    @Override
    public List<FlowHisTask> selectHisTaskByIdList(List<Long> taskIdList) {
        return flowHisTaskMapper.selectList(new LambdaQueryWrapper<>(FlowHisTask.class)
            .in(FlowHisTask::getId, taskIdList));
    }
    /**
     * æŒ‰ç…§ä»»åŠ¡id查询任务
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     */
    @Override
    public FlowHisTask selectHisTaskById(Long taskId) {
        return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class)
            .eq(FlowHisTask::getId, taskId));
    }
    /**
     * æŒ‰ç…§å®žä¾‹id查询任务
     *
     * @param instanceIdList æµç¨‹å®žä¾‹id
     */
    @Override
    public List<FlowTask> selectByInstIdList(List<Long> instanceIdList) {
        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
            .in(FlowTask::getInstanceId, instanceIdList));
    }
    /**
     * æŒ‰ç…§å®žä¾‹id查询任务
     *
     * @param instanceId æµç¨‹å®žä¾‹id
     */
    @Override
    public List<FlowTask> selectByInstId(Long instanceId) {
        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
            .eq(FlowTask::getInstanceId, instanceId));
    }
    /**
     * ä»»åŠ¡æ“ä½œ
     *
     * @param bo            å‚æ•°
     * @param taskOperation æ“ä½œç±»åž‹ï¼Œå§”æ´¾ delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
        FlowParams flowParams = new FlowParams();
        flowParams.message(bo.getMessage());
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
            flowParams.ignore(true);
        }
        // æ ¹æ®æ“ä½œç±»åž‹æž„建 FlowParams
        switch (taskOperation) {
            case DELEGATE_TASK, TRANSFER_TASK -> {
                ValidatorUtils.validate(bo, AddGroup.class);
                flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
            }
            case ADD_SIGNATURE -> {
                ValidatorUtils.validate(bo, EditGroup.class);
                flowParams.addHandlers(bo.getUserIds());
            }
            case REDUCTION_SIGNATURE -> {
                ValidatorUtils.validate(bo, EditGroup.class);
                flowParams.reductionHandlers(bo.getUserIds());
            }
            default -> {
                log.error("Invalid operation type:{} ", taskOperation);
                throw new ServiceException("Invalid operation type " + taskOperation);
            }
        }
        Long taskId = bo.getTaskId();
        FlowTaskVo flowTaskVo = selectById(taskId);
        if ("addSignature".equals(taskOperation) || "reductionSignature".equals(taskOperation)) {
            if (flowTaskVo.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
                throw new ServiceException(flowTaskVo.getNodeName() + "不是会签节点!");
            }
        }
        // è®¾ç½®ä»»åŠ¡çŠ¶æ€å¹¶æ‰§è¡Œå¯¹åº”çš„ä»»åŠ¡æ“ä½œ
        switch (taskOperation) {
            //委派任务
            case DELEGATE_TASK -> {
                flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
                return taskService.depute(taskId, flowParams);
            }
            //转办任务
            case TRANSFER_TASK -> {
                flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
                return taskService.transfer(taskId, flowParams);
            }
            //加签,增加办理人
            case ADD_SIGNATURE -> {
                flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
                return taskService.addSignature(taskId, flowParams);
            }
            //减签,减少办理人
            case REDUCTION_SIGNATURE -> {
                flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
                return taskService.reductionSignature(taskId, flowParams);
            }
            default -> {
                log.error("Invalid operation type:{} ", taskOperation);
                throw new ServiceException("Invalid operation type " + taskOperation);
            }
        }
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡åŠžç†äººï¼ˆæ­¤æ–¹æ³•å°†ä¼šæ‰¹é‡ä¿®æ”¹æ‰€æœ‰ä»»åŠ¡çš„åŠžç†äººï¼‰
     *
     * @param taskIdList ä»»åŠ¡id
     * @param userId     ç”¨æˆ·id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateAssignee(List<Long> taskIdList, String userId) {
        if (CollUtil.isEmpty(taskIdList)) {
            return false;
        }
        try {
            List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
            // æ‰¹é‡åˆ é™¤çŽ°æœ‰ä»»åŠ¡çš„åŠžç†äººè®°å½•
            if (CollUtil.isNotEmpty(flowTasks)) {
                WorkflowUtils.getFlowUserService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
                List<User> userList = flowTasks.stream()
                    .map(flowTask -> {
                        FlowUser flowUser = new FlowUser();
                        flowUser.setType(TaskAssigneeType.APPROVER.getCode());
                        flowUser.setProcessedBy(userId);
                        flowUser.setAssociated(flowTask.getId());
                        return flowUser;
                    })
                    .collect(Collectors.toList());
                if (CollUtil.isNotEmpty(userList)) {
                    WorkflowUtils.getFlowUserService().saveBatch(userList);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage());
        }
        return true;
    }
    /**
     * èŽ·å–ä»»åŠ¡æ‰€æœ‰åŠžç†äºº
     *
     * @param taskIdList ä»»åŠ¡id
     */
    @Override
    public Map<Long, List<UserDTO>> currentTaskAllUser(List<Long> taskIdList) {
        Map<Long, List<UserDTO>> map = new HashMap<>();
        // èŽ·å–ä¸Žå½“å‰ä»»åŠ¡å…³è”çš„ç”¨æˆ·åˆ—è¡¨
        List<User> associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList);
        Map<Long, List<User>> listMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
        for (Map.Entry<Long, List<User>> entry : listMap.entrySet()) {
            List<User> value = entry.getValue();
            if (CollUtil.isNotEmpty(value)) {
                List<UserDTO> userDTOS = userService.selectListByIds(StreamUtils.toList(value, e -> Long.valueOf(e.getProcessedBy())));
                map.put(entry.getKey(), userDTOS);
            }
        }
        return map;
    }
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡çš„æ‰€æœ‰åŠžç†äºº
     *
     * @param taskId ä»»åŠ¡id
     */
    @Override
    public List<UserDTO> currentTaskAllUser(Long taskId) {
        // èŽ·å–ä¸Žå½“å‰ä»»åŠ¡å…³è”çš„ç”¨æˆ·åˆ—è¡¨
        List<User> userList = WorkflowUtils.getFlowUserService().getByAssociateds(Collections.singletonList(taskId));
        if (CollUtil.isEmpty(userList)) {
            return Collections.emptyList();
        }
        return userService.selectListByIds(StreamUtils.toList(userList, e -> Long.valueOf(e.getProcessedBy())));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
@@ -1,20 +1,25 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.event.ProcessDeleteEvent;
import org.dromara.common.core.domain.event.ProcessEvent;
import org.dromara.common.core.domain.event.ProcessTaskEvent;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.service.WorkflowService;
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.common.ConditionalOnEnable;
import org.dromara.workflow.domain.TestLeave;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
@@ -24,8 +29,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * è¯·å‡Service业务层处理
@@ -33,6 +38,7 @@
 * @author may
 * @date 2023-07-21
 */
@ConditionalOnEnable
@RequiredArgsConstructor
@Service
@Slf4j
@@ -82,6 +88,9 @@
     */
    @Override
    public TestLeaveVo insertByBo(TestLeaveBo bo) {
        long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true);
        // æˆªæ­¢æ—¥æœŸä¹Ÿç®—一天
        bo.setLeaveDays((int) day + 1);
        TestLeave add = MapstructUtils.convert(bo, TestLeave.class);
        if (StringUtils.isBlank(add.getStatus())) {
            add.setStatus(BusinessStatusEnum.DRAFT.getStatus());
@@ -108,24 +117,33 @@
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        List<String> idList = StreamUtils.toList(ids, String::valueOf);
        workflowService.deleteRunAndHisInstance(idList);
    public Boolean deleteWithValidByIds(List<Long> ids) {
        workflowService.deleteInstance(ids);
        return baseMapper.deleteByIds(ids) > 0;
    }
    /**
     * æ€»ä½“流程监听(例如: æäº¤ é€€å›ž æ’¤é”€ ç»ˆæ­¢ ä½œåºŸç­‰)
     * æ­£å¸¸ä½¿ç”¨åªéœ€#processEvent.key=='leave1'
     * æ€»ä½“流程监听(例如: è‰ç¨¿ï¼Œæ’¤é”€ï¼Œé€€å›žï¼Œä½œåºŸï¼Œç»ˆæ­¢ï¼Œå·²å®Œæˆç­‰)
     * æ­£å¸¸ä½¿ç”¨åªéœ€#processEvent.flowCode=='leave1'
     * ç¤ºä¾‹ä¸ºäº†æ–¹ä¾¿åˆ™ä½¿ç”¨startsWith匹配了全部示例key
     *
     * @param processEvent å‚æ•°
     */
    @EventListener(condition = "#processEvent.key.startsWith('leave')")
    @EventListener(condition = "#processEvent.flowCode.startsWith('leave')")
    public void processHandler(ProcessEvent processEvent) {
        log.info("当前任务执行了{}", processEvent.toString());
        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessKey()));
        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId()));
        testLeave.setStatus(processEvent.getStatus());
        // ç”¨äºŽä¾‹å¦‚审批附件 å®¡æ‰¹æ„è§ç­‰ å­˜å‚¨åˆ°ä¸šåŠ¡è¡¨å†… è‡ªè¡Œæ ¹æ®ä¸šåŠ¡å®žçŽ°å­˜å‚¨æµç¨‹
        Map<String, Object> params = processEvent.getParams();
        if (MapUtil.isNotEmpty(params)) {
            // åŽ†å²ä»»åŠ¡æ‰©å±•(通常为附件)
            String hisTaskExt = Convert.toStr(params.get("hisTaskExt"));
            // åŠžç†äºº
            String handler = Convert.toStr(params.get("handler"));
            // åŠžç†æ„è§
            String message = Convert.toStr(params.get("message"));
        }
        if (processEvent.isSubmit()) {
            testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
        }
@@ -134,19 +152,37 @@
    /**
     * æ‰§è¡ŒåŠžç†ä»»åŠ¡ç›‘å¬
     * ç¤ºä¾‹ï¼šä¹Ÿå¯é€šè¿‡  @EventListener(condition = "#processTaskEvent.key=='leave1'")进行判断
     * ç¤ºä¾‹ï¼šä¹Ÿå¯é€šè¿‡  @EventListener(condition = "#processTaskEvent.flowCode=='leave1'")进行判断
     * åœ¨æ–¹æ³•中判断流程节点key
     * if ("xxx".equals(processTaskEvent.getTaskDefinitionKey())) {
     * if ("xxx".equals(processTaskEvent.getNodeCode())) {
     * //执行业务逻辑
     * }
     *
     * @param processTaskEvent å‚æ•°
     */
    @EventListener(condition = "#processTaskEvent.key=='leave1' && #processTaskEvent.taskDefinitionKey=='Activity_14633hx'")
    @EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')")
    public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
        log.info("当前任务执行了{}", processTaskEvent.toString());
        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessKey()));
        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessId()));
        testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
        baseMapper.updateById(testLeave);
    }
    /**
     * ç›‘听删除流程事件
     * æ­£å¸¸ä½¿ç”¨åªéœ€#processDeleteEvent.flowCode=='leave1'
     * ç¤ºä¾‹ä¸ºäº†æ–¹ä¾¿åˆ™ä½¿ç”¨startsWith匹配了全部示例key
     *
     * @param processDeleteEvent å‚æ•°
     */
    @EventListener(condition = "#processDeleteEvent.flowCode.startsWith('leave')")
    public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) {
        log.info("监听删除流程事件,当前任务执行了{}", processDeleteEvent.toString());
        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processDeleteEvent.getBusinessId()));
        if (ObjectUtil.isNull(testLeave)) {
            return;
        }
        baseMapper.deleteById(testLeave.getId());
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfDefinitionConfigServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfFormManageServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfNodeConfigServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfTaskBackNodeServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
@@ -1,14 +1,20 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.dto.CompleteTaskDTO;
import org.dromara.common.core.domain.dto.StartProcessDTO;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import org.dromara.common.core.service.WorkflowService;
import org.dromara.workflow.domain.ActHiProcinst;
import org.dromara.workflow.service.IActHiProcinstService;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.CompleteTaskBo;
import org.dromara.workflow.domain.bo.StartProcessBo;
import org.dromara.workflow.service.IFlwDefinitionService;
import org.dromara.workflow.service.IFlwInstanceService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -19,23 +25,24 @@
 *
 * @author may
 */
@ConditionalOnEnable
@RequiredArgsConstructor
@Service
public class WorkflowServiceImpl implements WorkflowService {
    @Autowired(required = false)
    private RuntimeService runtimeService;
    private final IActProcessInstanceService iActProcessInstanceService;
    private final IActHiProcinstService iActHiProcinstService;
    private final IFlwInstanceService flwInstanceService;
    private final IFlwDefinitionService flwDefinitionService;
    private final IFlwTaskService flwTaskService;
    /**
     * è¿è¡Œä¸­çš„实例 åˆ é™¤ç¨‹å®žä¾‹ï¼Œåˆ é™¤åŽ†å²è®°å½•ï¼Œåˆ é™¤ä¸šåŠ¡ä¸Žæµç¨‹å…³è”ä¿¡æ¯
     * åˆ é™¤æµç¨‹å®žä¾‹
     *
     * @param businessKeys ä¸šåŠ¡id
     * @param businessIds ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    @Override
    public boolean deleteRunAndHisInstance(List<String> businessKeys) {
        return iActProcessInstanceService.deleteRunAndHisInstance(businessKeys);
    public boolean deleteInstance(List<Long> businessIds) {
        return flwInstanceService.deleteByBusinessIds(businessIds);
    }
    /**
@@ -44,78 +51,82 @@
     * @param taskId ä»»åŠ¡id
     */
    @Override
    public String getBusinessStatusByTaskId(String taskId) {
        return WorkflowUtils.getBusinessStatusByTaskId(taskId);
    public String getBusinessStatusByTaskId(Long taskId) {
        FlowInstance flowInstance = flwInstanceService.selectByTaskId(taskId);
        return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getFlowStatus() : StringUtils.EMPTY;
    }
    /**
     * èŽ·å–å½“å‰æµç¨‹çŠ¶æ€
     *
     * @param businessKey ä¸šåŠ¡id
     * @param businessId ä¸šåŠ¡id
     */
    @Override
    public String getBusinessStatus(String businessKey) {
        return WorkflowUtils.getBusinessStatus(businessKey);
    public String getBusinessStatus(String businessId) {
        FlowInstance flowInstance = flwInstanceService.selectInstByBusinessId(businessId);
        return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getFlowStatus() : StringUtils.EMPTY;
    }
    /**
     * è®¾ç½®æµç¨‹å˜é‡(全局变量)
     * è®¾ç½®æµç¨‹å˜é‡
     *
     * @param taskId       ä»»åŠ¡id
     * @param variableName å˜é‡åç§°
     * @param value        å˜é‡å€¼
     * @param instanceId æµç¨‹å®žä¾‹id
     * @param variables  æµç¨‹å˜é‡
     */
    @Override
    public void setVariable(String taskId, String variableName, Object value) {
        runtimeService.setVariable(taskId, variableName, value);
    public void setVariable(Long instanceId, Map<String, Object> variables) {
        flwInstanceService.setVariable(instanceId, variables);
    }
    /**
     * è®¾ç½®æµç¨‹å˜é‡(全局变量)
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param taskId    ä»»åŠ¡id
     * @param variables æµç¨‹å˜é‡
     * @param instanceId æµç¨‹å®žä¾‹id
     */
    @Override
    public void setVariables(String taskId, Map<String, Object> variables) {
        runtimeService.setVariables(taskId, variables);
    }
    /**
     * è®¾ç½®æµç¨‹å˜é‡(本地变量,非全局变量)
     *
     * @param taskId       ä»»åŠ¡id
     * @param variableName å˜é‡åç§°
     * @param value        å˜é‡å€¼
     */
    @Override
    public void setVariableLocal(String taskId, String variableName, Object value) {
        runtimeService.setVariableLocal(taskId, variableName, value);
    }
    /**
     * è®¾ç½®æµç¨‹å˜é‡(本地变量,非全局变量)
     *
     * @param taskId    ä»»åŠ¡id
     * @param variables æµç¨‹å˜é‡
     */
    @Override
    public void setVariablesLocal(String taskId, Map<String, Object> variables) {
        runtimeService.setVariablesLocal(taskId, variables);
    public Map<String, Object> instanceVariable(Long instanceId) {
        return flwInstanceService.instanceVariable(instanceId);
    }
    /**
     * æŒ‰ç…§ä¸šåŠ¡id查询流程实例id
     *
     * @param businessKey ä¸šåŠ¡id
     * @param businessId ä¸šåŠ¡id
     * @return ç»“æžœ
     */
    @Override
    public String getInstanceIdByBusinessKey(String businessKey) {
        ActHiProcinst actHiProcinst = iActHiProcinstService.selectByBusinessKey(businessKey);
        if (actHiProcinst == null) {
            return StrUtil.EMPTY;
        }
        return actHiProcinst.getId();
    public Long getInstanceIdByBusinessId(String businessId) {
        FlowInstance flowInstance = flwInstanceService.selectInstByBusinessId(businessId);
        return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getId() : null;
    }
    /**
     * æ–°å¢žç§Ÿæˆ·æµç¨‹å®šä¹‰
     *
     * @param tenantId ç§Ÿæˆ·id
     */
    @Override
    public void syncDef(String tenantId) {
        flwDefinitionService.syncDef(tenantId);
    }
    /**
     * å¯åŠ¨æµç¨‹
     *
     * @param startProcess å‚æ•°
     */
    @Override
    public StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess) {
        return flwTaskService.startWorkFlow(BeanUtil.toBean(startProcess, StartProcessBo.class));
    }
    /**
     * åŠžç†ä»»åŠ¡
     *
     * @param completeTask å‚æ•°
     */
    @Override
    public boolean completeTask(CompleteTaskDTO completeTask) {
        return flwTaskService.completeTask(BeanUtil.toBean(completeTask, CompleteTaskBo.class));
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java
ÎļþÒÑɾ³ý
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
@@ -2,43 +2,42 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.service.UserService;
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.mail.utils.MailUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.warm.flow.core.constant.ExceptionCons;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.Node;
import org.dromara.warm.flow.core.entity.Task;
import org.dromara.warm.flow.core.entity.User;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.enums.SkipType;
import org.dromara.warm.flow.core.service.NodeService;
import org.dromara.warm.flow.core.service.TaskService;
import org.dromara.warm.flow.core.service.UserService;
import org.dromara.warm.flow.core.utils.AssertUtil;
import org.dromara.warm.flow.orm.entity.FlowNode;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.entity.FlowUser;
import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.dromara.workflow.common.enums.MessageTypeEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiTaskinst;
import org.dromara.workflow.domain.vo.MultiInstanceVo;
import org.dromara.workflow.domain.vo.ParticipantVo;
import org.dromara.workflow.flowable.cmd.UpdateHiTaskInstCmd;
import org.dromara.workflow.mapper.ActHiTaskinstMapper;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
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.dromara.workflow.service.IFlwTaskAssigneeService;
import org.dromara.workflow.service.IFlwTaskService;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * å·¥ä½œæµå·¥å…·
@@ -48,220 +47,84 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class WorkflowUtils {
    private static final ProcessEngine PROCESS_ENGINE = SpringUtils.getBean(ProcessEngine.class);
    private static final ActHiTaskinstMapper ACT_HI_TASKINST_MAPPER = SpringUtils.getBean(ActHiTaskinstMapper.class);
    private static final IFlwTaskAssigneeService TASK_ASSIGNEE_SERVICE = SpringUtils.getBean(IFlwTaskAssigneeService.class);
    private static final IFlwTaskService FLW_TASK_SERVICE = SpringUtils.getBean(IFlwTaskService.class);
    private static final FlowNodeMapper FLOW_NODE_MAPPER = SpringUtils.getBean(FlowNodeMapper.class);
    private static final FlowTaskMapper FLOW_TASK_MAPPER = SpringUtils.getBean(FlowTaskMapper.class);
    private static final UserService USER_SERVICE = SpringUtils.getBean(UserService.class);
    private static final TaskService TASK_SERVICE = SpringUtils.getBean(TaskService.class);
    private static final NodeService NODE_SERVICE = SpringUtils.getBean(NodeService.class);
    /**
     * åˆ›å»ºä¸€ä¸ªæ–°ä»»åŠ¡
     *
     * @param currentTask å‚æ•°
     * èŽ·å–å·¥ä½œæµç”¨æˆ·service
     */
    public static TaskEntity createNewTask(Task currentTask) {
        TaskEntity task = null;
        if (ObjectUtil.isNotEmpty(currentTask)) {
            task = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
            task.setCategory(currentTask.getCategory());
            task.setDescription(currentTask.getDescription());
            task.setAssignee(currentTask.getAssignee());
            task.setName(currentTask.getName());
            task.setProcessDefinitionId(currentTask.getProcessDefinitionId());
            task.setProcessInstanceId(currentTask.getProcessInstanceId());
            task.setTaskDefinitionKey(currentTask.getTaskDefinitionKey());
            task.setPriority(currentTask.getPriority());
            task.setCreateTime(new Date());
            task.setTenantId(TenantHelper.getTenantId());
            PROCESS_ENGINE.getTaskService().saveTask(task);
        }
        if (ObjectUtil.isNotNull(task)) {
            UpdateHiTaskInstCmd updateHiTaskInstCmd = new UpdateHiTaskInstCmd(Collections.singletonList(task.getId()), task.getProcessDefinitionId(), task.getProcessInstanceId());
            PROCESS_ENGINE.getManagementService().executeCommand(updateHiTaskInstCmd);
        }
        return task;
    public static UserService getFlowUserService() {
        return USER_SERVICE;
    }
    /**
     * æŠ„送任务
     * æž„建工作流用户
     *
     * @param parentTaskList çˆ¶çº§ä»»åŠ¡
     * @param userIds        äººå‘˜id
     * @param userList åŠžç†ç”¨æˆ·
     * @param taskId   ä»»åŠ¡ID
     * @return ç”¨æˆ·
     */
    public static void createCopyTask(List<Task> parentTaskList, List<Long> userIds) {
        List<Task> list = new ArrayList<>();
        String tenantId = TenantHelper.getTenantId();
        for (Task parentTask : parentTaskList) {
            for (Long userId : userIds) {
                TaskEntity newTask = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
                newTask.setParentTaskId(parentTask.getId());
                newTask.setAssignee(userId.toString());
                newTask.setName("【抄送】-" + parentTask.getName());
                newTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
                newTask.setProcessInstanceId(parentTask.getProcessInstanceId());
                newTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
                newTask.setTenantId(tenantId);
                list.add(newTask);
    public static Set<User> buildUser(List<User> userList, Long taskId) {
        if (CollUtil.isEmpty(userList)) {
            return Set.of();
        }
        Set<User> list = new HashSet<>();
        Set<String> processedBySet = new HashSet<>();
        for (User user : userList) {
            // æ ¹æ® processedBy å‰ç¼€åˆ¤æ–­å¤„理人类型,分别获取用户列表
            List<UserDTO> users = TASK_ASSIGNEE_SERVICE.fetchUsersByStorageId(user.getProcessedBy());
            // è½¬æ¢ä¸º FlowUser å¹¶æ·»åŠ åˆ°ç»“æžœé›†åˆ
            if (CollUtil.isNotEmpty(users)) {
                users.forEach(dto -> {
                    String processedBy = String.valueOf(dto.getUserId());
                    if (!processedBySet.contains(processedBy)) {
                        FlowUser flowUser = new FlowUser();
                        flowUser.setType(user.getType());
                        flowUser.setProcessedBy(processedBy);
                        flowUser.setAssociated(taskId);
                        list.add(flowUser);
                        processedBySet.add(processedBy);
                    }
                });
            }
        }
        PROCESS_ENGINE.getTaskService().bulkSaveTasks(list);
        if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(parentTaskList)) {
            String processInstanceId = parentTaskList.get(0).getProcessInstanceId();
            String processDefinitionId = parentTaskList.get(0).getProcessDefinitionId();
            List<String> taskIds = StreamUtils.toList(list, Task::getId);
            ActHiTaskinst actHiTaskinst = new ActHiTaskinst();
            actHiTaskinst.setProcDefId(processDefinitionId);
            actHiTaskinst.setProcInstId(processInstanceId);
            actHiTaskinst.setScopeType(TaskStatusEnum.COPY.getStatus());
            actHiTaskinst.setTenantId(tenantId);
            LambdaUpdateWrapper<ActHiTaskinst> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.in(ActHiTaskinst::getId, taskIds);
            ACT_HI_TASKINST_MAPPER.update(actHiTaskinst, updateWrapper);
            for (Task task : list) {
                PROCESS_ENGINE.getTaskService().addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), StrUtil.EMPTY);
            }
        }
    }
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡å‚ä¸Žè€…
     *
     * @param taskId ä»»åŠ¡id
     */
    public static ParticipantVo getCurrentTaskParticipant(String taskId, UserService userService) {
        ParticipantVo participantVo = new ParticipantVo();
        List<HistoricIdentityLink> linksForTask = PROCESS_ENGINE.getHistoryService().getHistoricIdentityLinksForTask(taskId);
        Task task = QueryUtils.taskQuery().taskId(taskId).singleResult();
        if (task != null && CollUtil.isNotEmpty(linksForTask)) {
            List<HistoricIdentityLink> groupList = StreamUtils.filter(linksForTask, e -> StringUtils.isNotBlank(e.getGroupId()));
            if (CollUtil.isNotEmpty(groupList)) {
                List<Long> groupIds = StreamUtils.toList(groupList, e -> Long.valueOf(e.getGroupId()));
                List<Long> userIds = userService.selectUserIdsByRoleIds(groupIds);
                if (CollUtil.isNotEmpty(userIds)) {
                    participantVo.setGroupIds(groupIds);
                    List<UserDTO> userList = userService.selectListByIds(userIds);
                    if (CollUtil.isNotEmpty(userList)) {
                        List<Long> userIdList = StreamUtils.toList(userList, UserDTO::getUserId);
                        List<String> nickNames = StreamUtils.toList(userList, UserDTO::getNickName);
                        participantVo.setCandidate(userIdList);
                        participantVo.setCandidateName(nickNames);
                        participantVo.setClaim(!StringUtils.isBlank(task.getAssignee()));
                    }
                }
            } else {
                List<HistoricIdentityLink> candidateList = StreamUtils.filter(linksForTask, e -> FlowConstant.CANDIDATE.equals(e.getType()));
                List<Long> userIdList = new ArrayList<>();
                for (HistoricIdentityLink historicIdentityLink : linksForTask) {
                    try {
                        userIdList.add(Long.valueOf(historicIdentityLink.getUserId()));
                    } catch (NumberFormatException ignored) {
                    }
                }
                List<UserDTO> userList = userService.selectListByIds(userIdList);
                if (CollUtil.isNotEmpty(userList)) {
                    List<Long> userIds = StreamUtils.toList(userList, UserDTO::getUserId);
                    List<String> nickNames = StreamUtils.toList(userList, UserDTO::getNickName);
                    participantVo.setCandidate(userIds);
                    participantVo.setCandidateName(nickNames);
                    // åˆ¤æ–­å½“前任务是否具有多个办理人
                    if (CollUtil.isNotEmpty(candidateList) && candidateList.size() > 1) {
                        // å¦‚æžœ assignee å­˜åœ¨ï¼Œåˆ™è®¾ç½®å½“前任务已经被认领
                        participantVo.setClaim(StringUtils.isNotBlank(task.getAssignee()));
                    }
                }
            }
        }
        return participantVo;
    }
    /**
     * åˆ¤æ–­å½“前节点是否为会签节点
     *
     * @param processDefinitionId æµç¨‹å®šä¹‰id
     * @param taskDefinitionKey   æµç¨‹å®šä¹‰id
     */
    public static MultiInstanceVo isMultiInstance(String processDefinitionId, String taskDefinitionKey) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
        MultiInstanceVo multiInstanceVo = new MultiInstanceVo();
        //判断是否为并行会签节点
        if (flowNode.getBehavior() instanceof ParallelMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
            Expression collectionExpression = behavior.getCollectionExpression();
            String assigneeList = collectionExpression.getExpressionText();
            String assignee = behavior.getCollectionElementVariable();
            multiInstanceVo.setType(behavior);
            multiInstanceVo.setAssignee(assignee);
            multiInstanceVo.setAssigneeList(assigneeList);
            return multiInstanceVo;
            //判断是否为串行会签节点
        } else if (flowNode.getBehavior() instanceof SequentialMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
            Expression collectionExpression = behavior.getCollectionExpression();
            String assigneeList = collectionExpression.getExpressionText();
            String assignee = behavior.getCollectionElementVariable();
            multiInstanceVo.setType(behavior);
            multiInstanceVo.setAssignee(assignee);
            multiInstanceVo.setAssigneeList(assigneeList);
            return multiInstanceVo;
        }
        return null;
    }
    /**
     * èŽ·å–å½“å‰æµç¨‹çŠ¶æ€
     *
     * @param taskId ä»»åŠ¡id
     */
    public static String getBusinessStatusByTaskId(String taskId) {
        HistoricTaskInstance historicTaskInstance = QueryUtils.hisTaskInstanceQuery().taskId(taskId).singleResult();
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery(historicTaskInstance.getProcessInstanceId()).singleResult();
        return historicProcessInstance.getBusinessStatus();
    }
    /**
     * èŽ·å–å½“å‰æµç¨‹çŠ¶æ€
     *
     * @param businessKey ä¸šåŠ¡id
     */
    public static String getBusinessStatus(String businessKey) {
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisBusinessKeyQuery(businessKey).singleResult();
        return historicProcessInstance.getBusinessStatus();
        return list;
    }
    /**
     * å‘送消息
     *
     * @param list        ä»»åŠ¡
     * @param name        æµç¨‹åç§°
     * @param flowName    æµç¨‹å®šä¹‰åç§°
     * @param messageType æ¶ˆæ¯ç±»åž‹
     * @param message     æ¶ˆæ¯å†…容,为空则发送默认配置的消息内容
     */
    public static void sendMessage(List<Task> list, String name, List<String> messageType, String message, UserService userService) {
        Set<Long> userIds = new HashSet<>();
    public static void sendMessage(String flowName, Long instId, List<String> messageType, String message) {
        List<UserDTO> userList = new ArrayList<>();
        List<FlowTask> list = FLW_TASK_SERVICE.selectByInstId(instId);
        if (StringUtils.isBlank(message)) {
            message = "有新的【" + name + "】单据已经提交至您的待办,请您及时处理。";
            message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。";
        }
        for (Task t : list) {
            ParticipantVo taskParticipant = WorkflowUtils.getCurrentTaskParticipant(t.getId(), userService);
            if (CollUtil.isNotEmpty(taskParticipant.getGroupIds())) {
                List<Long> userIdList = userService.selectUserIdsByRoleIds(taskParticipant.getGroupIds());
                if (CollUtil.isNotEmpty(userIdList)) {
                    userIds.addAll(userIdList);
                }
            }
            List<Long> candidate = taskParticipant.getCandidate();
            if (CollUtil.isNotEmpty(candidate)) {
                userIds.addAll(candidate);
        for (Task task : list) {
            List<UserDTO> users = FLW_TASK_SERVICE.currentTaskAllUser(task.getId());
            if (CollUtil.isNotEmpty(users)) {
                userList.addAll(users);
            }
        }
        if (CollUtil.isNotEmpty(userIds)) {
            List<UserDTO> userList = userService.selectListByIds(new ArrayList<>(userIds));
        if (CollUtil.isNotEmpty(userList)) {
            for (String code : messageType) {
                MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
                if (ObjectUtil.isNotEmpty(messageTypeEnum)) {
                    switch (messageTypeEnum) {
                        case SYSTEM_MESSAGE:
                            WebSocketMessageDto dto = new WebSocketMessageDto();
                            dto.setSessionKeys(new ArrayList<>(userIds));
                            SseMessageDto dto = new SseMessageDto();
                            dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList()));
                            dto.setMessage(message);
                            WebSocketUtils.publishMessage(dto);
                            SseMessageUtils.publishMessage(dto);
                            break;
                        case EMAIL_MESSAGE:
                            MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message);
@@ -269,6 +132,8 @@
                        case SMS_MESSAGE:
                            //todo çŸ­ä¿¡å‘送
                            break;
                        default:
                            throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
                    }
                }
            }
@@ -276,20 +141,66 @@
    }
    /**
     * æ ¹æ®ä»»åŠ¡id查询 å½“前用户的任务,检查 å½“前人员 æ˜¯å¦æ˜¯è¯¥ taskId çš„办理人
     * é©³å›ž
     *
     * @param taskId ä»»åŠ¡id
     * @return ç»“æžœ
     * @param message        å®¡æ‰¹æ„è§
     * @param instanceId     æµç¨‹å®žä¾‹id
     * @param targetNodeCode ç›®æ ‡èŠ‚ç‚¹
     * @param flowStatus     æµç¨‹çŠ¶æ€
     * @param flowHisStatus  èŠ‚ç‚¹æ“ä½œçŠ¶æ€
     */
    public static Task getTaskByCurrentUser(String taskId) {
        TaskQuery taskQuery = QueryUtils.taskQuery();
        taskQuery.taskId(taskId).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));
        List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
        if (CollUtil.isNotEmpty(roles)) {
            List<String> groupIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId()));
            taskQuery.taskCandidateGroupIn(groupIds);
    public static void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) {
        List<FlowTask> list = FLW_TASK_SERVICE.selectByInstId(instanceId);
        if (CollUtil.isNotEmpty(list)) {
            List<FlowTask> tasks = StreamUtils.filter(list, e -> e.getNodeCode().equals(targetNodeCode));
            if (list.size() == tasks.size()) {
                return;
            }
        }
        return taskQuery.singleResult();
        for (FlowTask task : list) {
            List<UserDTO> userList = FLW_TASK_SERVICE.currentTaskAllUser(task.getId());
            FlowParams flowParams = FlowParams.build();
            flowParams.nodeCode(targetNodeCode);
            flowParams.message(message);
            flowParams.skipType(SkipType.PASS.getKey());
            flowParams.flowStatus(flowStatus).hisStatus(flowHisStatus);
            flowParams.ignore(true);
            //解决会签没权限问题
            if (CollUtil.isNotEmpty(userList)) {
                flowParams.handler(userList.get(0).getUserId().toString());
            }
            TASK_SERVICE.skip(task.getId(), flowParams);
        }
        //解决会签多人审批问题
        backTask(message, instanceId, targetNodeCode, flowStatus, flowHisStatus);
    }
    /**
     * ç”³è¯·äººèŠ‚ç‚¹ç¼–ç 
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @return ç”³è¯·äººèŠ‚ç‚¹ç¼–ç 
     */
    public static String applyNodeCode(Long definitionId) {
        //获取已发布的流程节点
        List<FlowNode> flowNodes = FLOW_NODE_MAPPER.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, definitionId));
        AssertUtil.isTrue(CollUtil.isEmpty(flowNodes), ExceptionCons.NOT_PUBLISH_NODE);
        Node startNode = flowNodes.stream().filter(t -> NodeType.isStart(t.getNodeType())).findFirst().orElse(null);
        AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE);
        Node nextNode = NODE_SERVICE.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
        return nextNode.getNodeCode();
    }
    /**
     * åˆ é™¤è¿è¡Œä¸­çš„任务
     *
     * @param taskIds ä»»åŠ¡id
     */
    public static void deleteRunTask(List<Long> taskIds) {
        if (CollUtil.isEmpty(taskIds)) {
            return;
        }
        USER_SERVICE.deleteByTaskIds(taskIds);
        FLOW_TASK_MAPPER.deleteByIds(taskIds);
    }
}
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/FlwCategoryMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.workflow.mapper.FlwCategoryMapper">
    <select id="countCategoryById" resultType="Long">
        select count(*) from flow_category where del_flag = '0' and category_id = #{categoryId}
    </select>
</mapper>
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.workflow.mapper.FlwInstanceMapper">
    <resultMap type="org.dromara.workflow.domain.vo.FlowInstanceVo" id="FlowInstanceResult">
    </resultMap>
    <select id="selectInstanceList" resultMap="FlowInstanceResult">
        select fi.id,
               fi.create_time,
               fi.update_time,
               fi.tenant_id,
               fi.del_flag,
               fi.definition_id,
               fi.business_id,
               fi.node_type,
               fi.node_code,
               fi.node_name,
               fi.variable,
               fi.flow_status,
               fi.activity_status,
               fi.create_by,
               fi.ext,
               fd.flow_name,
               fd.flow_code,
               fd.version,
               fd.form_custom,
               fd.form_path,
               fd.category
        from flow_instance fi
                 left join flow_definition fd on fi.definition_id = fd.id
                ${ew.getCustomSqlSegment}
    </select>
</mapper>
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.workflow.mapper.FlwTaskMapper">
    <resultMap type="org.dromara.workflow.domain.vo.FlowTaskVo" id="FlowTaskResult">
    </resultMap>
    <resultMap type="org.dromara.workflow.domain.vo.FlowHisTaskVo" id="FlowHisTaskResult">
    </resultMap>
    <select id="getListRunTask" resultMap="FlowTaskResult">
        select * from (
            select distinct
                t.id,
                t.node_code,
                t.node_name,
                t.node_type,
                t.definition_id,
                t.instance_id,
                t.create_time,
                t.update_time,
                t.tenant_id,
                i.business_id,
                i.flow_status,
                i.create_by,
                d.flow_name,
                d.flow_code,
                d.form_custom,
                d.category,
                COALESCE(t.form_path, d.form_path) as form_path,
                d.version,
                uu.processed_by,
                uu.type
            from flow_task as t
                    left join flow_user uu on uu.associated = t.id
                    left join flow_definition d on t.definition_id = d.id
                    left join flow_instance i on t.instance_id = i.id
            where t.node_type = 1
              and t.del_flag = '0'
              and uu.del_flag = '0'
              and uu.type in ('1','2','3')
         ) t
         ${ew.getCustomSqlSegment}
    </select>
    <select id="getListFinishTask" resultMap="FlowHisTaskResult">
        select * from (
            select
                a.id,
                a.node_code,
                a.node_name,
                a.cooperate_type,
                a.approver,
                a.collaborator,
                a.node_type,
                a.target_node_code,
                a.target_node_name,
                a.definition_id,
                a.instance_id,
                a.flow_status flow_task_status,
                a.message,
                a.ext,
                a.create_time,
                a.update_time,
                a.tenant_id,
                a.form_custom,
                a.form_path,
                b.flow_status,
                b.business_id,
                b.create_by,
                c.flow_name,
                c.flow_code,
                c.category,
                c.version
            from flow_his_task a
                    left join flow_instance b on a.instance_id = b.id
                    left join flow_definition c on a.definition_id = c.id
            where a.del_flag ='0'
              and b.del_flag = '0'
              and c.del_flag = '0'
              and a.node_type in ('1','3','4')
        ) t
        ${ew.getCustomSqlSegment}
    </select>
    <select id="getTaskCopyByPage" resultMap="FlowTaskResult">
       select * from (
            select
                b.id,
                b.update_time,
                c.business_id,
                c.flow_status,
                c.create_by,
                a.processed_by,
                a.create_time,
                b.form_custom,
                b.form_path,
                b.node_name,
                b.node_code,
                d.flow_name,
                d.flow_code,
                d.category,
                d.version
            from flow_user a
                left join flow_his_task b on a.associated = b.task_id
                left join flow_instance c on b.instance_id = c.id
                left join flow_definition d on c.definition_id=d.id
            where a.type = '4'
               and a.del_flag = '0'
               and b.del_flag = '0'
               and d.del_flag = '0'
            ) t
        ${ew.getCustomSqlSegment}
    </select>
</mapper>
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/bpmn/Ä£ÐÍ.zip
Binary files differ
script/docker/database.yml
@@ -1,5 +1,3 @@
version: '3'
services:
  # æ­¤é•œåƒä»…用于测试 æ­£å¼çŽ¯å¢ƒéœ€è‡ªè¡Œå®‰è£…æ•°æ®åº“
  # SID: XE user: system password: oracle
script/docker/docker-compose.yml
@@ -1,5 +1,3 @@
version: '3'
services:
  mysql:
    image: mysql:8.0.33
@@ -100,7 +98,7 @@
    network_mode: "host"
  ruoyi-server1:
    image: ruoyi/ruoyi-server:5.2.2
    image: ruoyi/ruoyi-server:5.3.0
    container_name: ruoyi-server1
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -115,7 +113,7 @@
    network_mode: "host"
  ruoyi-server2:
    image: ruoyi/ruoyi-server:5.2.2
    image: ruoyi/ruoyi-server:5.3.0
    container_name: ruoyi-server2
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -130,7 +128,7 @@
    network_mode: "host"
  ruoyi-monitor-admin:
    image: ruoyi/ruoyi-monitor-admin:5.2.2
    image: ruoyi/ruoyi-monitor-admin:5.3.0
    container_name: ruoyi-monitor-admin
    environment:
      # æ—¶åŒºä¸Šæµ·
@@ -142,7 +140,7 @@
    network_mode: "host"
  ruoyi-snailjob-server:
    image: ruoyi/ruoyi-snailjob-server:5.2.2
    image: ruoyi/ruoyi-snailjob-server:5.3.0
    container_name: ruoyi-snailjob-server
    environment:
      # æ—¶åŒºä¸Šæµ·
script/leave/leave1.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
{
  "flowCode" : "leave1",
  "flowName" : "请假申请-普通",
  "category" : "1",
  "version" : "1",
  "formCustom" : "N",
  "formPath" : "/workflow/leaveEdit/index",
  "nodeList" : [ {
    "nodeType" : 0,
    "nodeCode" : "d5ee3ddf-3968-4379-a86f-9ceabde5faac",
    "nodeName" : "开始",
    "nodeRatio" : 0.000,
    "coordinate" : "200,200|200,200",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "d5ee3ddf-3968-4379-a86f-9ceabde5faac",
      "nextNodeCode" : "dd515cdd-59f6-446f-94ca-25ca062afb42",
      "skipType" : "PASS",
      "coordinate" : "220,200;310,200"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "dd515cdd-59f6-446f-94ca-25ca062afb42",
    "nodeName" : "申请人",
    "nodeRatio" : 0.000,
    "coordinate" : "360,200|360,200",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "dd515cdd-59f6-446f-94ca-25ca062afb42",
      "nextNodeCode" : "78fa8e5b-e809-44ed-978a-41092409ebcf",
      "skipType" : "PASS",
      "coordinate" : "410,200;490,200"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "78fa8e5b-e809-44ed-978a-41092409ebcf",
    "nodeName" : "组长",
    "permissionFlag" : "role:1",
    "nodeRatio" : 0.000,
    "coordinate" : "540,200|540,200",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "78fa8e5b-e809-44ed-978a-41092409ebcf",
      "nextNodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe",
      "skipType" : "PASS",
      "coordinate" : "590,200;670,200"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe",
    "nodeName" : "部门主管",
    "permissionFlag" : "role:3,role:4",
    "nodeRatio" : 0.000,
    "coordinate" : "720,200|720,200",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe",
      "nextNodeCode" : "8b82b7d7-8660-455e-b880-d6d22ea3eb6d",
      "skipType" : "PASS",
      "coordinate" : "770,200;880,200"
    } ]
  }, {
    "nodeType" : 2,
    "nodeCode" : "8b82b7d7-8660-455e-b880-d6d22ea3eb6d",
    "nodeName" : "结束",
    "nodeRatio" : 0.000,
    "coordinate" : "900,200|900,200",
    "skipAnyNode" : "N",
    "formCustom" : "N"
  } ]
}
script/leave/leave2.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
{
  "flowCode" : "leave2",
  "flowName" : "请假申请-排他网关",
  "category" : "1",
  "version" : "1",
  "formCustom" : "N",
  "formPath" : "/workflow/leaveEdit/index",
  "nodeList" : [ {
    "nodeType" : 0,
    "nodeCode" : "cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a",
    "nodeName" : "开始",
    "nodeRatio" : 0.000,
    "coordinate" : "300,240|300,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a",
      "nextNodeCode" : "fdcae93b-b69c-498a-b231-09255e74bcbd",
      "skipType" : "PASS",
      "coordinate" : "320,240;390,240"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "fdcae93b-b69c-498a-b231-09255e74bcbd",
    "nodeName" : "申请人",
    "nodeRatio" : 0.000,
    "coordinate" : "440,240|440,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "fdcae93b-b69c-498a-b231-09255e74bcbd",
      "nextNodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
      "skipType" : "PASS",
      "coordinate" : "490,240;535,240"
    } ]
  }, {
    "nodeType" : 3,
    "nodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
    "nodeRatio" : 0.000,
    "coordinate" : "560,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
      "nextNodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86",
      "skipType" : "PASS",
      "skipCondition" : "le@@leaveDays|2",
      "coordinate" : "560,265;560,320;670,320"
    }, {
      "nowNodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
      "nextNodeCode" : "5ed2362b-fc0c-4d52-831f-95208b830605",
      "skipName" : "大于两天",
      "skipType" : "PASS",
      "skipCondition" : "gt@@leaveDays|2",
      "coordinate" : "560,215;560,160;670,160|560,187"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86",
    "nodeName" : "组长",
    "permissionFlag" : "3,4",
    "nodeRatio" : 0.000,
    "coordinate" : "720,320|720,320",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86",
      "nextNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
      "skipType" : "PASS",
      "coordinate" : "770,320;860,320;860,280"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
    "nodeName" : "总经理",
    "permissionFlag" : "role:1",
    "nodeRatio" : 0.000,
    "coordinate" : "860,240|860,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
      "nextNodeCode" : "40aa65fd-0712-4d23-b6f7-d0432b920fd1",
      "skipType" : "PASS",
      "coordinate" : "910,240;980,240"
    } ]
  }, {
    "nodeType" : 2,
    "nodeCode" : "40aa65fd-0712-4d23-b6f7-d0432b920fd1",
    "nodeName" : "结束",
    "nodeRatio" : 0.000,
    "coordinate" : "1000,240|1000,240",
    "skipAnyNode" : "N",
    "formCustom" : "N"
  }, {
    "nodeType" : 1,
    "nodeCode" : "5ed2362b-fc0c-4d52-831f-95208b830605",
    "nodeName" : "部门领导",
    "permissionFlag" : "role:1",
    "nodeRatio" : 0.000,
    "coordinate" : "720,160|720,160",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "5ed2362b-fc0c-4d52-831f-95208b830605",
      "nextNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
      "skipType" : "PASS",
      "coordinate" : "770,160;860,160;860,200"
    } ]
  } ]
}
script/leave/leave3.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
{
  "flowCode" : "leave3",
  "flowName" : "请假申请-并行网关",
  "category" : "1",
  "version" : "1",
  "formCustom" : "N",
  "formPath" : "/workflow/leaveEdit/index",
  "nodeList" : [ {
    "nodeType" : 0,
    "nodeCode" : "a80ecf9f-f465-4ae5-a429-e30ec5d0f957",
    "nodeName" : "开始",
    "nodeRatio" : 0.000,
    "coordinate" : "380,220|380,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "a80ecf9f-f465-4ae5-a429-e30ec5d0f957",
      "nextNodeCode" : "b7bbb571-06de-455c-8083-f83c07bf0b99",
      "skipType" : "PASS",
      "coordinate" : "400,220;470,220"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "b7bbb571-06de-455c-8083-f83c07bf0b99",
    "nodeName" : "申请人",
    "nodeRatio" : 0.000,
    "coordinate" : "520,220|520,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "b7bbb571-06de-455c-8083-f83c07bf0b99",
      "nextNodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
      "skipType" : "PASS",
      "coordinate" : "570,220;655,220"
    } ]
  }, {
    "nodeType" : 4,
    "nodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
    "nodeRatio" : 0.000,
    "coordinate" : "680,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
      "nextNodeCode" : "4b7743cd-940c-431b-926f-e7b614fbf1fe",
      "skipType" : "PASS",
      "coordinate" : "680,195;680,140;750,140"
    }, {
      "nowNodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
      "nextNodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394",
      "skipType" : "PASS",
      "coordinate" : "680,245;680,300;750,300"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "4b7743cd-940c-431b-926f-e7b614fbf1fe",
    "nodeName" : "市场部",
    "permissionFlag" : "role:1",
    "nodeRatio" : 0.000,
    "coordinate" : "800,140|800,140",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "4b7743cd-940c-431b-926f-e7b614fbf1fe",
      "nextNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
      "skipType" : "PASS",
      "coordinate" : "850,140;920,140;920,195"
    } ]
  }, {
    "nodeType" : 4,
    "nodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
    "nodeRatio" : 0.000,
    "coordinate" : "920,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
      "nextNodeCode" : "23e7429e-2b47-4431-b93e-40db7c431ce6",
      "skipType" : "PASS",
      "coordinate" : "945,220;975,220;975,220;960,220;960,220;990,220"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "23e7429e-2b47-4431-b93e-40db7c431ce6",
    "nodeName" : "CEO",
    "permissionFlag" : "1",
    "nodeRatio" : 0.000,
    "coordinate" : "1040,220|1040,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "23e7429e-2b47-4431-b93e-40db7c431ce6",
      "nextNodeCode" : "f5ace37f-5a5e-4e64-a6f6-913ab9a71cd1",
      "skipType" : "PASS",
      "coordinate" : "1090,220;1140,220"
    } ]
  }, {
    "nodeType" : 2,
    "nodeCode" : "f5ace37f-5a5e-4e64-a6f6-913ab9a71cd1",
    "nodeName" : "结束",
    "nodeRatio" : 0.000,
    "coordinate" : "1160,220|1160,220",
    "skipAnyNode" : "N",
    "formCustom" : "N"
  }, {
    "nodeType" : 1,
    "nodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394",
    "nodeName" : "综合部",
    "permissionFlag" : "role:3,role:4",
    "nodeRatio" : 0.000,
    "coordinate" : "800,300|800,300",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394",
      "nextNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
      "skipType" : "PASS",
      "coordinate" : "850,300;920,300;920,245"
    } ]
  } ]
}
script/leave/leave4.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
{
  "flowCode" : "leave4",
  "flowName" : "请假申请-会签",
  "category" : "1",
  "version" : "1",
  "formCustom" : "N",
  "formPath" : "/workflow/leaveEdit/index",
  "nodeList" : [ {
    "nodeType" : 0,
    "nodeCode" : "9ce8bf00-f25b-4fc6-91b8-827082fc4876",
    "nodeName" : "开始",
    "nodeRatio" : 0.000,
    "coordinate" : "320,240|320,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "9ce8bf00-f25b-4fc6-91b8-827082fc4876",
      "nextNodeCode" : "e90b98ef-35b4-410c-a663-bae8b7624b9f",
      "skipType" : "PASS",
      "coordinate" : "340,240;410,240"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "e90b98ef-35b4-410c-a663-bae8b7624b9f",
    "nodeName" : "申请人",
    "nodeRatio" : 0.000,
    "coordinate" : "460,240|460,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "e90b98ef-35b4-410c-a663-bae8b7624b9f",
      "nextNodeCode" : "768b5b1a-6726-4d67-8853-4cc70d5b1045",
      "skipType" : "PASS",
      "coordinate" : "510,240;590,240"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "768b5b1a-6726-4d67-8853-4cc70d5b1045",
    "nodeName" : "百分之60通过",
    "permissionFlag" : "${userList}",
    "nodeRatio" : 60.000,
    "coordinate" : "640,240|640,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "768b5b1a-6726-4d67-8853-4cc70d5b1045",
      "nextNodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
      "skipType" : "PASS",
      "coordinate" : "690,240;770,240"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
    "nodeName" : "全部审批通过",
    "permissionFlag" : "role:1,role:3",
    "nodeRatio" : 100.000,
    "coordinate" : "820,240|820,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
      "nextNodeCode" : "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
      "skipType" : "PASS",
      "coordinate" : "870,240;950,240"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
    "nodeName" : "CEO",
    "permissionFlag" : "1",
    "nodeRatio" : 0.000,
    "coordinate" : "1000,240|1000,240",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
      "nextNodeCode" : "b62b88c3-8d8d-4969-911e-2aaea219e7fc",
      "skipType" : "PASS",
      "coordinate" : "1050,240;1080,240;1080,240;1070,240;1070,240;1100,240"
    } ]
  }, {
    "nodeType" : 2,
    "nodeCode" : "b62b88c3-8d8d-4969-911e-2aaea219e7fc",
    "nodeName" : "结束",
    "nodeRatio" : 0.000,
    "coordinate" : "1120,240|1120,240",
    "skipAnyNode" : "N",
    "formCustom" : "N"
  } ]
}
script/leave/leave5.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
{
  "flowCode" : "leave5",
  "flowName" : "请假申请-并行会签网关",
  "category" : "1",
  "version" : "1",
  "formCustom" : "N",
  "formPath" : "/workflow/leaveEdit/index",
  "nodeList" : [ {
    "nodeType" : 0,
    "nodeCode" : "ebebaf26-9cb6-497e-8119-4c9fed4c597c",
    "nodeName" : "开始",
    "nodeRatio" : 0.000,
    "coordinate" : "300,220|300,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "ebebaf26-9cb6-497e-8119-4c9fed4c597c",
      "nextNodeCode" : "e1b04e96-dc81-4858-a309-2fe945d2f374",
      "skipType" : "PASS",
      "coordinate" : "320,220;350,220;350,220;340,220;340,220;370,220"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "e1b04e96-dc81-4858-a309-2fe945d2f374",
    "nodeName" : "申请人",
    "nodeRatio" : 0.000,
    "coordinate" : "420,220|420,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "e1b04e96-dc81-4858-a309-2fe945d2f374",
      "nextNodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
      "skipType" : "PASS",
      "coordinate" : "470,220;535,220"
    } ]
  }, {
    "nodeType" : 4,
    "nodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
    "nodeRatio" : 0.000,
    "coordinate" : "560,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
      "nextNodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
      "skipType" : "PASS",
      "coordinate" : "560,245;560,320;650,320"
    }, {
      "nowNodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
      "nextNodeCode" : "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
      "skipType" : "PASS",
      "coordinate" : "560,195;560,120;650,120"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
    "nodeName" : "会签",
    "permissionFlag" : "role:1,role:3",
    "nodeRatio" : 100.000,
    "coordinate" : "700,320|700,320",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
      "nextNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b",
      "skipType" : "PASS",
      "coordinate" : "750,320;860,320;860,245"
    } ]
  }, {
    "nodeType" : 4,
    "nodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b",
    "nodeRatio" : 0.000,
    "coordinate" : "860,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b",
      "nextNodeCode" : "7a8f0473-e409-442e-a843-5c2b813d00e9",
      "skipType" : "PASS",
      "coordinate" : "885,220;950,220"
    } ]
  }, {
    "nodeType" : 1,
    "nodeCode" : "7a8f0473-e409-442e-a843-5c2b813d00e9",
    "nodeName" : "CEO",
    "permissionFlag" : "1",
    "nodeRatio" : 0.000,
    "coordinate" : "1000,220|1000,220",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "7a8f0473-e409-442e-a843-5c2b813d00e9",
      "nextNodeCode" : "03c4d2bc-58b5-4408-a2e4-65afb046f169",
      "skipType" : "PASS",
      "coordinate" : "1050,220;1120,220"
    } ]
  }, {
    "nodeType" : 2,
    "nodeCode" : "03c4d2bc-58b5-4408-a2e4-65afb046f169",
    "nodeName" : "结束",
    "nodeRatio" : 0.000,
    "coordinate" : "1140,220|1140,220",
    "skipAnyNode" : "N",
    "formCustom" : "N"
  }, {
    "nodeType" : 1,
    "nodeCode" : "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
    "nodeName" : "百分之60票签",
    "permissionFlag" : "${userList}",
    "nodeRatio" : 60.000,
    "coordinate" : "700,120|700,120",
    "skipAnyNode" : "N",
    "formCustom" : "N",
    "skipList" : [ {
      "nowNodeCode" : "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
      "nextNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b",
      "skipType" : "PASS",
      "coordinate" : "750,120;860,120;860,195"
    } ]
  } ]
}
script/sql/flowable.sql
ÎļþÒÑɾ³ý
script/sql/oracle/flowable.sql
ÎļþÒÑɾ³ý
script/sql/oracle/oracle_ry_job.sql
ÎļþÃû´Ó script/sql/oracle/snail_job_oracle.sql ÐÞ¸Ä
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : Oracle
 Date: 2024-07-06 12:49:36
 Date: 2024-12-27 22:22:15
*/
@@ -74,6 +74,7 @@
COMMENT ON TABLE sj_group_config IS '组配置';
INSERT INTO sj_group_config (namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, bucket_index, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, sysdate, sysdate);
INSERT INTO sj_group_config (namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, bucket_index, create_dt, update_dt) VALUES ('prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, sysdate, sysdate);
-- sj_notify_config
CREATE TABLE sj_notify_config
@@ -81,7 +82,7 @@
    id                     number GENERATED ALWAYS AS IDENTITY,
    namespace_id           varchar2(64)  DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
    group_name             varchar2(64)                                             NULL,
    business_id            varchar2(64)                                             NULL,
    notify_name            varchar2(64)  DEFAULT ''                                 NULL,
    system_task_type       smallint      DEFAULT 3                                  NOT NULL,
    notify_status          smallint      DEFAULT 0                                  NOT NULL,
    recipient_ids          varchar2(128)                                            NULL,
@@ -97,12 +98,12 @@
ALTER TABLE sj_notify_config
    ADD CONSTRAINT pk_sj_notify_config PRIMARY KEY (id);
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name, business_id);
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name);
COMMENT ON COLUMN sj_notify_config.id IS '主键';
COMMENT ON COLUMN sj_notify_config.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_notify_config.group_name IS '组名称';
COMMENT ON COLUMN sj_notify_config.business_id IS '业务id  ( job_id或workflow_id或scene_name ) ';
COMMENT ON COLUMN sj_notify_config.notify_name IS '通知名称';
COMMENT ON COLUMN sj_notify_config.system_task_type IS '任务类型 1. é‡è¯•任务 2. é‡è¯•回调 3、JOB任务 4、WORKFLOW任务';
COMMENT ON COLUMN sj_notify_config.notify_status IS '通知状态 0、未启用 1、启用';
COMMENT ON COLUMN sj_notify_config.recipient_ids IS '接收人id列表';
@@ -320,6 +321,7 @@
    max_retry_count  number        DEFAULT 5                                  NOT NULL,
    back_off         smallint      DEFAULT 1                                  NOT NULL,
    trigger_interval varchar2(16)  DEFAULT ''                                 NULL,
    notify_ids       varchar2(128) DEFAULT ''                                 NULL,
    deadline_request number        DEFAULT 60000                              NOT NULL,
    executor_timeout number        DEFAULT 5                                  NOT NULL,
    route_key        smallint      DEFAULT 4                                  NOT NULL,
@@ -341,6 +343,7 @@
COMMENT ON COLUMN sj_retry_scene_config.max_retry_count IS '最大重试次数';
COMMENT ON COLUMN sj_retry_scene_config.back_off IS '1、默认等级 2、固定间隔时间 3、CRON è¡¨è¾¾å¼';
COMMENT ON COLUMN sj_retry_scene_config.trigger_interval IS '间隔时长';
COMMENT ON COLUMN sj_retry_scene_config.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_retry_scene_config.deadline_request IS 'Deadline Request è°ƒç”¨é“¾è¶…æ—¶ å•位毫秒';
COMMENT ON COLUMN sj_retry_scene_config.executor_timeout IS '任务执行超时时间,单位秒';
COMMENT ON COLUMN sj_retry_scene_config.route_key IS '路由策略';
@@ -505,6 +508,8 @@
    retry_interval   number        DEFAULT 0                                  NOT NULL,
    bucket_index     number        DEFAULT 0                                  NOT NULL,
    resident         smallint      DEFAULT 0                                  NOT NULL,
    notify_ids       varchar2(128) DEFAULT ''                                 NULL,
    owner_id         number                                                   NULL,
    description      varchar2(256) DEFAULT ''                                 NULL,
    ext_attrs        varchar2(256) DEFAULT ''                                 NULL,
    deleted          smallint      DEFAULT 0                                  NOT NULL,
@@ -540,6 +545,8 @@
COMMENT ON COLUMN sj_job.retry_interval IS '重试间隔 ( s ) ';
COMMENT ON COLUMN sj_job.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务';
COMMENT ON COLUMN sj_job.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_job.owner_id IS '负责人id';
COMMENT ON COLUMN sj_job.description IS '描述';
COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除';
@@ -547,7 +554,7 @@
COMMENT ON COLUMN sj_job.update_dt IS '修改时间';
COMMENT ON TABLE sj_job IS '任务信息';
INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', '', 0, sysdate, sysdate);
INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1,'', '', 0, sysdate, sysdate);
-- sj_job_log_message
CREATE TABLE sj_job_log_message
@@ -780,6 +787,7 @@
    description      varchar2(256) DEFAULT ''                                 NULL,
    flow_info        clob          DEFAULT NULL                               NULL,
    wf_context       clob          DEFAULT NULL                               NULL,
    notify_ids       varchar2(128) DEFAULT ''                                 NULL,
    bucket_index     number        DEFAULT 0                                  NOT NULL,
    version          number                                                   NOT NULL,
    ext_attrs        varchar2(256) DEFAULT ''                                 NULL,
@@ -807,6 +815,7 @@
COMMENT ON COLUMN sj_workflow.description IS '描述';
COMMENT ON COLUMN sj_workflow.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow.wf_context IS '上下文';
COMMENT ON COLUMN sj_workflow.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_workflow.version IS '版本号';
COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段';
script/sql/oracle/oracle_ry_vue_5.X.sql
@@ -5,7 +5,7 @@
(
    id                 number(20)        not null,
    user_id            number(20)        not null,
    tenant_id          varchar2(20)      default null,
    tenant_id          varchar2(20)      default '000000',
    auth_id            varchar2(255)     not null,
    source             varchar2(255)     not null,
    open_id            varchar2(255)     default null,
@@ -65,7 +65,7 @@
comment on column  sys_social.create_time       is '创建时间';
comment on column  sys_social.update_by         is '更新者';
comment on column  sys_social.update_time       is '更新时间';
comment on column  sys_social.del_flag          is '删除标志(0代表存在 2代表删除)';
comment on column  sys_social.del_flag          is '删除标志(0代表存在 1代表删除)';
-- ----------------------------
-- ç§Ÿæˆ·è¡¨
@@ -108,7 +108,7 @@
comment on column  sys_tenant.expire_time        is '过期时间';
comment on column  sys_tenant.account_count      is '用户数量(-1不限制)';
comment on column  sys_tenant.status             is '租户状态(0正常 1停用)';
comment on column  sys_tenant.del_flag           is '删除标志(0代表存在 2代表删除)';
comment on column  sys_tenant.del_flag           is '删除标志(0代表存在 1代表删除)';
comment on column  sys_tenant.create_dept        is '创建部门';
comment on column  sys_tenant.create_by          is '创建者';
comment on column  sys_tenant.create_time        is '创建时间';
@@ -148,7 +148,7 @@
comment on column  sys_tenant_package.menu_ids           is '关联菜单id';
comment on column  sys_tenant_package.remark             is '备注';
comment on column  sys_tenant_package.status             is '状态(0正常 1停用)';
comment on column  sys_tenant_package.del_flag           is '删除标志(0代表存在 2代表删除)';
comment on column  sys_tenant_package.del_flag           is '删除标志(0代表存在 1代表删除)';
comment on column  sys_tenant_package.create_dept        is '创建部门';
comment on column  sys_tenant_package.create_by          is '创建者';
comment on column  sys_tenant_package.create_time        is '创建时间';
@@ -193,7 +193,7 @@
comment on column sys_dept.phone        is '联系电话';
comment on column sys_dept.email        is '邮箱';
comment on column sys_dept.status       is '部门状态(0正常 1停用)';
comment on column sys_dept.del_flag     is '删除标志(0代表存在 2代表删除)';
comment on column sys_dept.del_flag     is '删除标志(0代表存在 1代表删除)';
comment on column sys_dept.create_dept  is '创建部门';
comment on column sys_dept.create_by    is '创建者';
comment on column sys_dept.create_time  is '创建时间';
@@ -258,7 +258,7 @@
comment on column sys_user.avatar       is '头像路径';
comment on column sys_user.password     is '密码';
comment on column sys_user.status       is '帐号状态(0正常 1停用)';
comment on column sys_user.del_flag     is '删除标志(0代表存在 2代表删除)';
comment on column sys_user.del_flag     is '删除标志(0代表存在 1代表删除)';
comment on column sys_user.login_ip     is '最后登录IP';
comment on column sys_user.login_date   is '最后登录时间';
comment on column sys_user.create_dept  is '创建部门';
@@ -356,7 +356,7 @@
comment on column sys_role.menu_check_strictly   is '菜单树选择项是否关联显示';
comment on column sys_role.dept_check_strictly   is '部门树选择项是否关联显示';
comment on column sys_role.status                is '角色状态(0正常 1停用)';
comment on column sys_role.del_flag              is '删除标志(0代表存在 2代表删除)';
comment on column sys_role.del_flag              is '删除标志(0代表存在 1代表删除)';
comment on column sys_role.create_dept           is '创建部门';
comment on column sys_role.create_by             is '创建者';
comment on column sys_role.create_time           is '创建时间';
@@ -615,6 +615,8 @@
insert into sys_role_menu values ('3', '106');
insert into sys_role_menu values ('3', '107');
insert into sys_role_menu values ('3', '108');
insert into sys_role_menu values ('3', '118');
insert into sys_role_menu values ('3', '123');
insert into sys_role_menu values ('3', '500');
insert into sys_role_menu values ('3', '501');
insert into sys_role_menu values ('3', '1001');
@@ -662,6 +664,12 @@
insert into sys_role_menu values ('3', '1043');
insert into sys_role_menu values ('3', '1044');
insert into sys_role_menu values ('3', '1045');
insert into sys_role_menu values ('3', '1050');
insert into sys_role_menu values ('3', '1061');
insert into sys_role_menu values ('3', '1062');
insert into sys_role_menu values ('3', '1063');
insert into sys_role_menu values ('3', '1064');
insert into sys_role_menu values ('3', '1065');
insert into sys_role_menu values ('3', '1500');
insert into sys_role_menu values ('3', '1501');
insert into sys_role_menu values ('3', '1502');
@@ -674,6 +682,25 @@
insert into sys_role_menu values ('3', '1509');
insert into sys_role_menu values ('3', '1510');
insert into sys_role_menu values ('3', '1511');
insert into sys_role_menu values ('3', '1600');
insert into sys_role_menu values ('3', '1601');
insert into sys_role_menu values ('3', '1602');
insert into sys_role_menu values ('3', '1603');
insert into sys_role_menu values ('3', '1620');
insert into sys_role_menu values ('3', '1621');
insert into sys_role_menu values ('3', '1622');
insert into sys_role_menu values ('3', '1623');
insert into sys_role_menu values ('3', '11618');
insert into sys_role_menu values ('3', '11619');
insert into sys_role_menu values ('3', '11629');
insert into sys_role_menu values ('3', '11632');
insert into sys_role_menu values ('3', '11633');
insert into sys_role_menu values ('3', '11638');
insert into sys_role_menu values ('3', '11639');
insert into sys_role_menu values ('3', '11640');
insert into sys_role_menu values ('3', '11641');
insert into sys_role_menu values ('3', '11642');
insert into sys_role_menu values ('3', '11643');
insert into sys_role_menu values ('4', '5');
insert into sys_role_menu values ('4', '1500');
insert into sys_role_menu values ('4', '1501');
@@ -738,10 +765,10 @@
  oper_url          varchar2(255)   default '',
  oper_ip           varchar2(128)   default '',
  oper_location     varchar2(255)   default '',
  oper_param        varchar2(2100)  default '',
  json_result       varchar2(2100)  default '',
  oper_param        varchar2(4000)  default '',
  json_result       varchar2(4000)  default '',
  status            number(1)       default 0,
  error_msg         varchar2(2100)  default '',
  error_msg         varchar2(4000)  default '',
  oper_time         date,
  cost_time         number(20)      default 0
);
@@ -1245,7 +1272,7 @@
comment on column sys_client.active_timeout         is 'token活跃超时时间';
comment on column sys_client.timeout                is 'token固定超时';
comment on column sys_client.status                 is '状态(0正常 1停用)';
comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
comment on column sys_client.del_flag               is '删除标志(0代表存在 1代表删除)';
comment on column sys_client.create_dept            is '创建部门';
comment on column sys_client.create_by              is '创建者';
comment on column sys_client.create_time            is '创建时间';
script/sql/oracle/oracle_ry_workflow.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,414 @@
create table FLOW_DEFINITION
(
    ID              NUMBER(20)            not null,
    FLOW_CODE       VARCHAR2(40)          not null,
    FLOW_NAME       VARCHAR2(100)         not null,
    CATEGORY        VARCHAR2(100),
    VERSION         VARCHAR2(20)          not null,
    IS_PUBLISH      NUMBER(1)   default 0 not null,
    FORM_CUSTOM     VARCHAR2(1) default 'N',
    FORM_PATH       VARCHAR2(100),
    ACTIVITY_STATUS NUMBER(1)   default 1,
    LISTENER_TYPE   VARCHAR2(100),
    LISTENER_PATH   VARCHAR2(500),
    EXT             VARCHAR2(500),
    CREATE_TIME     DATE,
    UPDATE_TIME     DATE,
    DEL_FLAG        VARCHAR2(1) default '0',
    TENANT_ID       VARCHAR2(40)
);
alter table FLOW_DEFINITION add constraint PK_FLOW_DEFINITION primary key (ID);
comment on table FLOW_DEFINITION is '流程定义表';
comment on column FLOW_DEFINITION.ID is '主键id';
comment on column FLOW_DEFINITION.FLOW_CODE is '流程编码';
comment on column FLOW_DEFINITION.FLOW_NAME is '流程名称';
comment on column FLOW_DEFINITION.CATEGORY is '流程类别';
comment on column FLOW_DEFINITION.VERSION is '流程版本';
comment on column FLOW_DEFINITION.IS_PUBLISH is '是否发布 (0未发布 1已发布 9失效)';
comment on column FLOW_DEFINITION.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
comment on column FLOW_DEFINITION.FORM_PATH is '审批表单路径';
comment on column FLOW_DEFINITION.ACTIVITY_STATUS is '流程激活状态(0挂起 1激活)';
comment on column FLOW_DEFINITION.LISTENER_TYPE is '监听器类型';
comment on column FLOW_DEFINITION.LISTENER_PATH is '监听器路径';
comment on column FLOW_DEFINITION.EXT is '扩展字段,预留给业务系统使用';
comment on column FLOW_DEFINITION.CREATE_TIME is '创建时间';
comment on column FLOW_DEFINITION.UPDATE_TIME is '更新时间';
comment on column FLOW_DEFINITION.DEL_FLAG is '删除标志';
comment on column FLOW_DEFINITION.TENANT_ID is '租户id';
create table FLOW_NODE
(
    ID              NUMBER(20)    not null,
    NODE_TYPE       NUMBER(1)     not null,
    DEFINITION_ID   NUMBER(20)    not null,
    NODE_CODE       VARCHAR2(100) not null,
    NODE_NAME       VARCHAR2(100),
    NODE_RATIO      NUMBER(6, 3),
    COORDINATE      VARCHAR2(100),
    SKIP_ANY_NODE   VARCHAR2(100) default 'N',
    ANY_NODE_SKIP   VARCHAR2(100),
    LISTENER_TYPE   VARCHAR2(100),
    LISTENER_PATH   VARCHAR2(500),
    HANDLER_TYPE    VARCHAR2(100),
    HANDLER_PATH    VARCHAR2(400),
    FORM_CUSTOM     VARCHAR2(1)   default 'N',
    FORM_PATH       VARCHAR2(100),
    VERSION         VARCHAR2(20),
    CREATE_TIME     DATE,
    UPDATE_TIME     DATE,
    DEL_FLAG        VARCHAR2(1)   default '0',
    TENANT_ID       VARCHAR2(40),
    PERMISSION_FLAG VARCHAR2(200)
);
alter table FLOW_NODE add constraint PK_FLOW_NODE primary key (ID);
comment on table FLOW_NODE is '流程节点表';
comment on column FLOW_NODE.ID is '主键id';
comment on column FLOW_NODE.NODE_TYPE is '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_NODE.DEFINITION_ID is '对应flow_definition表的id';
comment on column FLOW_NODE.NODE_CODE is '流程节点编码';
comment on column FLOW_NODE.NODE_NAME is '流程节点名称';
comment on column FLOW_NODE.NODE_RATIO is '流程签署比例值';
comment on column FLOW_NODE.COORDINATE is '坐标';
comment on column FLOW_NODE.SKIP_ANY_NODE is '是否可以退回任意节点(Y是 N否)即将删除';
comment on column FLOW_NODE.ANY_NODE_SKIP is '任意结点跳转';
comment on column FLOW_NODE.LISTENER_TYPE is '监听器类型';
comment on column FLOW_NODE.LISTENER_PATH is '监听器路径';
comment on column FLOW_NODE.HANDLER_TYPE is '处理器类型';
comment on column FLOW_NODE.HANDLER_PATH is '处理器路径';
comment on column FLOW_NODE.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
comment on column FLOW_NODE.FORM_PATH is '审批表单路径';
comment on column FLOW_NODE.VERSION is '版本';
comment on column FLOW_NODE.CREATE_TIME is '创建时间';
comment on column FLOW_NODE.UPDATE_TIME is '更新时间';
comment on column FLOW_NODE.DEL_FLAG is '删除标志';
comment on column FLOW_NODE.TENANT_ID is '租户id';
comment on column FLOW_NODE.PERMISSION_FLAG is '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';
create table FLOW_SKIP
(
    ID             NUMBER(20)    not null,
    DEFINITION_ID  NUMBER(20)    not null,
    NOW_NODE_CODE  VARCHAR2(100) not null,
    NOW_NODE_TYPE  NUMBER(1),
    NEXT_NODE_CODE VARCHAR2(100) not null,
    NEXT_NODE_TYPE NUMBER(1),
    SKIP_NAME      VARCHAR2(100),
    SKIP_TYPE      VARCHAR2(40),
    SKIP_CONDITION VARCHAR2(200),
    COORDINATE     VARCHAR2(100),
    CREATE_TIME    DATE,
    UPDATE_TIME    DATE,
    DEL_FLAG       VARCHAR2(1) default '0',
    TENANT_ID      VARCHAR2(40)
);
alter table FLOW_SKIP add constraint PK_FLOW_SKIP primary key (ID);
comment on table FLOW_SKIP is '节点跳转关联表';
comment on column FLOW_SKIP.ID is '主键id';
comment on column FLOW_SKIP.DEFINITION_ID is '流程定义id';
comment on column FLOW_SKIP.NOW_NODE_CODE is '当前流程节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_SKIP.NOW_NODE_TYPE is '下一个流程节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_SKIP.NEXT_NODE_CODE is '下一个流程节点编码';
comment on column FLOW_SKIP.NEXT_NODE_TYPE is '下一个流程节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_SKIP.SKIP_NAME is '跳转名称';
comment on column FLOW_SKIP.SKIP_TYPE is '跳转类型 (PASS审批通过 REJECT退回)';
comment on column FLOW_SKIP.SKIP_CONDITION is '跳转条件';
comment on column FLOW_SKIP.COORDINATE is '坐标';
comment on column FLOW_SKIP.CREATE_TIME is '创建时间';
comment on column FLOW_SKIP.UPDATE_TIME is '更新时间';
comment on column FLOW_SKIP.DEL_FLAG is '删除标志';
comment on column FLOW_SKIP.TENANT_ID is '租户id';
create table FLOW_INSTANCE
(
    ID              NUMBER       not null,
    DEFINITION_ID   NUMBER       not null,
    BUSINESS_ID     VARCHAR2(40) not null,
    NODE_TYPE       NUMBER(1),
    NODE_CODE       VARCHAR2(100),
    NODE_NAME       VARCHAR2(100),
    VARIABLE        CLOB,
    FLOW_STATUS     VARCHAR2(20),
    ACTIVITY_STATUS NUMBER(1)    default 1,
    DEF_JSON        CLOB,
    CREATE_BY       VARCHAR2(64) default '',
    CREATE_TIME     DATE,
    UPDATE_TIME     DATE,
    EXT             VARCHAR2(500),
    DEL_FLAG        VARCHAR2(1)  default '0',
    TENANT_ID       VARCHAR2(40)
);
alter table FLOW_INSTANCE add constraint PK_FLOW_INSTANCE primary key (ID);
comment on table FLOW_INSTANCE is '流程实例表';
comment on column FLOW_INSTANCE.ID is '主键id';
comment on column FLOW_INSTANCE.DEFINITION_ID is '对应flow_definition表的id';
comment on column FLOW_INSTANCE.BUSINESS_ID is '业务id';
comment on column FLOW_INSTANCE.NODE_TYPE is '开始节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_INSTANCE.NODE_CODE is '开始节点编码';
comment on column FLOW_INSTANCE.NODE_NAME is '开始节点名称';
comment on column FLOW_INSTANCE.VARIABLE is '任务变量';
comment on column FLOW_INSTANCE.FLOW_STATUS is '流程状态(0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 3自动通过 4终止 5作废 6撤销 7取回  8已完成 9已退回 10失效)';
comment on column FLOW_INSTANCE.ACTIVITY_STATUS is '流程激活状态(0挂起 1激活)';
comment on column FLOW_INSTANCE.DEF_JSON is '流程定义json';
comment on column FLOW_INSTANCE.CREATE_BY is '创建者';
comment on column FLOW_INSTANCE.CREATE_TIME is '创建时间';
comment on column FLOW_INSTANCE.UPDATE_TIME is '更新时间';
comment on column FLOW_INSTANCE.EXT is '扩展字段,预留给业务系统使用';
comment on column FLOW_INSTANCE.DEL_FLAG is '删除标志';
comment on column FLOW_INSTANCE.TENANT_ID is '租户id';
create table FLOW_TASK
(
    ID            NUMBER(20) not null,
    DEFINITION_ID NUMBER(20) not null,
    INSTANCE_ID   NUMBER(20) not null,
    NODE_CODE     VARCHAR2(100),
    NODE_NAME     VARCHAR2(100),
    NODE_TYPE     NUMBER(1),
    FORM_CUSTOM   VARCHAR2(1) default 'N',
    FORM_PATH     VARCHAR2(100),
    CREATE_TIME   DATE,
    UPDATE_TIME   DATE,
    DEL_FLAG      VARCHAR2(1) default '0',
    TENANT_ID     VARCHAR2(40)
);
alter table FLOW_TASK add constraint PK_FLOW_TASK primary key (ID);
comment on table FLOW_TASK is '待办任务表';
comment on column FLOW_TASK.ID is '主键id';
comment on column FLOW_TASK.DEFINITION_ID is '对应flow_definition表的id';
comment on column FLOW_TASK.INSTANCE_ID is '对应flow_instance表的id';
comment on column FLOW_TASK.NODE_CODE is '节点编码';
comment on column FLOW_TASK.NODE_NAME is '节点名称';
comment on column FLOW_TASK.NODE_TYPE is '节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_TASK.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
comment on column FLOW_TASK.FORM_PATH is '审批表单路径';
comment on column FLOW_TASK.CREATE_TIME is '创建时间';
comment on column FLOW_TASK.UPDATE_TIME is '更新时间';
comment on column FLOW_TASK.DEL_FLAG is '删除标志';
comment on column FLOW_TASK.TENANT_ID is '租户id';
create table FLOW_HIS_TASK
(
    ID               NUMBER(20) not null,
    DEFINITION_ID    NUMBER(20) not null,
    INSTANCE_ID      NUMBER(20) not null,
    TASK_ID          NUMBER(20) not null,
    NODE_CODE        VARCHAR2(100),
    NODE_NAME        VARCHAR2(100),
    NODE_TYPE        NUMBER(1),
    TARGET_NODE_CODE VARCHAR2(200),
    TARGET_NODE_NAME VARCHAR2(200),
    APPROVER         VARCHAR2(40),
    COOPERATE_TYPE   NUMBER(1)   default 0,
    COLLABORATOR     VARCHAR2(40),
    SKIP_TYPE        VARCHAR2(10),
    FLOW_STATUS      VARCHAR2(20),
    FORM_CUSTOM      VARCHAR2(1) default 'N',
    FORM_PATH        VARCHAR2(100),
    MESSAGE          VARCHAR2(500),
    VARIABLE         CLOB,
    EXT              VARCHAR2(500),
    CREATE_TIME      DATE,
    UPDATE_TIME      DATE,
    DEL_FLAG         VARCHAR2(1) default '0',
    TENANT_ID        VARCHAR2(40)
);
alter table FLOW_HIS_TASK add constraint PK_FLOW_HIS_TASK primary key (ID);
comment on table FLOW_HIS_TASK is '历史任务记录表';
comment on column FLOW_HIS_TASK.ID is '主键id';
comment on column FLOW_HIS_TASK.DEFINITION_ID is '对应flow_definition表的id';
comment on column FLOW_HIS_TASK.INSTANCE_ID is '对应flow_instance表的id';
comment on column FLOW_HIS_TASK.TASK_ID is '对应flow_task表的id';
comment on column FLOW_HIS_TASK.NODE_CODE is '开始节点编码';
comment on column FLOW_HIS_TASK.NODE_NAME is '开始节点名称';
comment on column FLOW_HIS_TASK.NODE_TYPE is '开始节点类型 (0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
comment on column FLOW_HIS_TASK.TARGET_NODE_CODE is '目标节点编码';
comment on column FLOW_HIS_TASK.TARGET_NODE_NAME is '目标节点名称';
comment on column FLOW_HIS_TASK.SKIP_TYPE is '流转类型(PASS通过 REJECT退回 NONE无动作)';
comment on column FLOW_HIS_TASK.FLOW_STATUS is '流程状态(1审批中 2 å®¡æ‰¹é€šè¿‡ 9已退回 10失效)';
comment on column FLOW_HIS_TASK.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
comment on column FLOW_HIS_TASK.FORM_PATH is '审批表单路径';
comment on column FLOW_HIS_TASK.MESSAGE is '审批意见';
comment on column FLOW_HIS_TASK.VARIABLE is '任务变量';
comment on column FLOW_HIS_TASK.EXT is '扩展字段,预留给业务系统使用';
comment on column FLOW_HIS_TASK.CREATE_TIME is '任务开始时间';
comment on column FLOW_HIS_TASK.UPDATE_TIME is '审批完成时间';
comment on column FLOW_HIS_TASK.DEL_FLAG is '删除标志';
comment on column FLOW_HIS_TASK.TENANT_ID is '租户id';
comment on column FLOW_HIS_TASK.APPROVER is '审批者';
comment on column FLOW_HIS_TASK.COOPERATE_TYPE is '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)';
comment on column FLOW_HIS_TASK.COLLABORATOR is '协作人';
create table FLOW_USER
(
    ID           NUMBER(20)  not null,
    TYPE         VARCHAR2(1) not null,
    PROCESSED_BY VARCHAR2(80),
    ASSOCIATED   NUMBER(20)  not null,
    CREATE_TIME  DATE,
    CREATE_BY    VARCHAR2(80),
    UPDATE_TIME  DATE,
    DEL_FLAG     VARCHAR2(1) default '0',
    TENANT_ID    VARCHAR2(40)
);
alter table FLOW_USER add constraint PK_FLOW_USER primary key (ID);
comment on table FLOW_USER is '待办任务表';
comment on column FLOW_USER.ID is '主键id';
comment on column FLOW_USER.TYPE is '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)';
comment on column FLOW_USER.PROCESSED_BY is '权限人)';
comment on column FLOW_USER.ASSOCIATED is '任务表id';
comment on column FLOW_USER.CREATE_TIME is '创建时间';
comment on column FLOW_USER.CREATE_BY is '节点名称';
comment on column FLOW_USER.UPDATE_TIME is '更新时间';
comment on column FLOW_USER.DEL_FLAG is '删除标志';
comment on column FLOW_USER.TENANT_ID is '租户id';
create index USER_PROCESSED_TYPE on FLOW_USER (PROCESSED_BY, TYPE);
-- ----------------------------
-- æµç¨‹åˆ†ç±»è¡¨
-- ----------------------------
CREATE TABLE flow_category
(
    category_id NUMBER (20) NOT NULL,
    tenant_id VARCHAR2 (20) DEFAULT '000000',
    parent_id NUMBER (20) DEFAULT 0,
    ancestors VARCHAR2 (500) DEFAULT '',
    category_name VARCHAR2 (30) NOT NULL,
    order_num NUMBER (4) DEFAULT 0,
    del_flag    CHAR(1) DEFAULT '0',
    create_dept NUMBER (20),
    create_by NUMBER (20),
    create_time DATE,
    update_by NUMBER (20),
    update_time DATE
);
alter table flow_category add constraint pk_flow_category primary key (category_id);
COMMENT ON TABLE flow_category IS '流程分类';
COMMENT ON COLUMN flow_category.category_id IS '流程分类ID';
COMMENT ON COLUMN flow_category.tenant_id IS '租户编号';
COMMENT ON COLUMN flow_category.parent_id IS '父流程分类id';
COMMENT ON COLUMN flow_category.ancestors IS '祖级列表';
COMMENT ON COLUMN flow_category.category_name IS '流程分类名称';
COMMENT ON COLUMN flow_category.order_num IS '显示顺序';
COMMENT ON COLUMN flow_category.del_flag IS '删除标志(0代表存在 1代表删除)';
COMMENT ON COLUMN flow_category.create_dept IS '创建部门';
COMMENT ON COLUMN flow_category.create_by IS '创建者';
COMMENT ON COLUMN flow_category.create_time IS '创建时间';
COMMENT ON COLUMN flow_category.update_by IS '更新者';
COMMENT ON COLUMN flow_category.update_time IS '更新时间';
INSERT INTO flow_category VALUES (100, '000000', 0, '0', 'OA审批', 0, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (101, '000000', 100, '0,100', '假勤管理', 0, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (102, '000000', 100, '0,100', '人事管理', 1, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (103, '000000', 101, '0,100,101', '请假', 0, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (104, '000000', 101, '0,100,101', '出差', 1, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (105, '000000', 101, '0,100,101', '加班', 2, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (106, '000000', 101, '0,100,101', '换班', 3, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '外出', 4, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, SYSDATE, NULL, NULL);
INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, SYSDATE, NULL, NULL);
-- ----------------------------
-- è¯·å‡å•信息
-- ----------------------------
CREATE TABLE test_leave
(
    id NUMBER (20) NOT NULL,
    tenant_id VARCHAR2 (20) DEFAULT '000000',
    leave_type VARCHAR2 (255) NOT NULL,
    start_date  DATE NOT NULL,
    end_date    DATE NOT NULL,
    leave_days NUMBER (10) NOT NULL,
    remark VARCHAR2 (255),
    status VARCHAR2 (255),
    create_dept NUMBER (20),
    create_by NUMBER (20),
    create_time DATE,
    update_by NUMBER (20),
    update_time DATE
);
alter table test_leave add constraint pk_test_leave primary key (id);
COMMENT ON TABLE test_leave IS '请假申请表';
COMMENT ON COLUMN test_leave.id IS 'ID';
COMMENT ON COLUMN test_leave.tenant_id IS '租户编号';
COMMENT ON COLUMN test_leave.leave_type IS '请假类型';
COMMENT ON COLUMN test_leave.start_date IS '开始时间';
COMMENT ON COLUMN test_leave.end_date IS '结束时间';
COMMENT ON COLUMN test_leave.leave_days IS '请假天数';
COMMENT ON COLUMN test_leave.remark IS '请假原因';
COMMENT ON COLUMN test_leave.status IS '状态';
COMMENT ON COLUMN test_leave.create_dept IS '创建部门';
COMMENT ON COLUMN test_leave.create_by IS '创建者';
COMMENT ON COLUMN test_leave.create_time IS '创建时间';
COMMENT ON COLUMN test_leave.update_by IS '更新者';
COMMENT ON COLUMN test_leave.update_time IS '更新时间';
INSERT INTO sys_menu VALUES ('11616', '工作流', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11623', '流程分类查询', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11624', '流程分类新增', '11622', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, SYSDATE, NULL, NULL, '请假申请菜单');
INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11640', '请假申请新增', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11641', '请假申请修改', '11638', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11642', '请假申请删除', '11638', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11643', '请假申请导出', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_dict_type VALUES (13, '000000', '业务状态', 'wf_business_status', 103, 1, SYSDATE, NULL, NULL, '业务状态列表');
INSERT INTO sys_dict_type VALUES (14, '000000', '表单类型', 'wf_form_type', 103, 1, SYSDATE, NULL, NULL, '表单类型列表');
INSERT INTO sys_dict_type VALUES (15, '000000', '任务状态', 'wf_task_status', 103, 1, SYSDATE, NULL, NULL, '任务状态');
INSERT INTO sys_dict_data VALUES (39, '000000', 1, '已撤销', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '已撤销');
INSERT INTO sys_dict_data VALUES (40, '000000', 2, '草稿', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, SYSDATE, NULL, NULL, '草稿');
INSERT INTO sys_dict_data VALUES (41, '000000', 3, '待审核', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '待审核');
INSERT INTO sys_dict_data VALUES (42, '000000', 4, '已完成', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '已完成');
INSERT INTO sys_dict_data VALUES (43, '000000', 5, '已作废', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '已作废');
INSERT INTO sys_dict_data VALUES (44, '000000', 6, '已退回', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '已退回');
INSERT INTO sys_dict_data VALUES (45, '000000', 7, '已终止', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '已终止');
INSERT INTO sys_dict_data VALUES (46, '000000', 1, '自定义表单', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '自定义表单');
INSERT INTO sys_dict_data VALUES (47, '000000', 2, '动态表单', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '动态表单');
INSERT INTO sys_dict_data VALUES (48, '000000', 1, '撤销', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '撤销');
INSERT INTO sys_dict_data VALUES (49, '000000', 2, '通过', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '通过');
INSERT INTO sys_dict_data VALUES (50, '000000', 3, '待审核', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '待审核');
INSERT INTO sys_dict_data VALUES (51, '000000', 4, '作废', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '作废');
INSERT INTO sys_dict_data VALUES (52, '000000', 5, '退回', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '退回');
INSERT INTO sys_dict_data VALUES (53, '000000', 6, '终止', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '终止');
INSERT INTO sys_dict_data VALUES (54, '000000', 7, '转办', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '转办');
INSERT INTO sys_dict_data VALUES (55, '000000', 8, '委托', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '委托');
INSERT INTO sys_dict_data VALUES (56, '000000', 9, '抄送', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '抄送');
INSERT INTO sys_dict_data VALUES (57, '000000', 10, '加签', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '加签');
INSERT INTO sys_dict_data VALUES (58, '000000', 11, '减签', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '减签');
INSERT INTO sys_dict_data VALUES (59, '000000', 11, '超时', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '超时');
script/sql/postgres/flowable.sql
ÎļþÒÑɾ³ý
script/sql/postgres/postgres_ry_job.sql
ÎļþÃû´Ó script/sql/postgres/snail_job_postgre.sql ÐÞ¸Ä
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : PostgreSQL
 Date: 2024-07-06 11:45:40
 Date: 2024-12-27 22:13:49
*/
@@ -68,6 +68,7 @@
COMMENT ON TABLE sj_group_config IS '组配置';
INSERT INTO sj_group_config VALUES (1, 'dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, now(), now());
INSERT INTO sj_group_config VALUES (2, 'prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, now(), now());
-- sj_notify_config
CREATE TABLE sj_notify_config
@@ -75,7 +76,7 @@
    id                     bigserial PRIMARY KEY,
    namespace_id           varchar(64)  NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
    group_name             varchar(64)  NOT NULL,
    business_id            varchar(64)  NOT NULL,
    notify_name            varchar(64)  NOT NULL DEFAULT '',
    system_task_type       smallint     NOT NULL DEFAULT 3,
    notify_status          smallint     NOT NULL DEFAULT 0,
    recipient_ids          varchar(128) NOT NULL,
@@ -88,12 +89,12 @@
    update_dt              timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name, business_id);
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name);
COMMENT ON COLUMN sj_notify_config.id IS '主键';
COMMENT ON COLUMN sj_notify_config.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_notify_config.group_name IS '组名称';
COMMENT ON COLUMN sj_notify_config.business_id IS '业务id  ( job_id或workflow_id或scene_name ) ';
COMMENT ON COLUMN sj_notify_config.notify_name IS '通知名称';
COMMENT ON COLUMN sj_notify_config.system_task_type IS '任务类型 1. é‡è¯•任务 2. é‡è¯•回调 3、JOB任务 4、WORKFLOW任务';
COMMENT ON COLUMN sj_notify_config.notify_status IS '通知状态 0、未启用 1、启用';
COMMENT ON COLUMN sj_notify_config.recipient_ids IS '接收人id列表';
@@ -296,6 +297,7 @@
    max_retry_count  int          NOT NULL DEFAULT 5,
    back_off         smallint     NOT NULL DEFAULT 1,
    trigger_interval varchar(16)  NOT NULL DEFAULT '',
    notify_ids       varchar(128) NOT NULL DEFAULT '',
    deadline_request bigint       NOT NULL DEFAULT 60000,
    executor_timeout int          NOT NULL DEFAULT 5,
    route_key        smallint     NOT NULL DEFAULT 4,
@@ -314,6 +316,7 @@
COMMENT ON COLUMN sj_retry_scene_config.max_retry_count IS '最大重试次数';
COMMENT ON COLUMN sj_retry_scene_config.back_off IS '1、默认等级 2、固定间隔时间 3、CRON è¡¨è¾¾å¼';
COMMENT ON COLUMN sj_retry_scene_config.trigger_interval IS '间隔时长';
COMMENT ON COLUMN sj_retry_scene_config.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_retry_scene_config.deadline_request IS 'Deadline Request è°ƒç”¨é“¾è¶…æ—¶ å•位毫秒';
COMMENT ON COLUMN sj_retry_scene_config.executor_timeout IS '任务执行超时时间,单位秒';
COMMENT ON COLUMN sj_retry_scene_config.route_key IS '路由策略';
@@ -463,6 +466,8 @@
    retry_interval   int          NOT NULL DEFAULT 0,
    bucket_index     int          NOT NULL DEFAULT 0,
    resident         smallint     NOT NULL DEFAULT 0,
    notify_ids       varchar(128) NOT NULL DEFAULT '',
    owner_id         bigint       NULL,
    description      varchar(256) NOT NULL DEFAULT '',
    ext_attrs        varchar(256) NULL     DEFAULT '',
    deleted          smallint     NOT NULL DEFAULT 0,
@@ -495,6 +500,8 @@
COMMENT ON COLUMN sj_job.retry_interval IS '重试间隔 ( s ) ';
COMMENT ON COLUMN sj_job.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务';
COMMENT ON COLUMN sj_job.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_job.owner_id IS '负责人id';
COMMENT ON COLUMN sj_job.description IS '描述';
COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除';
@@ -502,7 +509,7 @@
COMMENT ON COLUMN sj_job.update_dt IS '修改时间';
COMMENT ON TABLE sj_job IS '任务信息';
INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', '', 0, now(), now());
INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', 0, now(), now());
-- sj_job_log_message
CREATE TABLE sj_job_log_message
@@ -720,6 +727,7 @@
    description      varchar(256) NOT NULL DEFAULT '',
    flow_info        text         NULL     DEFAULT NULL,
    wf_context       text         NULL     DEFAULT NULL,
    notify_ids       varchar(128) NOT NULL DEFAULT '',
    bucket_index     int          NOT NULL DEFAULT 0,
    version          int          NOT NULL,
    ext_attrs        varchar(256) NULL     DEFAULT '',
@@ -744,6 +752,7 @@
COMMENT ON COLUMN sj_workflow.description IS '描述';
COMMENT ON COLUMN sj_workflow.flow_info IS '流程信息';
COMMENT ON COLUMN sj_workflow.wf_context IS '上下文';
COMMENT ON COLUMN sj_workflow.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_workflow.version IS '版本号';
COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段';
script/sql/postgres/postgres_ry_vue_5.X.sql
@@ -5,7 +5,7 @@
(
    id                 int8             not null,
    user_id            int8             not null,
    tenant_id          varchar(20)      default null::varchar,
    tenant_id          varchar(20)      default '000000'::varchar,
    auth_id            varchar(255)     not null,
    source             varchar(255)     not null,
    open_id            varchar(255)     default null::varchar,
@@ -64,7 +64,7 @@
comment on column  sys_social.create_time       is '创建时间';
comment on column  sys_social.update_by         is '更新者';
comment on column  sys_social.update_time       is '更新时间';
comment on column  sys_social.del_flag          is '删除标志(0代表存在 2代表删除)';
comment on column  sys_social.del_flag          is '删除标志(0代表存在 1代表删除)';
-- ----------------------------
-- ç§Ÿæˆ·è¡¨
@@ -109,7 +109,7 @@
comment on column  sys_tenant.expire_time        is '过期时间';
comment on column  sys_tenant.account_count      is '用户数量(-1不限制)';
comment on column  sys_tenant.status             is '租户状态(0正常 1停用)';
comment on column  sys_tenant.del_flag           is '删除标志(0代表存在 2代表删除)';
comment on column  sys_tenant.del_flag           is '删除标志(0代表存在 1代表删除)';
comment on column  sys_tenant.create_dept        is '创建部门';
comment on column  sys_tenant.create_by          is '创建者';
comment on column  sys_tenant.create_time        is '创建时间';
@@ -151,7 +151,7 @@
comment on column  sys_tenant_package.menu_ids           is '关联菜单id';
comment on column  sys_tenant_package.remark             is '备注';
comment on column  sys_tenant_package.status             is '状态(0正常 1停用)';
comment on column  sys_tenant_package.del_flag           is '删除标志(0代表存在 2代表删除)';
comment on column  sys_tenant_package.del_flag           is '删除标志(0代表存在 1代表删除)';
comment on column  sys_tenant_package.create_dept        is '创建部门';
comment on column  sys_tenant_package.create_by          is '创建者';
comment on column  sys_tenant_package.create_time        is '创建时间';
@@ -196,7 +196,7 @@
comment on column sys_dept.phone        is '联系电话';
comment on column sys_dept.email        is '邮箱';
comment on column sys_dept.status       is '部门状态(0正常 1停用)';
comment on column sys_dept.del_flag     is '删除标志(0代表存在 2代表删除)';
comment on column sys_dept.del_flag     is '删除标志(0代表存在 1代表删除)';
comment on column sys_dept.create_dept  is '创建部门';
comment on column sys_dept.create_by    is '创建者';
comment on column sys_dept.create_time  is '创建时间';
@@ -259,7 +259,7 @@
comment on column sys_user.avatar       is '头像地址';
comment on column sys_user.password     is '密码';
comment on column sys_user.status       is '帐号状态(0正常 1停用)';
comment on column sys_user.del_flag     is '删除标志(0代表存在 2代表删除)';
comment on column sys_user.del_flag     is '删除标志(0代表存在 1代表删除)';
comment on column sys_user.login_ip     is '最后登陆IP';
comment on column sys_user.login_date   is '最后登陆时间';
comment on column sys_user.create_dept  is '创建部门';
@@ -357,7 +357,7 @@
comment on column sys_role.menu_check_strictly  is '菜单树选择项是否关联显示';
comment on column sys_role.dept_check_strictly  is '部门树选择项是否关联显示';
comment on column sys_role.status               is '角色状态(0正常 1停用)';
comment on column sys_role.del_flag             is '删除标志(0代表存在 2代表删除)';
comment on column sys_role.del_flag             is '删除标志(0代表存在 1代表删除)';
comment on column sys_role.create_dept          is '创建部门';
comment on column sys_role.create_by            is '创建者';
comment on column sys_role.create_time          is '创建时间';
@@ -427,7 +427,7 @@
-- ----------------------------
-- ä¸€çº§èœå•
insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null, '', '1', '0', 'M', '0', '0', '', 'system',   103, 1, now(), null, null, '系统管理目录');
insert into sys_menu values('6', '系统管理', '0', '2', 'tenant',           null, '', '1', '0', 'M', '0', '0', '', 'chart',    103, 1, now(), null, null, '租户管理目录');
insert into sys_menu values('6', '租户管理', '0', '2', 'tenant',           null, '', '1', '0', 'M', '0', '0', '', 'chart',    103, 1, now(), null, null, '租户管理目录');
insert into sys_menu values('2', '系统监控', '0', '3', 'monitor',          null, '', '1', '0', 'M', '0', '0', '', 'monitor',  103, 1, now(), null, null, '系统监控目录');
insert into sys_menu values('3', '系统工具', '0', '4', 'tool',             null, '', '1', '0', 'M', '0', '0', '', 'tool',     103, 1, now(), null, null, '系统工具目录');
insert into sys_menu values('4', 'PLUS官网', '0', '5', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', '0', '0', 'M', '0', '0', '', 'guide',    103, 1, now(), null, null, 'RuoYi-Vue-Plus官网地址');
@@ -617,6 +617,8 @@
insert into sys_role_menu values ('3', '106');
insert into sys_role_menu values ('3', '107');
insert into sys_role_menu values ('3', '108');
insert into sys_role_menu values ('3', '118');
insert into sys_role_menu values ('3', '123');
insert into sys_role_menu values ('3', '500');
insert into sys_role_menu values ('3', '501');
insert into sys_role_menu values ('3', '1001');
@@ -664,6 +666,12 @@
insert into sys_role_menu values ('3', '1043');
insert into sys_role_menu values ('3', '1044');
insert into sys_role_menu values ('3', '1045');
insert into sys_role_menu values ('3', '1050');
insert into sys_role_menu values ('3', '1061');
insert into sys_role_menu values ('3', '1062');
insert into sys_role_menu values ('3', '1063');
insert into sys_role_menu values ('3', '1064');
insert into sys_role_menu values ('3', '1065');
insert into sys_role_menu values ('3', '1500');
insert into sys_role_menu values ('3', '1501');
insert into sys_role_menu values ('3', '1502');
@@ -676,6 +684,25 @@
insert into sys_role_menu values ('3', '1509');
insert into sys_role_menu values ('3', '1510');
insert into sys_role_menu values ('3', '1511');
insert into sys_role_menu values ('3', '1600');
insert into sys_role_menu values ('3', '1601');
insert into sys_role_menu values ('3', '1602');
insert into sys_role_menu values ('3', '1603');
insert into sys_role_menu values ('3', '1620');
insert into sys_role_menu values ('3', '1621');
insert into sys_role_menu values ('3', '1622');
insert into sys_role_menu values ('3', '1623');
insert into sys_role_menu values ('3', '11618');
insert into sys_role_menu values ('3', '11619');
insert into sys_role_menu values ('3', '11629');
insert into sys_role_menu values ('3', '11632');
insert into sys_role_menu values ('3', '11633');
insert into sys_role_menu values ('3', '11638');
insert into sys_role_menu values ('3', '11639');
insert into sys_role_menu values ('3', '11640');
insert into sys_role_menu values ('3', '11641');
insert into sys_role_menu values ('3', '11642');
insert into sys_role_menu values ('3', '11643');
insert into sys_role_menu values ('4', '5');
insert into sys_role_menu values ('4', '1500');
insert into sys_role_menu values ('4', '1501');
@@ -741,10 +768,10 @@
    oper_url       varchar(255)  default ''::varchar,
    oper_ip        varchar(128)  default ''::varchar,
    oper_location  varchar(255)  default ''::varchar,
    oper_param     varchar(2000) default ''::varchar,
    json_result    varchar(2000) default ''::varchar,
    oper_param     varchar(4000) default ''::varchar,
    json_result    varchar(4000) default ''::varchar,
    status         int4          default 0,
    error_msg      varchar(2000) default ''::varchar,
    error_msg      varchar(4000) default ''::varchar,
    oper_time      timestamp,
    cost_time      int8          default 0,
    constraint sys_oper_log_pk primary key (oper_id)
@@ -1209,7 +1236,7 @@
insert into sys_oss_config values (1, '000000', 'minio',  'ruoyi',            'ruoyi123',        'ruoyi',             '', '127.0.0.1:9000',                      '','N', '',            '1', '0', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (2, '000000', 'qiniu',  'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi',             '', 's3-cn-north-1.qiniucs.com',           '','N', '',            '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi',             '', 'oss-cn-beijing.aliyuncs.com',         '','N', '',            '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1250000000',  '', 'cos.ap-beijing.myqcloud.com',         '','N', 'ap-beijing',  '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1240000000',  '', 'cos.ap-beijing.myqcloud.com',         '','N', 'ap-beijing',  '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (5, '000000', 'image',  'ruoyi',            'ruoyi123',        'ruoyi',             'image', '127.0.0.1:9000',                 '','N', '',            '1', '1', '', 103, 1, now(), 1, now(), NULL);
-- ----------------------------
@@ -1244,7 +1271,7 @@
comment on column sys_client.active_timeout         is 'token活跃超时时间';
comment on column sys_client.timeout                is 'token固定超时';
comment on column sys_client.status                 is '状态(0正常 1停用)';
comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
comment on column sys_client.del_flag               is '删除标志(0代表存在 1代表删除)';
comment on column sys_client.create_dept            is '创建部门';
comment on column sys_client.create_by              is '创建者';
comment on column sys_client.create_time            is '创建时间';
script/sql/postgres/postgres_ry_workflow.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,405 @@
-- ----------------------------
-- 0、warm-flow-all.sql,地址:https://gitee.com/dromara/warm-flow/blob/master/sql/postgresql/postgresql-warm-flow-all.sql
-- ----------------------------
CREATE TABLE flow_definition
(
    id              int8         NOT NULL,                                 -- ä¸»é”®id
    flow_code       varchar(40)  NOT NULL,                                 -- æµç¨‹ç¼–码
    flow_name       varchar(100) NOT NULL,                                 -- æµç¨‹åç§°
    category        varchar(100) NULL,                                     -- æµç¨‹ç±»åˆ«
    "version"       varchar(20)  NOT NULL,                                 -- æµç¨‹ç‰ˆæœ¬
    is_publish      int2         NOT NULL DEFAULT 0,                       -- æ˜¯å¦å‘布(0未发布 1已发布 9失效)
    form_custom     bpchar(1)    NULL     DEFAULT 'N':: character varying, -- å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
    form_path       varchar(100) NULL,                                     -- å®¡æ‰¹è¡¨å•路径
    activity_status int2         NOT NULL DEFAULT 1,                       -- æµç¨‹æ¿€æ´»çŠ¶æ€ï¼ˆ0挂起 1激活)
    listener_type   varchar(100) NULL,                                     -- ç›‘听器类型
    listener_path   varchar(400) NULL,                                     -- ç›‘听器路径
    ext             varchar(500) NULL,                                     -- æ‰©å±•字段,预留给业务系统使用
    create_time     timestamp    NULL,                                     -- åˆ›å»ºæ—¶é—´
    update_time     timestamp    NULL,                                     -- æ›´æ–°æ—¶é—´
    del_flag        bpchar(1)    NULL     DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id       varchar(40)  NULL,                                     -- ç§Ÿæˆ·id
    CONSTRAINT flow_definition_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_definition IS '流程定义表';
COMMENT ON COLUMN flow_definition.id IS '主键id';
COMMENT ON COLUMN flow_definition.flow_code IS '流程编码';
COMMENT ON COLUMN flow_definition.flow_name IS '流程名称';
COMMENT ON COLUMN flow_definition.category IS '流程类别';
COMMENT ON COLUMN flow_definition."version" IS '流程版本';
COMMENT ON COLUMN flow_definition.is_publish IS '是否发布(0未发布 1已发布 9失效)';
COMMENT ON COLUMN flow_definition.form_custom IS '审批表单是否自定义(Y是 N否)';
COMMENT ON COLUMN flow_definition.form_path IS '审批表单路径';
COMMENT ON COLUMN flow_definition.activity_status IS '流程激活状态(0挂起 1激活)';
COMMENT ON COLUMN flow_definition.listener_type IS '监听器类型';
COMMENT ON COLUMN flow_definition.listener_path IS '监听器路径';
COMMENT ON COLUMN flow_definition.ext IS '扩展字段,预留给业务系统使用';
COMMENT ON COLUMN flow_definition.create_time IS '创建时间';
COMMENT ON COLUMN flow_definition.update_time IS '更新时间';
COMMENT ON COLUMN flow_definition.del_flag IS '删除标志';
COMMENT ON COLUMN flow_definition.tenant_id IS '租户id';
CREATE TABLE flow_node
(
    id              int8          NOT NULL,                             -- ä¸»é”®id
    node_type       int2          NOT NULL,                             -- èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    definition_id   int8          NOT NULL,                             -- æµç¨‹å®šä¹‰id
    node_code       varchar(100)  NOT NULL,                             -- æµç¨‹èŠ‚ç‚¹ç¼–ç 
    node_name       varchar(100)  NULL,                                 -- æµç¨‹èŠ‚ç‚¹åç§°
    permission_flag varchar(200)  NULL,                                 -- æƒé™æ ‡è¯†ï¼ˆæƒé™ç±»åž‹:权限标识,可以多个,用逗号隔开)
    node_ratio      numeric(6, 3) NULL,                                 -- æµç¨‹ç­¾ç½²æ¯”例值
    coordinate      varchar(100)  NULL,                                 -- åæ ‡
    skip_any_node   varchar(100)  NULL DEFAULT 'N':: character varying, -- æ˜¯å¦å¯ä»¥é€€å›žä»»æ„èŠ‚ç‚¹ï¼ˆY是 N否)即将删除
    any_node_skip   varchar(100)  NULL,                                 -- ä»»æ„ç»“点跳转
    listener_type   varchar(100)  NULL,                                 -- ç›‘听器类型
    listener_path   varchar(400)  NULL,                                 -- ç›‘听器路径
    handler_type    varchar(100)  NULL,                                 -- å¤„理器类型
    handler_path    varchar(400)  NULL,                                 -- å¤„理器路径
    form_custom     bpchar(1)     NULL DEFAULT 'N':: character varying, -- å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
    form_path       varchar(100)  NULL,                                 -- å®¡æ‰¹è¡¨å•路径
    "version"       varchar(20)   NOT NULL,                             -- ç‰ˆæœ¬
    create_time     timestamp     NULL,                                 -- åˆ›å»ºæ—¶é—´
    update_time     timestamp     NULL,                                 -- æ›´æ–°æ—¶é—´
    del_flag        bpchar(1)     NULL DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id       varchar(40)   NULL,                                 -- ç§Ÿæˆ·id
    CONSTRAINT flow_node_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_node IS '流程节点表';
COMMENT ON COLUMN flow_node.id IS '主键id';
COMMENT ON COLUMN flow_node.node_type IS '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_node.definition_id IS '流程定义id';
COMMENT ON COLUMN flow_node.node_code IS '流程节点编码';
COMMENT ON COLUMN flow_node.node_name IS '流程节点名称';
COMMENT ON COLUMN flow_node.permission_flag IS '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';
COMMENT ON COLUMN flow_node.node_ratio IS '流程签署比例值';
COMMENT ON COLUMN flow_node.coordinate IS '坐标';
COMMENT ON COLUMN flow_node.skip_any_node IS '是否可以退回任意节点(Y是 N否)即将删除';
COMMENT ON COLUMN flow_node.any_node_skip IS '任意结点跳转';
COMMENT ON COLUMN flow_node.listener_type IS '监听器类型';
COMMENT ON COLUMN flow_node.listener_path IS '监听器路径';
COMMENT ON COLUMN flow_node.handler_type IS '处理器类型';
COMMENT ON COLUMN flow_node.handler_path IS '处理器路径';
COMMENT ON COLUMN flow_node.form_custom IS '审批表单是否自定义(Y是 N否)';
COMMENT ON COLUMN flow_node.form_path IS '审批表单路径';
COMMENT ON COLUMN flow_node."version" IS '版本';
COMMENT ON COLUMN flow_node.create_time IS '创建时间';
COMMENT ON COLUMN flow_node.update_time IS '更新时间';
COMMENT ON COLUMN flow_node.del_flag IS '删除标志';
COMMENT ON COLUMN flow_node.tenant_id IS '租户id';
CREATE TABLE flow_skip
(
    id             int8         NOT NULL,                             -- ä¸»é”®id
    definition_id  int8         NOT NULL,                             -- æµç¨‹å®šä¹‰id
    now_node_code  varchar(100) NOT NULL,                             -- å½“前流程节点的编码
    now_node_type  int2         NULL,                                 -- å½“前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    next_node_code varchar(100) NOT NULL,                             -- ä¸‹ä¸€ä¸ªæµç¨‹èŠ‚ç‚¹çš„ç¼–ç 
    next_node_type int2         NULL,                                 -- ä¸‹ä¸€ä¸ªèŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    skip_name      varchar(100) NULL,                                 -- è·³è½¬åç§°
    skip_type      varchar(40)  NULL,                                 -- è·³è½¬ç±»åž‹ï¼ˆPASS审批通过 REJECT退回)
    skip_condition varchar(200) NULL,                                 -- è·³è½¬æ¡ä»¶
    coordinate     varchar(100) NULL,                                 -- åæ ‡
    create_time    timestamp    NULL,                                 -- åˆ›å»ºæ—¶é—´
    update_time    timestamp    NULL,                                 -- æ›´æ–°æ—¶é—´
    del_flag       bpchar(1)    NULL DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id      varchar(40)  NULL,                                 -- ç§Ÿæˆ·id
    CONSTRAINT flow_skip_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_skip IS '节点跳转关联表';
COMMENT ON COLUMN flow_skip.id IS '主键id';
COMMENT ON COLUMN flow_skip.definition_id IS '流程定义id';
COMMENT ON COLUMN flow_skip.now_node_code IS '当前流程节点的编码';
COMMENT ON COLUMN flow_skip.now_node_type IS '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_skip.next_node_code IS '下一个流程节点的编码';
COMMENT ON COLUMN flow_skip.next_node_type IS '下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_skip.skip_name IS '跳转名称';
COMMENT ON COLUMN flow_skip.skip_type IS '跳转类型(PASS审批通过 REJECT退回)';
COMMENT ON COLUMN flow_skip.skip_condition IS '跳转条件';
COMMENT ON COLUMN flow_skip.coordinate IS '坐标';
COMMENT ON COLUMN flow_skip.create_time IS '创建时间';
COMMENT ON COLUMN flow_skip.update_time IS '更新时间';
COMMENT ON COLUMN flow_skip.del_flag IS '删除标志';
COMMENT ON COLUMN flow_skip.tenant_id IS '租户id';
CREATE TABLE flow_instance
(
    id              int8         NOT NULL,                                 -- ä¸»é”®id
    definition_id   int8         NOT NULL,                                 -- å¯¹åº”flow_definition表的id
    business_id     varchar(40)  NOT NULL,                                 -- ä¸šåŠ¡id
    node_type       int2         NOT NULL,                                 -- èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    node_code       varchar(40)  NOT NULL,                                 -- æµç¨‹èŠ‚ç‚¹ç¼–ç 
    node_name       varchar(100) NULL,                                     -- æµç¨‹èŠ‚ç‚¹åç§°
    variable        text         NULL,                                     -- ä»»åŠ¡å˜é‡
    flow_status     varchar(20)  NOT NULL,                                 -- æµç¨‹çŠ¶æ€ï¼ˆ0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 8已完成 9已退回 10失效)
    activity_status int2         NOT NULL DEFAULT 1,                       -- æµç¨‹æ¿€æ´»çŠ¶æ€ï¼ˆ0挂起 1激活)
    def_json        text         NULL,                                     -- æµç¨‹å®šä¹‰json
    create_by       varchar(64)  NULL     DEFAULT '':: character varying,  -- åˆ›å»ºè€…
    create_time     timestamp    NULL,                                     -- åˆ›å»ºæ—¶é—´
    update_time     timestamp    NULL,                                     -- æ›´æ–°æ—¶é—´
    ext             varchar(500) NULL,                                     -- æ‰©å±•字段,预留给业务系统使用
    del_flag        bpchar(1)    NULL     DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id       varchar(40)  NULL,                                     -- ç§Ÿæˆ·id
    CONSTRAINT flow_instance_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_instance IS '流程实例表';
COMMENT ON COLUMN flow_instance.id IS '主键id';
COMMENT ON COLUMN flow_instance.definition_id IS '对应flow_definition表的id';
COMMENT ON COLUMN flow_instance.business_id IS '业务id';
COMMENT ON COLUMN flow_instance.node_type IS '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_instance.node_code IS '流程节点编码';
COMMENT ON COLUMN flow_instance.node_name IS '流程节点名称';
COMMENT ON COLUMN flow_instance.variable IS '任务变量';
COMMENT ON COLUMN flow_instance.flow_status IS '流程状态(0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 3自动通过 4终止 5作废 6撤销 7取回  8已完成 9已退回 10失效)';
COMMENT ON COLUMN flow_instance.activity_status IS '流程激活状态(0挂起 1激活)';
COMMENT ON COLUMN flow_instance.def_json IS '流程定义json';
COMMENT ON COLUMN flow_instance.create_by IS '创建者';
COMMENT ON COLUMN flow_instance.create_time IS '创建时间';
COMMENT ON COLUMN flow_instance.update_time IS '更新时间';
COMMENT ON COLUMN flow_instance.ext IS '扩展字段,预留给业务系统使用';
COMMENT ON COLUMN flow_instance.del_flag IS '删除标志';
COMMENT ON COLUMN flow_instance.tenant_id IS '租户id';
CREATE TABLE flow_task
(
    id            int8         NOT NULL,                             -- ä¸»é”®id
    definition_id int8         NOT NULL,                             -- å¯¹åº”flow_definition表的id
    instance_id   int8         NOT NULL,                             -- å¯¹åº”flow_instance表的id
    node_code     varchar(100) NOT NULL,                             -- èŠ‚ç‚¹ç¼–ç 
    node_name     varchar(100) NULL,                                 -- èŠ‚ç‚¹åç§°
    node_type     int2         NOT NULL,                             -- èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    form_custom   bpchar(1)    NULL DEFAULT 'N':: character varying, -- å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
    form_path     varchar(100) NULL,                                 -- å®¡æ‰¹è¡¨å•路径
    create_time   timestamp    NULL,                                 -- åˆ›å»ºæ—¶é—´
    update_time   timestamp    NULL,                                 -- æ›´æ–°æ—¶é—´
    del_flag      bpchar(1)    NULL DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id     varchar(40)  NULL,                                 -- ç§Ÿæˆ·id
    CONSTRAINT flow_task_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_task IS '待办任务表';
COMMENT ON COLUMN flow_task.id IS '主键id';
COMMENT ON COLUMN flow_task.definition_id IS '对应flow_definition表的id';
COMMENT ON COLUMN flow_task.instance_id IS '对应flow_instance表的id';
COMMENT ON COLUMN flow_task.node_code IS '节点编码';
COMMENT ON COLUMN flow_task.node_name IS '节点名称';
COMMENT ON COLUMN flow_task.node_type IS '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_task.form_custom IS '审批表单是否自定义(Y是 N否)';
COMMENT ON COLUMN flow_task.form_path IS '审批表单路径';
COMMENT ON COLUMN flow_task.create_time IS '创建时间';
COMMENT ON COLUMN flow_task.update_time IS '更新时间';
COMMENT ON COLUMN flow_task.del_flag IS '删除标志';
COMMENT ON COLUMN flow_task.tenant_id IS '租户id';
CREATE TABLE flow_his_task
(
    id               int8         NOT NULL,                                 -- ä¸»é”®id
    definition_id    int8         NOT NULL,                                 -- å¯¹åº”flow_definition表的id
    instance_id      int8         NOT NULL,                                 -- å¯¹åº”flow_instance表的id
    task_id          int8         NOT NULL,                                 -- å¯¹åº”flow_task表的id
    node_code        varchar(200) NULL,                                     -- å¼€å§‹èŠ‚ç‚¹ç¼–ç 
    node_name        varchar(200) NULL,                                     -- å¼€å§‹èŠ‚ç‚¹åç§°
    node_type        int2         NULL,                                     -- å¼€å§‹èŠ‚ç‚¹ç±»åž‹ï¼ˆ0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
    target_node_code varchar(200) NULL,                                     -- ç›®æ ‡èŠ‚ç‚¹ç¼–ç 
    target_node_name varchar(200) NULL,                                     -- ç»“束节点名称
    approver         varchar(40)  NULL,                                     -- å®¡æ‰¹è€…
    cooperate_type   int2         NOT NULL DEFAULT 0,                       -- åä½œæ–¹å¼(1审批 2转办 3委派 4会签 5票签 6加签 7减签)
    collaborator     varchar(40)  NULL,                                     -- åä½œäºº(只有转办、会签、票签、委派)
    skip_type        varchar(10)  NULL,                                     -- æµè½¬ç±»åž‹ï¼ˆPASS通过 REJECT退回 NONE无动作)
    flow_status      varchar(20)  NOT NULL,                                 -- æµç¨‹çŠ¶æ€ï¼ˆ0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 8已完成 9已退回 10失效)
    form_custom      bpchar(1)    NULL     DEFAULT 'N':: character varying, -- å®¡æ‰¹è¡¨å•是否自定义(Y是 N否)
    form_path        varchar(100) NULL,                                     -- å®¡æ‰¹è¡¨å•路径
    ext              varchar(500) NULL,                                     -- æ‰©å±•字段,预留给业务系统使用
    message          varchar(500) NULL,                                     -- å®¡æ‰¹æ„è§
    variable         text         NULL,                                     -- ä»»åŠ¡å˜é‡
    create_time      timestamp    NULL,                                     -- åˆ›å»ºæ—¶é—´
    update_time      timestamp    NULL,                                     -- æ›´æ–°æ—¶é—´
    del_flag         bpchar(1)    NULL     DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id        varchar(40)  NULL,                                     -- ç§Ÿæˆ·id
    CONSTRAINT flow_his_task_pkey PRIMARY KEY (id)
);
COMMENT ON TABLE flow_his_task IS '历史任务记录表';
COMMENT ON COLUMN flow_his_task.id IS '主键id';
COMMENT ON COLUMN flow_his_task.definition_id IS '对应flow_definition表的id';
COMMENT ON COLUMN flow_his_task.instance_id IS '对应flow_instance表的id';
COMMENT ON COLUMN flow_his_task.task_id IS '对应flow_task表的id';
COMMENT ON COLUMN flow_his_task.node_code IS '开始节点编码';
COMMENT ON COLUMN flow_his_task.node_name IS '开始节点名称';
COMMENT ON COLUMN flow_his_task.node_type IS '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)';
COMMENT ON COLUMN flow_his_task.target_node_code IS '目标节点编码';
COMMENT ON COLUMN flow_his_task.target_node_name IS '结束节点名称';
COMMENT ON COLUMN flow_his_task.approver IS '审批者';
COMMENT ON COLUMN flow_his_task.cooperate_type IS '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)';
COMMENT ON COLUMN flow_his_task.collaborator IS '协作人';
COMMENT ON COLUMN flow_his_task.skip_type IS '流转类型(PASS通过 REJECT退回 NONE无动作)';
COMMENT ON COLUMN flow_his_task.flow_status IS '流程状态(1审批中 2 å®¡æ‰¹é€šè¿‡ 9已退回 10失效)';
COMMENT ON COLUMN flow_his_task.form_custom IS '审批表单是否自定义(Y是 N否)';
COMMENT ON COLUMN flow_his_task.form_path IS '审批表单路径';
COMMENT ON COLUMN flow_his_task.message IS '审批意见';
COMMENT ON COLUMN flow_his_task.variable IS '任务变量';
COMMENT ON COLUMN flow_his_task.ext IS '扩展字段,预留给业务系统使用';
COMMENT ON COLUMN flow_his_task.create_time IS '任务开始时间';
COMMENT ON COLUMN flow_his_task.update_time IS '审批完成时间';
COMMENT ON COLUMN flow_his_task.del_flag IS '删除标志';
COMMENT ON COLUMN flow_his_task.tenant_id IS '租户id';
CREATE TABLE flow_user
(
    id           int8        NOT NULL,                             -- ä¸»é”®id
    "type"       bpchar(1)   NOT NULL,                             -- äººå‘˜ç±»åž‹ï¼ˆ1待办任务的审批人权限 2待办任务的转办人权限 3流程实例的抄送人权限 4待办任务的委托人权限)
    processed_by varchar(80) NULL,                                 -- æƒé™äºº
    associated   int8        NOT NULL,                             -- ä»»åŠ¡è¡¨id
    create_time  timestamp   NULL,                                 -- åˆ›å»ºæ—¶é—´
    create_by    varchar(80) NULL,                                 -- åˆ›å»ºäºº
    update_time  timestamp   NULL,                                 -- æ›´æ–°æ—¶é—´
    del_flag     bpchar(1)   NULL DEFAULT '0':: character varying, -- åˆ é™¤æ ‡å¿—
    tenant_id    varchar(40) NULL,                                 -- ç§Ÿæˆ·id
    CONSTRAINT flow_user_pk PRIMARY KEY (id)
);
CREATE INDEX user_processed_type ON flow_user USING btree (processed_by, type);
COMMENT ON TABLE flow_user IS '流程用户表';
COMMENT ON COLUMN flow_user.id IS '主键id';
COMMENT ON COLUMN flow_user."type" IS '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)';
COMMENT ON COLUMN flow_user.processed_by IS '权限人';
COMMENT ON COLUMN flow_user.associated IS '任务表id';
COMMENT ON COLUMN flow_user.create_time IS '创建时间';
COMMENT ON COLUMN flow_user.create_by IS '创建人';
COMMENT ON COLUMN flow_user.update_time IS '更新时间';
COMMENT ON COLUMN flow_user.del_flag IS '删除标志';
COMMENT ON COLUMN flow_user.tenant_id IS '租户id';
-- ----------------------------
-- æµç¨‹åˆ†ç±»è¡¨
-- ----------------------------
CREATE TABLE flow_category
(
    category_id   int8         NOT NULL,
    tenant_id     VARCHAR(20)  DEFAULT '000000'::varchar,
    parent_id     int8         DEFAULT 0,
    ancestors     VARCHAR(500) DEFAULT ''::varchar,
    category_name VARCHAR(30)  NOT NULL,
    order_num     INT          DEFAULT 0,
    del_flag      CHAR         DEFAULT '0'::bpchar,
    create_dept   int8,
    create_by     int8,
    create_time   TIMESTAMP,
    update_by     int8,
    update_time   TIMESTAMP,
    PRIMARY KEY (category_id)
);
COMMENT ON TABLE flow_category IS '流程分类';
COMMENT ON COLUMN flow_category.category_id IS '流程分类ID';
COMMENT ON COLUMN flow_category.tenant_id IS '租户编号';
COMMENT ON COLUMN flow_category.parent_id IS '父流程分类id';
COMMENT ON COLUMN flow_category.ancestors IS '祖级列表';
COMMENT ON COLUMN flow_category.category_name IS '流程分类名称';
COMMENT ON COLUMN flow_category.order_num IS '显示顺序';
COMMENT ON COLUMN flow_category.del_flag IS '删除标志(0代表存在 1代表删除)';
COMMENT ON COLUMN flow_category.create_dept IS '创建部门';
COMMENT ON COLUMN flow_category.create_by IS '创建者';
COMMENT ON COLUMN flow_category.create_time IS '创建时间';
COMMENT ON COLUMN flow_category.update_by IS '更新者';
COMMENT ON COLUMN flow_category.update_time IS '更新时间';
INSERT INTO flow_category VALUES (100, '000000', 0, '0', 'OA审批', 0, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (101, '000000', 100, '0,100', '假勤管理', 0, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (102, '000000', 100, '0,100', '人事管理', 1, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (103, '000000', 101, '0,100,101', '请假', 0, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (104, '000000', 101, '0,100,101', '出差', 1, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (105, '000000', 101, '0,100,101', '加班', 2, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (106, '000000', 101, '0,100,101', '换班', 3, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '外出', 4, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, now(), NULL, NULL);
INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, now(), NULL, NULL);
-- ----------------------------
-- è¯·å‡å•信息
-- ----------------------------
CREATE TABLE test_leave
(
    id          int8         NOT NULL,
    tenant_id   VARCHAR(20)  DEFAULT '000000'::varchar,
    leave_type  VARCHAR(255) NOT NULL,
    start_date  TIMESTAMP    NOT NULL,
    end_date    TIMESTAMP    NOT NULL,
    leave_days  int2          NOT NULL,
    remark      VARCHAR(255),
    status      VARCHAR(255),
    create_dept int8,
    create_by   int8,
    create_time TIMESTAMP,
    update_by   int8,
    update_time TIMESTAMP,
    PRIMARY KEY (id)
);
COMMENT ON TABLE test_leave IS '请假申请表';
COMMENT ON COLUMN test_leave.id IS 'id';
COMMENT ON COLUMN test_leave.tenant_id IS '租户编号';
COMMENT ON COLUMN test_leave.leave_type IS '请假类型';
COMMENT ON COLUMN test_leave.start_date IS '开始时间';
COMMENT ON COLUMN test_leave.end_date IS '结束时间';
COMMENT ON COLUMN test_leave.leave_days IS '请假天数';
COMMENT ON COLUMN test_leave.remark IS '请假原因';
COMMENT ON COLUMN test_leave.status IS '状态';
COMMENT ON COLUMN test_leave.create_dept IS '创建部门';
COMMENT ON COLUMN test_leave.create_by IS '创建者';
COMMENT ON COLUMN test_leave.create_time IS '创建时间';
COMMENT ON COLUMN test_leave.update_by IS '更新者';
COMMENT ON COLUMN test_leave.update_time IS '更新时间';
INSERT INTO sys_menu VALUES ('11616', '工作流', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11623', '流程分类查询', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11624', '流程分类新增', '11622', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:add', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, now(), NULL, NULL, '请假申请菜单');
INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11640', '请假申请新增', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11641', '请假申请修改', '11638', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11642', '请假申请删除', '11638', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11643', '请假申请导出', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_dict_type VALUES (13, '000000', '业务状态', 'wf_business_status', 103, 1, now(), NULL, NULL, '业务状态列表');
INSERT INTO sys_dict_type VALUES (14, '000000', '表单类型', 'wf_form_type', 103, 1, now(), NULL, NULL, '表单类型列表');
INSERT INTO sys_dict_type VALUES (15, '000000', '任务状态', 'wf_task_status', 103, 1, now(), NULL, NULL, '任务状态');
INSERT INTO sys_dict_data VALUES (39, '000000', 1, '已撤销', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '已撤销');
INSERT INTO sys_dict_data VALUES (40, '000000', 2, '草稿', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, now(), NULL, NULL, '草稿');
INSERT INTO sys_dict_data VALUES (41, '000000', 3, '待审核', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '待审核');
INSERT INTO sys_dict_data VALUES (42, '000000', 4, '已完成', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, now(), NULL, NULL, '已完成');
INSERT INTO sys_dict_data VALUES (43, '000000', 5, '已作废', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '已作废');
INSERT INTO sys_dict_data VALUES (44, '000000', 6, '已退回', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '已退回');
INSERT INTO sys_dict_data VALUES (45, '000000', 7, '已终止', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '已终止');
INSERT INTO sys_dict_data VALUES (46, '000000', 1, '自定义表单', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, now(), NULL, NULL, '自定义表单');
INSERT INTO sys_dict_data VALUES (47, '000000', 2, '动态表单', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '动态表单');
INSERT INTO sys_dict_data VALUES (48, '000000', 1, '撤销', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '撤销');
INSERT INTO sys_dict_data VALUES (49, '000000', 2, '通过', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, now(), NULL, NULL, '通过');
INSERT INTO sys_dict_data VALUES (50, '000000', 3, '待审核', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '待审核');
INSERT INTO sys_dict_data VALUES (51, '000000', 4, '作废', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '作废');
INSERT INTO sys_dict_data VALUES (52, '000000', 5, '退回', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '退回');
INSERT INTO sys_dict_data VALUES (53, '000000', 6, '终止', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '终止');
INSERT INTO sys_dict_data VALUES (54, '000000', 7, '转办', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '转办');
INSERT INTO sys_dict_data VALUES (55, '000000', 8, '委托', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '委托');
INSERT INTO sys_dict_data VALUES (56, '000000', 9, '抄送', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '抄送');
INSERT INTO sys_dict_data VALUES (57, '000000', 10, '加签', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '加签');
INSERT INTO sys_dict_data VALUES (58, '000000', 11, '减签', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '减签');
INSERT INTO sys_dict_data VALUES (59, '000000', 11, '超时', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '超时');
script/sql/ry_job.sql
ÎļþÃû´Ó script/sql/snail_job.sql ÐÞ¸Ä
@@ -40,13 +40,14 @@
  DEFAULT CHARSET = utf8mb4 COMMENT ='组配置';
INSERT INTO `sj_group_config` VALUES (1, 'dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, now(), now());
INSERT INTO `sj_group_config` VALUES (2, 'prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, 4, now(), now());
CREATE TABLE `sj_notify_config`
(
    `id`                     bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    `namespace_id`           varchar(64)         NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
    `group_name`             varchar(64)         NOT NULL COMMENT '组名称',
    `business_id`            varchar(64)         NOT NULL COMMENT '业务id (job_id或workflow_id或scene_name)',
    `notify_name`            varchar(64)         NOT NULL DEFAULT '' COMMENT '通知名称',
    `system_task_type`       tinyint(4)          NOT NULL DEFAULT 3 COMMENT '任务类型 1. é‡è¯•任务 2. é‡è¯•回调 3、JOB任务 4、WORKFLOW任务',
    `notify_status`          tinyint(4)          NOT NULL DEFAULT 0 COMMENT '通知状态 0、未启用 1、启用',
    `recipient_ids`          varchar(128)        NOT NULL COMMENT '接收人id列表',
@@ -58,7 +59,7 @@
    `create_dt`              datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_dt`              datetime            NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `business_id`)
    KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 0
  DEFAULT CHARSET = utf8mb4 COMMENT ='通知配置';
@@ -187,6 +188,7 @@
    `max_retry_count`  int(11)             NOT NULL DEFAULT 5 COMMENT '最大重试次数',
    `back_off`         tinyint(4)          NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON è¡¨è¾¾å¼',
    `trigger_interval` varchar(16)         NOT NULL DEFAULT '' COMMENT '间隔时长',
    `notify_ids`       varchar(128)        NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表',
    `deadline_request` bigint(20) unsigned NOT NULL DEFAULT 60000 COMMENT 'Deadline Request è°ƒç”¨é“¾è¶…æ—¶ å•位毫秒',
    `executor_timeout` int(11) unsigned    NOT NULL DEFAULT 5 COMMENT '任务执行超时时间,单位秒',
    `route_key`        tinyint(4)          NOT NULL DEFAULT 4 COMMENT '路由策略',
@@ -299,6 +301,8 @@
    `retry_interval`   int(11)             NOT NULL DEFAULT 0 COMMENT '重试间隔(s)',
    `bucket_index`     int(11)             NOT NULL DEFAULT 0 COMMENT 'bucket',
    `resident`         tinyint(4)          NOT NULL DEFAULT 0 COMMENT '是否是常驻任务',
    `notify_ids`       varchar(128)        NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表',
    `owner_id`         bigint(20)          NULL                 COMMENT '负责人id',
    `description`      varchar(256)        NOT NULL DEFAULT '' COMMENT '描述',
    `ext_attrs`        varchar(256)        NULL     DEFAULT '' COMMENT '扩展字段',
    `deleted`          tinyint(4)          NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除',
@@ -312,7 +316,7 @@
  AUTO_INCREMENT = 0
  DEFAULT CHARSET = utf8mb4 COMMENT ='任务信息';
INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', '', 0 , now(), now());
INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', 0 , now(), now());
CREATE TABLE `sj_job_log_message`
(
@@ -450,6 +454,7 @@
    `description`      varchar(256)        NOT NULL DEFAULT '' COMMENT '描述',
    `flow_info`        text                         DEFAULT NULL COMMENT '流程信息',
    `wf_context`       text                         DEFAULT NULL COMMENT '上下文',
    `notify_ids`       varchar(128)        NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表',
    `bucket_index`     int(11)             NOT NULL DEFAULT 0 COMMENT 'bucket',
    `version`          int(11)             NOT NULL COMMENT '版本号',
    `ext_attrs`        varchar(256)        NULL     DEFAULT '' COMMENT '扩展字段',
script/sql/ry_vue_5.X.sql
@@ -5,7 +5,7 @@
(
    id                 bigint           not null        comment '主键',
    user_id            bigint           not null        comment '用户ID',
    tenant_id          varchar(20)      default null    comment '租户id',
    tenant_id          varchar(20)      default '000000' comment '租户id',
    auth_id            varchar(255)     not null        comment '平台+平台唯一id',
    source             varchar(255)     not null        comment '用户来源',
    open_id            varchar(255)     default null    comment '平台编号唯一id',
@@ -31,7 +31,7 @@
    create_time        datetime                         comment '创建时间',
    update_by          bigint(20)                       comment '更新者',
    update_time        datetime                         comment '更新时间',
    del_flag           char(1)          default '0'     comment '删除标志(0代表存在 2代表删除)',
    del_flag           char(1)          default '0'     comment '删除标志(0代表存在 1代表删除)',
    PRIMARY KEY (id)
) engine=innodb comment = '社会化关系表';
@@ -55,7 +55,7 @@
    expire_time       datetime                      comment '过期时间',
    account_count     int           default -1      comment '用户数量(-1不限制)',
    status            char(1)       default '0'     comment '租户状态(0正常 1停用)',
    del_flag          char(1)       default '0'     comment '删除标志(0代表存在 2代表删除)',
    del_flag          char(1)       default '0'     comment '删除标志(0代表存在 1代表删除)',
    create_dept       bigint(20)                    comment '创建部门',
    create_by         bigint(20)                    comment '创建者',
    create_time       datetime                      comment '创建时间',
@@ -82,7 +82,7 @@
    remark                  varchar(200)               comment '备注',
    menu_check_strictly     tinyint(1)     default 1   comment '菜单树选择项是否关联显示',
    status                  char(1)        default '0' comment '状态(0正常 1停用)',
    del_flag                char(1)        default '0' comment '删除标志(0代表存在 2代表删除)',
    del_flag                char(1)        default '0' comment '删除标志(0代表存在 1代表删除)',
    create_dept             bigint(20)                 comment '创建部门',
    create_by               bigint(20)                 comment '创建者',
    create_time             datetime                   comment '创建时间',
@@ -107,7 +107,7 @@
    phone             varchar(11)     default null               comment '联系电话',
    email             varchar(50)     default null               comment '邮箱',
    status            char(1)         default '0'                comment '部门状态(0正常 1停用)',
    del_flag          char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
    del_flag          char(1)         default '0'                comment '删除标志(0代表存在 1代表删除)',
    create_dept       bigint(20)      default null               comment '创建部门',
    create_by         bigint(20)      default null               comment '创建者',
    create_time       datetime                                   comment '创建时间',
@@ -149,7 +149,7 @@
    avatar            bigint(20)                                 comment '头像地址',
    password          varchar(100)    default ''                 comment '密码',
    status            char(1)         default '0'                comment '帐号状态(0正常 1停用)',
    del_flag          char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
    del_flag          char(1)         default '0'                comment '删除标志(0代表存在 1代表删除)',
    login_ip          varchar(128)    default ''                 comment '最后登录IP',
    login_date        datetime                                   comment '最后登录时间',
    create_dept       bigint(20)      default null               comment '创建部门',
@@ -212,7 +212,7 @@
    menu_check_strictly  tinyint(1)      default 1                  comment '菜单树选择项是否关联显示',
    dept_check_strictly  tinyint(1)      default 1                  comment '部门树选择项是否关联显示',
    status               char(1)         not null                   comment '角色状态(0正常 1停用)',
    del_flag             char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
    del_flag             char(1)         default '0'                comment '删除标志(0代表存在 1代表删除)',
    create_dept          bigint(20)      default null               comment '创建部门',
    create_by            bigint(20)      default null               comment '创建者',
    create_time          datetime                                   comment '创建时间',
@@ -441,6 +441,8 @@
insert into sys_role_menu values ('3', '106');
insert into sys_role_menu values ('3', '107');
insert into sys_role_menu values ('3', '108');
insert into sys_role_menu values ('3', '118');
insert into sys_role_menu values ('3', '123');
insert into sys_role_menu values ('3', '500');
insert into sys_role_menu values ('3', '501');
insert into sys_role_menu values ('3', '1001');
@@ -488,6 +490,12 @@
insert into sys_role_menu values ('3', '1043');
insert into sys_role_menu values ('3', '1044');
insert into sys_role_menu values ('3', '1045');
insert into sys_role_menu values ('3', '1050');
insert into sys_role_menu values ('3', '1061');
insert into sys_role_menu values ('3', '1062');
insert into sys_role_menu values ('3', '1063');
insert into sys_role_menu values ('3', '1064');
insert into sys_role_menu values ('3', '1065');
insert into sys_role_menu values ('3', '1500');
insert into sys_role_menu values ('3', '1501');
insert into sys_role_menu values ('3', '1502');
@@ -500,6 +508,25 @@
insert into sys_role_menu values ('3', '1509');
insert into sys_role_menu values ('3', '1510');
insert into sys_role_menu values ('3', '1511');
insert into sys_role_menu values ('3', '1600');
insert into sys_role_menu values ('3', '1601');
insert into sys_role_menu values ('3', '1602');
insert into sys_role_menu values ('3', '1603');
insert into sys_role_menu values ('3', '1620');
insert into sys_role_menu values ('3', '1621');
insert into sys_role_menu values ('3', '1622');
insert into sys_role_menu values ('3', '1623');
insert into sys_role_menu values ('3', '11618');
insert into sys_role_menu values ('3', '11619');
insert into sys_role_menu values ('3', '11629');
insert into sys_role_menu values ('3', '11632');
insert into sys_role_menu values ('3', '11633');
insert into sys_role_menu values ('3', '11638');
insert into sys_role_menu values ('3', '11639');
insert into sys_role_menu values ('3', '11640');
insert into sys_role_menu values ('3', '11641');
insert into sys_role_menu values ('3', '11642');
insert into sys_role_menu values ('3', '11643');
insert into sys_role_menu values ('4', '5');
insert into sys_role_menu values ('4', '1500');
insert into sys_role_menu values ('4', '1501');
@@ -554,10 +581,10 @@
    oper_url          varchar(255)    default ''                 comment '请求URL',
    oper_ip           varchar(128)    default ''                 comment '主机地址',
    oper_location     varchar(255)    default ''                 comment '操作地点',
    oper_param        varchar(2000)   default ''                 comment '请求参数',
    json_result       varchar(2000)   default ''                 comment '返回参数',
    oper_param        varchar(4000)   default ''                 comment '请求参数',
    json_result       varchar(4000)   default ''                 comment '返回参数',
    status            int(1)          default 0                  comment '操作状态(0正常 1异常)',
    error_msg         varchar(2000)   default ''                 comment '错误消息',
    error_msg         varchar(4000)   default ''                 comment '错误消息',
    oper_time         datetime                                   comment '操作时间',
    cost_time         bigint(20)      default 0                  comment '消耗时间',
    primary key (oper_id),
@@ -840,7 +867,7 @@
insert into sys_oss_config values (1, '000000', 'minio',  'ruoyi',            'ruoyi123',        'ruoyi',             '', '127.0.0.1:9000',                '','N', '',             '1' ,'0', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (2, '000000', 'qiniu',  'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi',             '', 's3-cn-north-1.qiniucs.com',     '','N', '',             '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi',             '', 'oss-cn-beijing.aliyuncs.com',   '','N', '',             '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1250000000',  '', 'cos.ap-beijing.myqcloud.com',   '','N', 'ap-beijing',   '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1240000000',  '', 'cos.ap-beijing.myqcloud.com',   '','N', 'ap-beijing',   '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (5, '000000', 'image',  'ruoyi',            'ruoyi123',        'ruoyi',             'image', '127.0.0.1:9000',           '','N', '',             '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
-- ----------------------------
@@ -856,7 +883,7 @@
    active_timeout      int(11)       default 1800        comment 'token活跃超时时间',
    timeout             int(11)       default 604800      comment 'token固定超时',
    status              char(1)       default '0'         comment '状态(0正常 1停用)',
    del_flag            char(1)       default '0'         comment '删除标志(0代表存在 2代表删除)',
    del_flag            char(1)       default '0'         comment '删除标志(0代表存在 1代表删除)',
    create_dept         bigint(20)    default null        comment '创建部门',
    create_by           bigint(20)    default null        comment '创建者',
    create_time         datetime      default null        comment '创建时间',
script/sql/ry_workflow.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,253 @@
-- ----------------------------
-- 0、warm-flow-all.sql,地址:https://gitee.com/dromara/warm-flow/blob/master/sql/mysql/warm-flow-all.sql
-- ----------------------------
CREATE TABLE `flow_definition`
(
    `id`              bigint unsigned NOT NULL COMMENT '主键id',
    `flow_code`       varchar(40)     NOT NULL COMMENT '流程编码',
    `flow_name`       varchar(100)    NOT NULL COMMENT '流程名称',
    `category`        varchar(100)             DEFAULT NULL COMMENT '流程类别',
    `version`         varchar(20)     NOT NULL COMMENT '流程版本',
    `is_publish`      tinyint(1)      NOT NULL DEFAULT '0' COMMENT '是否发布(0未发布 1已发布 9失效)',
    `form_custom`     char(1)                  DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
    `form_path`       varchar(100)             DEFAULT NULL COMMENT '审批表单路径',
    `activity_status` tinyint(1)      NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
    `listener_type`   varchar(100)             DEFAULT NULL COMMENT '监听器类型',
    `listener_path`   varchar(400)             DEFAULT NULL COMMENT '监听器路径',
    `ext`             varchar(500)             DEFAULT NULL COMMENT '业务详情 å­˜ä¸šåŠ¡è¡¨å¯¹è±¡json字符串',
    `create_time`     datetime                 DEFAULT NULL COMMENT '创建时间',
    `update_time`     datetime                 DEFAULT NULL COMMENT '更新时间',
    `del_flag`        char(1)                  DEFAULT '0' COMMENT '删除标志',
    `tenant_id`       varchar(40)              DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='流程定义表';
CREATE TABLE `flow_node`
(
    `id`              bigint unsigned NOT NULL COMMENT '主键id',
    `node_type`       tinyint(1)      NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `definition_id`   bigint          NOT NULL COMMENT '流程定义id',
    `node_code`       varchar(100)    NOT NULL COMMENT '流程节点编码',
    `node_name`       varchar(100)  DEFAULT NULL COMMENT '流程节点名称',
    `permission_flag` varchar(200)  DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)',
    `node_ratio`      decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值',
    `coordinate`      varchar(100)  DEFAULT NULL COMMENT '坐标',
    `skip_any_node`   varchar(100)  DEFAULT 'N' COMMENT '是否可以退回任意节点(Y是 N否)即将删除',
    `any_node_skip`   varchar(100)  DEFAULT NULL COMMENT '任意结点跳转',
    `listener_type`   varchar(100)  DEFAULT NULL COMMENT '监听器类型',
    `listener_path`   varchar(400)  DEFAULT NULL COMMENT '监听器路径',
    `handler_type`    varchar(100)  DEFAULT NULL COMMENT '处理器类型',
    `handler_path`    varchar(400)  DEFAULT NULL COMMENT '处理器路径',
    `form_custom`     char(1)       DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
    `form_path`       varchar(100)  DEFAULT NULL COMMENT '审批表单路径',
    `version`         varchar(20)     NOT NULL COMMENT '版本',
    `create_time`     datetime      DEFAULT NULL COMMENT '创建时间',
    `update_time`     datetime      DEFAULT NULL COMMENT '更新时间',
    `del_flag`        char(1)       DEFAULT '0' COMMENT '删除标志',
    `tenant_id`       varchar(40)   DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='流程节点表';
CREATE TABLE `flow_skip`
(
    `id`             bigint unsigned NOT NULL COMMENT '主键id',
    `definition_id`  bigint          NOT NULL COMMENT '流程定义id',
    `now_node_code`  varchar(100)    NOT NULL COMMENT '当前流程节点的编码',
    `now_node_type`  tinyint(1)   DEFAULT NULL COMMENT '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `next_node_code` varchar(100)    NOT NULL COMMENT '下一个流程节点的编码',
    `next_node_type` tinyint(1)   DEFAULT NULL COMMENT '下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `skip_name`      varchar(100) DEFAULT NULL COMMENT '跳转名称',
    `skip_type`      varchar(40)  DEFAULT NULL COMMENT '跳转类型(PASS审批通过 REJECT退回)',
    `skip_condition` varchar(200) DEFAULT NULL COMMENT '跳转条件',
    `coordinate`     varchar(100) DEFAULT NULL COMMENT '坐标',
    `create_time`    datetime     DEFAULT NULL COMMENT '创建时间',
    `update_time`    datetime     DEFAULT NULL COMMENT '更新时间',
    `del_flag`       char(1)      DEFAULT '0' COMMENT '删除标志',
    `tenant_id`      varchar(40)  DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='节点跳转关联表';
CREATE TABLE `flow_instance`
(
    `id`              bigint      NOT NULL COMMENT '主键id',
    `definition_id`   bigint      NOT NULL COMMENT '对应flow_definition表的id',
    `business_id`     varchar(40) NOT NULL COMMENT '业务id',
    `node_type`       tinyint(1)  NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `node_code`       varchar(40) NOT NULL COMMENT '流程节点编码',
    `node_name`       varchar(100)         DEFAULT NULL COMMENT '流程节点名称',
    `variable`        text COMMENT '任务变量',
    `flow_status`     varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 3自动通过 4终止 5作废 6撤销 7取回  8已完成 9已退回 10失效)',
    `activity_status` tinyint(1)  NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
    `def_json`        text COMMENT '流程定义json',
    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建者',
    `create_time`     datetime             DEFAULT NULL COMMENT '创建时间',
    `update_time`     datetime             DEFAULT NULL COMMENT '更新时间',
    `ext`             varchar(500)         DEFAULT NULL COMMENT '扩展字段,预留给业务系统使用',
    `del_flag`        char(1)              DEFAULT '0' COMMENT '删除标志',
    `tenant_id`       varchar(40)          DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='流程实例表';
CREATE TABLE `flow_task`
(
    `id`            bigint       NOT NULL COMMENT '主键id',
    `definition_id` bigint       NOT NULL COMMENT '对应flow_definition表的id',
    `instance_id`   bigint       NOT NULL COMMENT '对应flow_instance表的id',
    `node_code`     varchar(100) NOT NULL COMMENT '节点编码',
    `node_name`     varchar(100) DEFAULT NULL COMMENT '节点名称',
    `node_type`     tinyint(1)   NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `form_custom`   char(1)      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
    `form_path`     varchar(100) DEFAULT NULL COMMENT '审批表单路径',
    `create_time`   datetime     DEFAULT NULL COMMENT '创建时间',
    `update_time`   datetime     DEFAULT NULL COMMENT '更新时间',
    `del_flag`      char(1)      DEFAULT '0' COMMENT '删除标志',
    `tenant_id`     varchar(40)  DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='待办任务表';
CREATE TABLE `flow_his_task`
(
    `id`               bigint(20) unsigned NOT NULL COMMENT '主键id',
    `definition_id`    bigint(20)          NOT NULL COMMENT '对应flow_definition表的id',
    `instance_id`      bigint(20)          NOT NULL COMMENT '对应flow_instance表的id',
    `task_id`          bigint(20)          NOT NULL COMMENT '对应flow_task表的id',
    `node_code`        varchar(100)                 DEFAULT NULL COMMENT '开始节点编码',
    `node_name`        varchar(100)                 DEFAULT NULL COMMENT '开始节点名称',
    `node_type`        tinyint(1)                   DEFAULT NULL COMMENT '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
    `target_node_code` varchar(200)                 DEFAULT NULL COMMENT '目标节点编码',
    `target_node_name` varchar(200)                 DEFAULT NULL COMMENT '结束节点名称',
    `approver`         varchar(40)                  DEFAULT NULL COMMENT '审批者',
    `cooperate_type`   tinyint(1)          NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)',
    `collaborator`     varchar(40)                  DEFAULT NULL COMMENT '协作人',
    `skip_type`        varchar(10)         NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)',
    `flow_status`      varchar(20)         NOT NULL COMMENT '流程状态(1审批中 2 å®¡æ‰¹é€šè¿‡ 9已退回 10失效)',
    `form_custom`      char(1)                      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
    `form_path`        varchar(100)                 DEFAULT NULL COMMENT '审批表单路径',
    `message`          varchar(500)                 DEFAULT NULL COMMENT '审批意见',
    `variable`         TEXT                         DEFAULT NULL COMMENT '任务变量',
    `ext`              varchar(500)                 DEFAULT NULL COMMENT '业务详情 å­˜ä¸šåŠ¡è¡¨å¯¹è±¡json字符串',
    `create_time`      datetime                     DEFAULT NULL COMMENT '任务开始时间',
    `update_time`      datetime                     DEFAULT NULL COMMENT '审批完成时间',
    `del_flag`         char(1)                      DEFAULT '0' COMMENT '删除标志',
    `tenant_id`        varchar(40)                  DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='历史任务记录表';
CREATE TABLE `flow_user`
(
    `id`           bigint unsigned NOT NULL COMMENT '主键id',
    `type`         char(1)         NOT NULL COMMENT '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)',
    `processed_by` varchar(80) DEFAULT NULL COMMENT '权限人',
    `associated`   bigint          NOT NULL COMMENT '任务表id',
    `create_time`  datetime    DEFAULT NULL COMMENT '创建时间',
    `create_by`    varchar(80) DEFAULT NULL COMMENT '创建人',
    `update_time`  datetime    DEFAULT NULL COMMENT '更新时间',
    `del_flag`     char(1)     DEFAULT '0' COMMENT '删除标志',
    `tenant_id`    varchar(40) DEFAULT NULL COMMENT '租户id',
    PRIMARY KEY (`id`) USING BTREE,
    KEY `user_processed_type` (`processed_by`, `type`)
) ENGINE = InnoDB COMMENT ='流程用户表';
-- ----------------------------
-- æµç¨‹åˆ†ç±»è¡¨
-- ----------------------------
create table flow_category
(
    category_id   bigint(20)  not null comment '流程分类ID',
    tenant_id     varchar(20)  default '000000' comment '租户编号',
    parent_id     bigint(20)   default 0 comment '父流程分类id',
    ancestors     varchar(500) default '' comment '祖级列表',
    category_name varchar(30) not null comment '流程分类名称',
    order_num     int(4)       default 0 comment '显示顺序',
    del_flag      char(1)      default '0' comment '删除标志(0代表存在 1代表删除)',
    create_dept   bigint(20)  null comment '创建部门',
    create_by     bigint(20)  null comment '创建者',
    create_time   datetime    null comment '创建时间',
    update_by     bigint(20)  null comment '更新者',
    update_time   datetime    null comment '更新时间',
    primary key (category_id)
) engine = innodb comment = '流程分类';
INSERT INTO flow_category values (100, '000000', 0, '0', 'OA审批', 0, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (101, '000000', 100, '0,100', '假勤管理', 0, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (102, '000000', 100, '0,100', '人事管理', 1, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (103, '000000', 101, '0,100,101', '请假', 0, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (104, '000000', 101, '0,100,101', '出差', 1, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (105, '000000', 101, '0,100,101', '加班', 2, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (106, '000000', 101, '0,100,101', '换班', 3, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (107, '000000', 101, '0,100,101', '外出', 4, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, sysdate(), null, null);
INSERT INTO flow_category values (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, sysdate(), null, null);
-- ----------------------------
-- è¯·å‡å•信息
-- ----------------------------
create table test_leave
(
    id          bigint(20)   not null comment 'id',
    tenant_id   varchar(20) default '000000' comment '租户编号',
    leave_type  varchar(255) not null comment '请假类型',
    start_date  datetime     not null comment '开始时间',
    end_date    datetime     not null comment '结束时间',
    leave_days  int(10)      not null comment '请假天数',
    remark      varchar(255) null comment '请假原因',
    status      varchar(255) null comment '状态',
    create_dept bigint       null comment '创建部门',
    create_by   bigint       null comment '创建者',
    create_time datetime     null comment '创建时间',
    update_by   bigint       null comment '更新者',
    update_time datetime     null comment '更新时间',
    PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB COMMENT = '请假申请表';
insert into sys_menu values ('11616', '工作流', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, sysdate(),NULL, NULL, '');
insert into sys_menu values ('11618', '我的任务', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu values ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
-- æµç¨‹åˆ†ç±»ç®¡ç†ç›¸å…³æŒ‰é’®
insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1,sysdate(), null, null, '');
insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1,sysdate(), null, null, '');
insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1,sysdate(), null, null, '');
insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove', '#', 103,1, sysdate(), null, null, '');
insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export', '#', 103,1, sysdate(), null, null, '');
-- è¯·å‡æµ‹è¯•相关按钮
insert into sys_menu VALUES (11638, '请假申请',     5,    1, 'leave', 'workflow/leave/index', '', 1, 0, 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, sysdate(), NULL, NULL, '请假申请菜单');
insert into sys_menu VALUES (11639, '请假申请查询', 11638, 1, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu VALUES (11640, '请假申请新增', 11638, 2, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu VALUES (11641, '请假申请修改', 11638, 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu VALUES (11642, '请假申请删除', 11638, 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
insert into sys_menu VALUES (11643, '请假申请导出', 11638, 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, sysdate(), NULL, NULL, '');
INSERT INTO sys_dict_type VALUES (13, '000000', '业务状态', 'wf_business_status', 103, 1, sysdate(), NULL, NULL, '业务状态列表');
INSERT INTO sys_dict_type VALUES (14, '000000', '表单类型', 'wf_form_type', 103, 1, sysdate(), NULL, NULL, '表单类型列表');
INSERT INTO sys_dict_type VALUES (15, '000000', '任务状态', 'wf_task_status', 103, 1, sysdate(), NULL, NULL, '任务状态');
INSERT INTO sys_dict_data VALUES (39, '000000', 1, '已撤销', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'已撤销');
INSERT INTO sys_dict_data VALUES (40, '000000', 2, '草稿', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, sysdate(), NULL, NULL, '草稿');
INSERT INTO sys_dict_data VALUES (41, '000000', 3, '待审核', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL,'待审核');
INSERT INTO sys_dict_data VALUES (42, '000000', 4, '已完成', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL,'已完成');
INSERT INTO sys_dict_data VALUES (43, '000000', 5, '已作废', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'已作废');
INSERT INTO sys_dict_data VALUES (44, '000000', 6, '已退回', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'已退回');
INSERT INTO sys_dict_data VALUES (45, '000000', 7, '已终止', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL,NULL, '已终止');
INSERT INTO sys_dict_data VALUES (46, '000000', 1, '自定义表单', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL,'自定义表单');
INSERT INTO sys_dict_data VALUES (47, '000000', 2, '动态表单', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL,'动态表单');
INSERT INTO sys_dict_data VALUES (48, '000000', 1, '撤销', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '撤销');
INSERT INTO sys_dict_data VALUES (49, '000000', 2, '通过', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL, '通过');
INSERT INTO sys_dict_data VALUES (50, '000000', 3, '待审核', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '待审核');
INSERT INTO sys_dict_data VALUES (51, '000000', 4, '作废', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '作废');
INSERT INTO sys_dict_data VALUES (52, '000000', 5, '退回', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '退回');
INSERT INTO sys_dict_data VALUES (53, '000000', 6, '终止', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '终止');
INSERT INTO sys_dict_data VALUES (54, '000000', 7, '转办', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '转办');
INSERT INTO sys_dict_data VALUES (55, '000000', 8, '委托', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '委托');
INSERT INTO sys_dict_data VALUES (56, '000000', 9, '抄送', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '抄送');
INSERT INTO sys_dict_data VALUES (57, '000000', 10, '加签', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '加签');
INSERT INTO sys_dict_data VALUES (58, '000000', 11, '减签', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '减签');
INSERT INTO sys_dict_data VALUES (59, '000000', 11, '超时', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '超时');
script/sql/sqlserver/flowable.sql
ÎļþÒÑɾ³ý
script/sql/sqlserver/sqlserver_ry_job.sql
ÎļþÃû´Ó script/sql/sqlserver/snail_job_sqlserver.sql ÐÞ¸Ä
@@ -2,7 +2,7 @@
 SnailJob Database Transfer Tool
 Source Server Type    : MySQL
 Target Server Type    : Microsoft SQL Server
 Date: 2024-07-06 12:55:47
 Date: 2024-12-27 22:24:37
*/
@@ -203,6 +203,8 @@
INSERT INTO sj_group_config(namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, bucket_index, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'', N'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', N'1', N'1', N'0', N'1', N'1', N'4', getdate(), getdate())
GO
INSERT INTO sj_group_config(namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, bucket_index, create_dt, update_dt) VALUES (N'prod', N'ruoyi_group', N'', N'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', N'1', N'1', N'0', N'1', N'1', N'4', getdate(), getdate())
GO
-- sj_notify_config
CREATE TABLE sj_notify_config
@@ -210,7 +212,7 @@
    id                     bigint        NOT NULL PRIMARY KEY IDENTITY,
    namespace_id           nvarchar(64)  NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
    group_name             nvarchar(64)  NOT NULL,
    business_id            nvarchar(64)  NOT NULL,
    notify_name            nvarchar(64)  NOT NULL DEFAULT '',
    system_task_type       tinyint       NOT NULL DEFAULT 3,
    notify_status          tinyint       NOT NULL DEFAULT 0,
    recipient_ids          nvarchar(128) NOT NULL,
@@ -224,7 +226,7 @@
)
GO
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name, business_id)
CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name)
GO
EXEC sp_addextendedproperty
@@ -249,10 +251,10 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'业务id  ( job_id或workflow_id或scene_name ) ',
     'MS_Description', N'通知名称',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_notify_config',
     'COLUMN', N'business_id'
     'COLUMN', N'notify_name'
GO
EXEC sp_addextendedproperty
@@ -915,6 +917,7 @@
    max_retry_count  int           NOT NULL DEFAULT 5,
    back_off         tinyint       NOT NULL DEFAULT 1,
    trigger_interval nvarchar(16)  NOT NULL DEFAULT '',
    notify_ids       nvarchar(128) NOT NULL DEFAULT '',
    deadline_request bigint        NOT NULL DEFAULT 60000,
    executor_timeout int           NOT NULL DEFAULT 5,
    route_key        tinyint       NOT NULL DEFAULT 4,
@@ -981,6 +984,13 @@
     'SCHEMA', N'dbo',
     'TABLE', N'sj_retry_scene_config',
     'COLUMN', N'trigger_interval'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'通知告警场景配置id列表',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_retry_scene_config',
     'COLUMN', N'notify_ids'
GO
EXEC sp_addextendedproperty
@@ -1413,6 +1423,8 @@
    retry_interval   int           NOT NULL DEFAULT 0,
    bucket_index     int           NOT NULL DEFAULT 0,
    resident         tinyint       NOT NULL DEFAULT 0,
    notify_ids       nvarchar(128) NOT NULL DEFAULT '',
    owner_id         bigint        NULL,
    description      nvarchar(256) NOT NULL DEFAULT '',
    ext_attrs        nvarchar(256) NULL     DEFAULT '',
    deleted          tinyint       NOT NULL DEFAULT 0,
@@ -1576,6 +1588,20 @@
GO
EXEC sp_addextendedproperty
     'MS_Description', N'通知告警场景配置id列表',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job',
     'COLUMN', N'notify_ids'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'负责人id',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job',
     'COLUMN', N'owner_id'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'描述',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_job',
@@ -1616,7 +1642,7 @@
     'TABLE', N'sj_job'
GO
INSERT INTO sj_job (namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, description, ext_attrs, deleted, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, N'testJobExecutor', 2, N'60', 1, 60, 3, 1, 1, 116, 0, N'', N'', 0, getdate(), getdate())
INSERT INTO sj_job (namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, description, ext_attrs, deleted, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, N'testJobExecutor', 2, N'60', 1, 60, 3, 1, 1, 116, 0, N'', 1, N'', N'', 0, getdate(), getdate())
GO
-- sj_job_log_message
@@ -2306,6 +2332,7 @@
    description      nvarchar(256) NOT NULL DEFAULT '',
    flow_info        nvarchar(max) NULL     DEFAULT NULL,
    wf_context       nvarchar(max) NULL     DEFAULT NULL,
    notify_ids       nvarchar(128) NOT NULL DEFAULT '',
    bucket_index     int           NOT NULL DEFAULT 0,
    version          int           NOT NULL,
    ext_attrs        nvarchar(256) NULL     DEFAULT '',
@@ -2409,6 +2436,13 @@
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow',
     'COLUMN', N'wf_context'
GO
EXEC sp_addextendedproperty
     'MS_Description', N'通知告警场景配置id列表',
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow',
     'COLUMN', N'notify_ids'
GO
EXEC sp_addextendedproperty
@@ -2742,4 +2776,3 @@
     'SCHEMA', N'dbo',
     'TABLE', N'sj_workflow_task_batch'
GO
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
@@ -2,7 +2,7 @@
(
    id                 bigint            NOT NULL,
    user_id            bigint            NOT NULL,
    tenant_id          nvarchar(20)      NULL,
    tenant_id          nvarchar(20)      DEFAULT ('000000') NULL,
    auth_id            nvarchar(255)     NOT NULL,
    source             nvarchar(255)     NOT NULL,
    open_id            nvarchar(255)     NULL,
@@ -175,7 +175,7 @@
    'COLUMN', N'oauth_token_secret'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_social',
    'COLUMN', N'del_flag'
@@ -210,7 +210,11 @@
    'TABLE', N'sys_social',
    'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
    'MS_Description', N'社会化关系表',
    'SCHEMA', N'dbo',
    'TABLE', N'sys_social'
GO
CREATE TABLE sys_tenant
(
@@ -326,7 +330,7 @@
    'COLUMN', N'status'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_tenant',
    'COLUMN', N'del_flag'
@@ -423,7 +427,7 @@
    'COLUMN', N'status'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_tenant_package',
    'COLUMN', N'del_flag'
@@ -1013,7 +1017,7 @@
    'COLUMN', N'status'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_dept',
    'COLUMN', N'del_flag'
@@ -1235,7 +1239,7 @@
GO
INSERT sys_dict_data VALUES (19, N'000000', 2, N'修改', N'2', N'sys_oper_type', N'', N'info', N'N', 103, 1, getdate(), NULL, NULL, N'修改操作')
GO
INSERT sys_dict_data VALUES (20, N'000000', 3, N'删除', N'3', N'sys_oper_type', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'删除操作')
INSERT sys_dict_data VALUES (20, N'000000', 3, N'删除', N3, N'sys_oper_type', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'删除操作')
GO
INSERT sys_dict_data VALUES (21, N'000000', 4, N'授权', N'4', N'sys_oper_type', N'', N'primary', N'N', 103, 1, getdate(), NULL, NULL, N'授权操作')
GO
@@ -2002,10 +2006,10 @@
    oper_url       nvarchar(255)  DEFAULT ''    NULL,
    oper_ip        nvarchar(128)  DEFAULT ''    NULL,
    oper_location  nvarchar(255)  DEFAULT ''    NULL,
    oper_param     nvarchar(2000) DEFAULT ''    NULL,
    json_result    nvarchar(2000) DEFAULT ''    NULL,
    oper_param     nvarchar(4000) DEFAULT ''    NULL,
    json_result    nvarchar(4000) DEFAULT ''    NULL,
    status         int            DEFAULT ((0)) NULL,
    error_msg      nvarchar(2000) DEFAULT ''    NULL,
    error_msg      nvarchar(4000) DEFAULT ''    NULL,
    oper_time      datetime2(7)                 NULL,
    cost_time      bigint         DEFAULT ((0)) NULL,
    CONSTRAINT PK__sys_oper__34723BF9BD954573 PRIMARY KEY CLUSTERED (oper_id)
@@ -2338,7 +2342,7 @@
    'COLUMN', N'status'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_role',
    'COLUMN', N'del_flag'
@@ -2475,6 +2479,10 @@
GO
INSERT sys_role_menu VALUES (3, 108);
GO
INSERT sys_role_menu VALUES (3, 118);
GO
INSERT sys_role_menu VALUES (3, 123);
GO
INSERT sys_role_menu VALUES (3, 500);
GO
INSERT sys_role_menu VALUES (3, 501);
@@ -2569,6 +2577,18 @@
GO
INSERT sys_role_menu VALUES (3, 1045);
GO
INSERT sys_role_menu VALUES (3, 1050);
GO
INSERT sys_role_menu VALUES (3, 1061);
GO
INSERT sys_role_menu VALUES (3, 1062);
GO
INSERT sys_role_menu VALUES (3, 1063);
GO
INSERT sys_role_menu VALUES (3, 1064);
GO
INSERT sys_role_menu VALUES (3, 1065);
GO
INSERT sys_role_menu VALUES (3, 1500);
GO
INSERT sys_role_menu VALUES (3, 1501);
@@ -2592,6 +2612,44 @@
INSERT sys_role_menu VALUES (3, 1510);
GO
INSERT sys_role_menu VALUES (3, 1511);
GO
INSERT sys_role_menu VALUES (3, 1600);
GO
INSERT sys_role_menu VALUES (3, 1601);
GO
INSERT sys_role_menu VALUES (3, 1602);
GO
INSERT sys_role_menu VALUES (3, 1603);
GO
INSERT sys_role_menu VALUES (3, 1620);
GO
INSERT sys_role_menu VALUES (3, 1621);
GO
INSERT sys_role_menu VALUES (3, 1622);
GO
INSERT sys_role_menu VALUES (3, 1623);
GO
INSERT sys_role_menu VALUES (3, 11618);
GO
INSERT sys_role_menu VALUES (3, 11619);
GO
INSERT sys_role_menu VALUES (3, 11629);
GO
INSERT sys_role_menu VALUES (3, 11632);
GO
INSERT sys_role_menu VALUES (3, 11633);
GO
INSERT sys_role_menu VALUES (3, 11638);
GO
INSERT sys_role_menu VALUES (3, 11639);
GO
INSERT sys_role_menu VALUES (3, 11640);
GO
INSERT sys_role_menu VALUES (3, 11641);
GO
INSERT sys_role_menu VALUES (3, 11642);
GO
INSERT sys_role_menu VALUES (3, 11643);
GO
INSERT sys_role_menu VALUES (4, 5);
GO
@@ -2723,7 +2781,7 @@
    'COLUMN', N'status'
GO
EXEC sys.sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)' ,
    'MS_Description', N'删除标志(0代表存在 1代表删除)' ,
    'SCHEMA', N'dbo',
    'TABLE', N'sys_user',
    'COLUMN', N'del_flag'
@@ -3115,7 +3173,7 @@
GO
INSERT INTO sys_oss_config VALUES (N'2', N'000000', N'qiniu', N'XXXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi',            N'', N's3-cn-north-1.qiniucs.com',         N'',N'N', N'',           N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
GO
INSERT INTO sys_oss_config VALUES (N'3', N'000000', N'aliyun', N'XXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi',            N'', N'oss-cn-beijing.aliyuncs.com',       N'',N'N', N'',           N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
INSERT INTO sys_oss_config VALUES (N3, N'000000', N'aliyun', N'XXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi',            N'', N'oss-cn-beijing.aliyuncs.com',       N'',N'N', N'',           N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
GO
INSERT INTO sys_oss_config VALUES (N'4', N'000000', N'qcloud', N'XXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi-1250000000', N'', N'cos.ap-beijing.myqcloud.com',       N'',N'N', N'ap-beijing', N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
GO
@@ -3202,7 +3260,7 @@
    'COLUMN', N'status'
GO
EXEC sp_addextendedproperty
    'MS_Description', N'删除标志(0代表存在 2代表删除)',
    'MS_Description', N'删除标志(0代表存在 1代表删除)',
    'SCHEMA', N'dbo',
    'TABLE', N'sys_client',
    'COLUMN', N'del_flag'
script/sql/sqlserver/sqlserver_ry_workflow.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1336 @@
CREATE TABLE flow_definition (
    id bigint NOT NULL,
    flow_code nvarchar(40) NOT NULL,
    flow_name nvarchar(100) NOT NULL,
    category nvarchar(100) NULL,
    version nvarchar(20) NOT NULL,
    is_publish tinyint DEFAULT('0') NULL,
    form_custom nchar(1) DEFAULT('N') NULL,
    form_path nvarchar(100) NULL,
    activity_status tinyint DEFAULT('1') NULL,
    listener_type nvarchar(100) NULL,
    listener_path nvarchar(400) NULL,
    ext nvarchar(500) NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_def__3213E83FEE39AE33 PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'flow_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'flow_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程类别',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'category'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程版本',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'version'
GO
EXEC sp_addextendedproperty
'MS_Description', N'是否发布(0未发布 1已发布 9失效)',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'is_publish'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单是否自定义(Y是 N否)',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'form_custom'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'form_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程激活状态(0挂起 1激活)',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'activity_status'
GO
EXEC sp_addextendedproperty
'MS_Description', N'监听器类型',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'listener_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'监听器路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'listener_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'业务详情 å­˜ä¸šåŠ¡è¡¨å¯¹è±¡json字符串',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'ext'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程定义表',
'SCHEMA', N'dbo',
'TABLE', N'flow_definition'
GO
CREATE TABLE flow_node (
    id bigint NOT NULL,
    node_type tinyint NOT NULL,
    definition_id bigint NOT NULL,
    node_code nvarchar(100) NOT NULL,
    node_name nvarchar(100) NULL,
    permission_flag nvarchar(200) NULL,
    node_ratio decimal(6,3)  NULL,
    coordinate nvarchar(100) NULL,
    skip_any_node nvarchar(100) DEFAULT('N') NULL,
    any_node_skip nvarchar(100) NULL,
    listener_type nvarchar(100) NULL,
    listener_path nvarchar(400) NULL,
    handler_type nvarchar(100) NULL,
    handler_path nvarchar(400) NULL,
    form_custom nchar(1) DEFAULT('N') NULL,
    form_path nvarchar(100) NULL,
    version nvarchar(20) NOT NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_nod__3213E83F372470DE PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程定义id',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'definition_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程节点编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程节点名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'node_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'权限标识(权限类型:权限标识,可以多个,用逗号隔开)',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'permission_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程签署比例值',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'node_ratio'
GO
EXEC sp_addextendedproperty
'MS_Description', N'坐标',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'coordinate'
GO
EXEC sp_addextendedproperty
'MS_Description', N'是否可以退回任意节点(Y是 N否)即将删除',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'skip_any_node'
GO
EXEC sp_addextendedproperty
'MS_Description', N'任意结点跳转',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'any_node_skip'
GO
EXEC sp_addextendedproperty
'MS_Description', N'监听器类型',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'listener_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'监听器路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'listener_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'处理器类型',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'handler_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'处理器路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'handler_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单是否自定义(Y是 N否)',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'form_custom'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'form_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'版本',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'version'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_node',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程节点表',
'SCHEMA', N'dbo',
'TABLE', N'flow_node'
GO
CREATE TABLE flow_skip (
    id bigint NOT NULL,
    definition_id bigint NOT NULL,
    now_node_code nvarchar(100) NOT NULL,
    now_node_type tinyint  NULL,
    next_node_code nvarchar(100) NOT NULL,
    next_node_type tinyint  NULL,
    skip_name nvarchar(100) NULL,
    skip_type nvarchar(40) NULL,
    skip_condition nvarchar(200) NULL,
    coordinate nvarchar(100) NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_ski__3213E83F073FEE6E PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程定义id',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'definition_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'当前流程节点的编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'now_node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'now_node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'下一个流程节点的编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'next_node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'next_node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'跳转名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'skip_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'跳转类型(PASS审批通过 REJECT退回)',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'skip_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'跳转条件',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'skip_condition'
GO
EXEC sp_addextendedproperty
'MS_Description', N'坐标',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'coordinate'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点跳转关联表',
'SCHEMA', N'dbo',
'TABLE', N'flow_skip'
GO
CREATE TABLE flow_instance (
    id bigint NOT NULL,
    definition_id bigint NOT NULL,
    business_id nvarchar(40) NOT NULL,
    node_type tinyint NOT NULL,
    node_code nvarchar(40) NOT NULL,
    node_name nvarchar(100) NULL,
    variable nvarchar(max) NULL,
    flow_status nvarchar(20) NOT NULL,
    activity_status tinyint DEFAULT('1') NULL,
    def_json nvarchar(max) NULL,
    create_by nvarchar(64) NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    ext nvarchar(500) NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_ins__3213E83F5190FEE1 PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
TEXTIMAGE_ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_definition表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'definition_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'业务id',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'business_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程节点编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程节点名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'node_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'任务变量',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'variable'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程状态(0待提交 1审批中 2 å®¡æ‰¹é€šè¿‡ 3自动通过 4终止 5作废 6撤销 7取回  8已完成 9已退回 10失效)',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'flow_status'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程激活状态(0挂起 1激活)',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'activity_status'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程定义json',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'def_json'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建者',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'create_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'扩展字段,预留给业务系统使用',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'ext'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程实例表',
'SCHEMA', N'dbo',
'TABLE', N'flow_instance'
GO
CREATE TABLE flow_task (
    id bigint NOT NULL,
    definition_id bigint NOT NULL,
    instance_id bigint NOT NULL,
    node_code nvarchar(100) NOT NULL,
    node_name nvarchar(100) NULL,
    node_type tinyint NOT NULL,
    form_custom nchar(1) DEFAULT('N') NULL,
    form_path nvarchar(100) NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_tas__3213E83F5AE1F1BA PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_definition表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'definition_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_instance表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'instance_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'node_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单是否自定义(Y是 N否)',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'form_custom'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'form_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_task',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'待办任务表',
'SCHEMA', N'dbo',
'TABLE', N'flow_task'
GO
CREATE TABLE flow_his_task (
    id bigint NOT NULL,
    definition_id bigint NOT NULL,
    instance_id bigint NOT NULL,
    task_id bigint NOT NULL,
    node_code nvarchar(200) NULL,
    node_name nvarchar(200) NULL,
    node_type tinyint  NULL,
    target_node_code nvarchar(100) NULL,
    target_node_name nvarchar(100) NULL,
    approver nvarchar(40) NULL,
    cooperate_type tinyint DEFAULT('0') NULL,
    collaborator nvarchar(40) NULL,
    skip_type nvarchar(10) NOT NULL,
    flow_status nvarchar(20) NOT NULL,
    form_custom nchar(1) DEFAULT('N') NULL,
    form_path nvarchar(100) NULL,
    message nvarchar(500) NULL,
    variable nvarchar(max) NULL,
    ext nvarchar(500) NULL,
    create_time datetime2(7)  NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_his__3213E83F67951564 PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_definition表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'definition_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_instance表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'instance_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'对应flow_task表的id',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'task_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'开始节点编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'开始节点名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'node_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'node_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'目标节点编码',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'target_node_code'
GO
EXEC sp_addextendedproperty
'MS_Description', N'结束节点名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'target_node_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批者',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'approver'
GO
EXEC sp_addextendedproperty
'MS_Description', N'协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'cooperate_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'协作人',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'collaborator'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流转类型(PASS通过 REJECT退回 NONE无动作)',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'skip_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程状态(1审批中 2 å®¡æ‰¹é€šè¿‡ 9已退回 10失效)',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'flow_status'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单是否自定义(Y是 N否)',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'form_custom'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批表单路径',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'form_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批意见',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'message'
GO
EXEC sp_addextendedproperty
'MS_Description', N'任务变量',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'variable'
GO
EXEC sp_addextendedproperty
'MS_Description', N'业务详情 å­˜ä¸šåŠ¡è¡¨å¯¹è±¡json字符串',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'ext'
GO
EXEC sp_addextendedproperty
'MS_Description', N'任务开始时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'审批完成时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'历史任务记录表',
'SCHEMA', N'dbo',
'TABLE', N'flow_his_task'
GO
CREATE TABLE flow_user (
    id bigint NOT NULL,
    type nchar(1) NOT NULL,
    processed_by nvarchar(80) NULL,
    associated bigint NOT NULL,
    create_time datetime2(7)  NULL,
    create_by nvarchar(80) NULL,
    update_time datetime2(7)  NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    tenant_id nvarchar(40) NULL,
    CONSTRAINT PK__flow_use__3213E83FFA38CA8B PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX user_processed_type ON flow_user (processed_by ASC, type ASC)
GO
EXEC sp_addextendedproperty
'MS_Description', N'主键id',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'权限人',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'processed_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'任务表id',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'associated'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建人',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'create_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户id',
'SCHEMA', N'dbo',
'TABLE', N'flow_user',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程用户表',
'SCHEMA', N'dbo',
'TABLE', N'flow_user'
GO
CREATE TABLE flow_category (
    category_id bigint NOT NULL,
    tenant_id nvarchar(20) DEFAULT('000000') NULL,
    parent_id bigint  DEFAULT(0) NULL,
    ancestors nvarchar(500) DEFAULT('') NULL,
    category_name nvarchar(30) NOT NULL,
    order_num int  DEFAULT(0) NULL,
    del_flag nchar(1) DEFAULT('0') NULL,
    create_dept bigint  NULL,
    create_by bigint  NULL,
    create_time datetime2(7)  NULL,
    update_by bigint  NULL,
    update_time datetime2(7)  NULL,
    CONSTRAINT PK__flow_cat__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (category_id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程分类ID',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'category_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户编号',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'父流程分类id',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'parent_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'祖级列表',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'ancestors'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程分类名称',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'category_name'
GO
EXEC sp_addextendedproperty
'MS_Description', N'显示顺序',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'order_num'
GO
EXEC sp_addextendedproperty
'MS_Description', N'删除标志(0代表存在 1代表删除)',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'del_flag'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建部门',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'create_dept'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建者',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'create_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新者',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'update_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'flow_category',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'流程分类',
'SCHEMA', N'dbo',
'TABLE', N'flow_category'
GO
INSERT flow_category VALUES (100, N'000000', 0, N'0', N'OA审批', 0, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (101, N'000000', 100, N'0,100', N'假勤管理', 0, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (102, N'000000', 100, N'0,100', N'人事管理', 1, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (103, N'000000', 101, N'0,100,101', N'请假', 0, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (104, N'000000', 101, N'0,100,101', N'出差', 1, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (105, N'000000', 101, N'0,100,101', N'加班', 2, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (106, N'000000', 101, N'0,100,101', N'换班', 3, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (107, N'000000', 101, N'0,100,101', N'外出', 4, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (108, N'000000', 102, N'0,100,102', N'转正', 1, N'0', 103, 1, getdate(), NULL, NULL);
GO
INSERT flow_category VALUES (109, N'000000', 102, N'0,100,102', N'离职', 2, N'0', 103, 1, getdate(), NULL, NULL);
GO
CREATE TABLE test_leave (
    id bigint NOT NULL,
    tenant_id nvarchar(20) DEFAULT('000000') NULL,
    leave_type nvarchar(255) NOT NULL,
    start_date datetime2(7) NOT NULL,
    end_date datetime2(7) NOT NULL,
    leave_days int NOT NULL,
    remark nvarchar(255) NULL,
    status nvarchar(255) NULL,
    create_dept bigint  NULL,
    create_by bigint  NULL,
    create_time datetime2(7)  NULL,
    update_by bigint  NULL,
    update_time datetime2(7)  NULL,
    CONSTRAINT PK__test_lea__3213E83F348788FA PRIMARY KEY CLUSTERED (id)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
    ON [PRIMARY]
)
ON [PRIMARY]
GO
EXEC sp_addextendedproperty
'MS_Description', N'id',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'租户编号',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'tenant_id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'请假类型',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'leave_type'
GO
EXEC sp_addextendedproperty
'MS_Description', N'开始时间',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'start_date'
GO
EXEC sp_addextendedproperty
'MS_Description', N'结束时间',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'end_date'
GO
EXEC sp_addextendedproperty
'MS_Description', N'请假天数',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'leave_days'
GO
EXEC sp_addextendedproperty
'MS_Description', N'请假原因',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'remark'
GO
EXEC sp_addextendedproperty
'MS_Description', N'状态',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'status'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建部门',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'create_dept'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建者',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'create_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'创建时间',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'create_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新者',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'update_by'
GO
EXEC sp_addextendedproperty
'MS_Description', N'更新时间',
'SCHEMA', N'dbo',
'TABLE', N'test_leave',
'COLUMN', N'update_time'
GO
EXEC sp_addextendedproperty
'MS_Description', N'请假申请表',
'SCHEMA', N'dbo',
'TABLE', N'test_leave'
GO
INSERT sys_menu VALUES (11616, N'工作流', 0, 6, N'workflow', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'workflow', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11618, N'我的任务', 0, 7, N'task', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'my-task', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11619, N'我的待办', 11618, 2, N'taskWaiting', N'workflow/task/taskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11632, N'我的已办', 11618, 3, N'taskFinish', N'workflow/task/taskFinish', N'', 1, 1, N'C', N'0', N'0', N'', N'finish', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11633, N'我的抄送', 11618, 4, N'taskCopyList', N'workflow/task/taskCopyList', N'', 1, 1, N'C', N'0', N'0', N'', N'my-copy', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11620, N'流程定义', 11616, 3, N'processDefinition', N'workflow/processDefinition/index', N'', 1, 1, N'C', N'0', N'0', N'', N'process-definition', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11621, N'流程实例', 11630, 1, N'processInstance', N'workflow/processInstance/index', N'', 1, 1, N'C', N'0', N'0', N'', N'tree-table', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11622, N'流程分类', 11616, 1, N'category', N'workflow/category/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:category:list', N'category', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11629, N'我发起的', 11618, 1, N'myDocument', N'workflow/task/myDocument', N'', 1, 1, N'C', N'0', N'0', N'', N'guide', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11630, N'流程监控', 11616, 4, N'monitor', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'monitor', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11631, N'待办任务', 11630, 2, N'allTaskWaiting', N'workflow/task/allTaskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N'');
GO
-- æµç¨‹åˆ†ç±»ç®¡ç†ç›¸å…³æŒ‰é’®
INSERT sys_menu VALUES (11623, N'流程分类查询', 11622, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11624, N'流程分类新增', 11622, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11625, N'流程分类修改', 11622, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11626, N'流程分类删除', 11622, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11627, N'流程分类导出', 11622, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
-- è¯·å‡æµ‹è¯•相关按钮
INSERT sys_menu VALUES (11638, N'请假申请', 5, 1, N'leave', N'workflow/leave/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:leave:list', N'#', 103, 1, GETDATE(), NULL, NULL, N'请假申请菜单');
GO
INSERT sys_menu VALUES (11639, N'请假申请查询', 11638, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11640, N'请假申请新增', 11638, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11641, N'请假申请修改', 11638, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11642, N'请假申请删除', 11638, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
GO
INSERT sys_menu VALUES (11643, N'请假申请导出', 11638, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
INSERT sys_dict_type VALUES (13, N'000000', N'业务状态', N'wf_business_status', 103, 1, GETDATE(), NULL, NULL, N'业务状态列表');
GO
INSERT sys_dict_type VALUES (14, N'000000', N'表单类型', N'wf_form_type', 103, 1, GETDATE(), NULL, NULL, N'表单类型列表');
GO
INSERT sys_dict_type VALUES (15, N'000000', N'任务状态', N'wf_task_status', 103, 1, GETDATE(), NULL, NULL, N'任务状态');
GO
INSERT sys_dict_data VALUES (39, N'000000', 1, N'已撤销', N'cancel', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'已撤销');
GO
INSERT sys_dict_data VALUES (40, N'000000', 2, N'草稿', N'draft', N'wf_business_status', N'', N'info', N'N', 103, 1, GETDATE(), NULL, NULL, N'草稿');
GO
INSERT sys_dict_data VALUES (41, N'000000', 3, N'待审核', N'waiting', N'wf_business_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'待审核');
GO
INSERT sys_dict_data VALUES (42, N'000000', 4, N'已完成', N'finish', N'wf_business_status', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'已完成');
GO
INSERT sys_dict_data VALUES (43, N'000000', 5, N'已作废', N'invalid', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'已作废');
GO
INSERT sys_dict_data VALUES (44, N'000000', 6, N'已退回', N'back', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'已退回');
GO
INSERT sys_dict_data VALUES (45, N'000000', 7, N'已终止', N'termination', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'已终止');
GO
INSERT sys_dict_data VALUES (46, N'000000', 1, N'自定义表单', N'static', N'wf_form_type', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'自定义表单');
GO
INSERT sys_dict_data VALUES (47, N'000000', 2, N'动态表单', N'dynamic', N'wf_form_type', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'动态表单');
GO
INSERT sys_dict_data VALUES (48, N'000000', 1, N'撤销', N'cancel', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'撤销');
GO
INSERT sys_dict_data VALUES (49, N'000000', 2, N'通过', N'pass', N'wf_task_status', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'通过');
GO
INSERT sys_dict_data VALUES (50, N'000000', 3, N'待审核', N'waiting', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'待审核');
GO
INSERT sys_dict_data VALUES (51, N'000000', 4, N'作废', N'invalid', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'作废');
GO
INSERT sys_dict_data VALUES (52, N'000000', 5, N'退回', N'back', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'退回');
GO
INSERT sys_dict_data VALUES (53, N'000000', 6, N'终止', N'termination', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'终止');
GO
INSERT sys_dict_data VALUES (54, N'000000', 7, N'转办', N'transfer', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'转办');
GO
INSERT sys_dict_data VALUES (55, N'000000', 8, N'委托', N'depute', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'委托');
GO
INSERT sys_dict_data VALUES (56, N'000000', 9, N'抄送', N'copy', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'抄送');
GO
INSERT sys_dict_data VALUES (57, N'000000', 10, N'加签', N'sign', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'加签');
GO
INSERT sys_dict_data VALUES (58, N'000000', 11, N'减签', N'sign_off', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'减签');
GO
INSERT sys_dict_data VALUES (59, N'000000', 11, N'超时', N'timeout', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'超时');
GO