办学质量监测教学评价系统
Flex
14 小时以前 18d57a650e29bbadefbca3022192fcac93c438ec
Merge branch 'main' of http://yykjgit.sdyyst.com/r/school-ai
已修改16个文件
已添加125个文件
13628 ■■■■■ 文件已修改
pom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/pom.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/pom.xml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java 405 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java 706 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java 374 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java 364 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java 1037 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java 664 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/pom.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerTemplateController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplate.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerTemplateMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerTemplateService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerTemplateServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerTemplateMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/sc-services/pom.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/package.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/template/index.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/api/tool/template/model.d.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/bootstrap.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/services/flowableService.js 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/data.ts 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/index.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/serivceDialog.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/system/process/index.vue 472 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/data.tsx 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/index.vue 432 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/tool/template/template-drawer.vue 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -308,7 +308,16 @@
                <artifactId>ruoyi-chat</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>sc-services</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>sc-page-designer</artifactId>
                <version>${revision}</version>
            </dependency>
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>ruoyi-knowledge-api</artifactId>
ruoyi-admin/pom.xml
@@ -61,6 +61,16 @@
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>sc-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>sc-page-designer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-generator</artifactId>
        </dependency>
ruoyi-admin/src/main/resources/application-dev.yml
@@ -89,9 +89,16 @@
  # è…¾è®¯äº‘ sms.tencentcloudapi.com
  endpoint: "dysmsapi.aliyuncs.com"
  accessKeyId: xxxxxxx
  accessKeySecret: xxxxxx
  accessKeySecret: xxxxxxq
  signName: æµ‹è¯•
  # è…¾è®¯ä¸“用
  sdkAppId:
# ===============================
# Flowable é…ç½®ï¼ˆé›†æˆ Flowable æ—¶å¯ç”¨ï¼‰
# ===============================
flowable:
  database-schema-update: true   # é¦–次运行建议为 true,后期设为 false
  async-executor-activate: false
  id-generator: simple
ruoyi-admin/src/main/resources/application.yml
@@ -314,5 +314,4 @@
              url: http://127.0.0.1:8081
        stdio:
          servers-configuration: classpath:mcp-server.json
        request-timeout: 300s
        request-timeout: 300s
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java
@@ -1,5 +1,6 @@
package org.ruoyi.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -16,7 +17,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_session")
@TableName("CHAT_SESSION")
public class ChatSession extends BaseEntity {
    @Serial
@@ -25,28 +26,30 @@
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    @TableId(value = "ID")
    private Long id;
    /**
     * ç”¨æˆ·id
     * ç”¨æˆ·ID
     */
    @TableField("USER_ID")
    private Long userId;
    /**
     * ä¼šè¯æ ‡é¢˜
     */
    @TableField("SESSION_TITLE")
    private String sessionTitle;
    /**
     * ä¼šè¯å†…容
     */
    @TableField("SESSION_CONTENT")
    private String sessionContent;
    /**
     * å¤‡æ³¨
     */
    @TableField("REMARK")
    private String remark;
}
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java
@@ -17,7 +17,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_info")
@TableName("KNOWLEDGE_INFO")
public class KnowledgeInfo extends BaseEntity {
    @Serial
@@ -27,6 +27,7 @@
     * ä¸»é”®ID
     */
    @TableId(value = "ID")
    @TableField("ID")
    private Long id;
    /**
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java
@@ -162,4 +162,8 @@
     *
     */
    void updateXcxUser(SysUserBo user);
    List<SysUser> selectUserByRoleIds(List<Long> groups);
    List<SysUser> selectUserByDeptIds(List<Long> groups);
}
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java
@@ -1,5 +1,6 @@
package org.ruoyi.system.service;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.system.domain.SysUser;
@@ -227,4 +228,7 @@
     */
    int deleteUserByIds(Long[] userIds);
    R<List<SysUser>> selectUserByRoleIds(List<Long> groups);
    R<List<SysUser>> selectUserByDeptIds(List<Long> groups);
}
ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -13,6 +13,7 @@
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.CacheNames;
import org.ruoyi.common.core.constant.UserConstants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.service.UserService;
import org.ruoyi.common.core.utils.MapstructUtils;
@@ -564,4 +565,19 @@
                .eq(SysUser::getUserName, userName));
        return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserBalance().toString();
    }
    /**
     * æ ¹æ®è§’色组查询用户
     */
    @Override
    public R<List<SysUser>> selectUserByRoleIds(List<Long> groups) {
        List<SysUser> list = baseMapper.selectUserByRoleIds(groups);
        return R.ok(list);
    }
    @Override
    public R<List<SysUser>> selectUserByDeptIds(List<Long> groups) {
        List<SysUser> list = baseMapper.selectUserByDeptIds(groups);
        return R.ok(list);
    }
}
ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml
@@ -143,6 +143,18 @@
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.user_id = #{userId}
    </select>
    <select id="selectUserByRoleIds" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and r.role_id in <foreach item="item" collection="list" separator="," open="(" close=")" index="">
            #{item}
        </foreach>
    </select>
    <select id="selectUserByDeptIds" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0' and u.dept_id in <foreach item="item" collection="list" separator="," open="(" close=")" index="">
            #{item}
        </foreach>
    </select>
</mapper>
ruoyi-modules/pom.xml
@@ -21,6 +21,9 @@
        <module>ruoyi-chat</module>
        <module>ruoyi-system</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-flowable</module>
        <module>sc-services</module>
        <module>sc-page-designer</module>
    </modules>
    <properties>
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -177,7 +177,8 @@
  @Transactional(rollbackFor = Exception.class)
  public void removeKnowledge(String id) {
    Map<String,Object> map = new HashMap<>();
    KnowledgeInfo knowledgeInfo = baseMapper.selectById(id);
    KnowledgeInfo knowledgeInfo = baseMapper.selectOne(new LambdaQueryWrapper<KnowledgeInfo>()
            .eq(KnowledgeInfo::getKid, id));
    check(knowledgeInfo);
    map.put("kid",knowledgeInfo.getKid());
    // åˆ é™¤å‘量数据
ruoyi-modules/ruoyi-flowable/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>ruoyi-flowable</artifactId>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <flowable.version>6.8.0</flowable.version>
    </properties>
    <dependencies>
        <!-- Flowable å·¥ä½œæµ -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-process</artifactId>
            <version>${flowable.version}</version>
        </dependency>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.googlecode.aviator</groupId>
            <artifactId>aviator</artifactId>
            <version>5.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-system-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>oauth2-oidc-sdk</artifactId>
            <version>11.10.1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package org.ruoyi.flowable.common.constant;
/**
 * æµç¨‹å¸¸é‡ä¿¡æ¯
 *
 * @author Xuan xuan
 * @date 2021/4/17 22:46
 */
public class ProcessConstants {
    public static final String SUFFIX = ".bpmn";
    /**
     * åŠ¨æ€æ•°æ®
     */
    public static final String DATA_TYPE = "dynamic";
    /**
     * å•个审批人
     */
    public static final String USER_TYPE_ASSIGNEE = "assignee";
    /**
     * å€™é€‰äºº
     */
    public static final String USER_TYPE_USERS = "candidateUsers";
    /**
     * å®¡æ‰¹ç»„
     */
    public static final String USER_TYPE_ROUPS = "candidateGroups";
    /**
     * å•个审批人
     */
    public static final String PROCESS_APPROVAL = "approval";
    /**
     * ä¼šç­¾äººå‘˜
     */
    public static final String PROCESS_MULTI_INSTANCE_USER = "userList";
    /**
     * nameapace
     */
    public static final String NAMASPASE = "http://flowable.org/bpmn";
    /**
     * ä¼šç­¾èŠ‚ç‚¹
     */
    public static final String PROCESS_MULTI_INSTANCE = "multiInstance";
    /**
     * è‡ªå®šä¹‰å±žæ€§ dataType
     */
    public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType";
    /**
     * è‡ªå®šä¹‰å±žæ€§ userType
     */
    public static final String PROCESS_CUSTOM_USER_TYPE = "userType";
    /**
     * è‡ªå®šä¹‰å±žæ€§ localScope
     */
    public static final String PROCESS_FORM_LOCAL_SCOPE = "localScope";
    /**
     * è‡ªå®šä¹‰å±žæ€§ æµç¨‹çŠ¶æ€
     */
    public static final String PROCESS_STATUS_KEY = "processStatus";
    /**
     * æµç¨‹è·³è¿‡
     */
    public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package org.ruoyi.flowable.common.constant;
/**
 * @author konbai
 * @createTime 2022/4/24 13:24
 */
public class TaskConstants {
    /**
     * æµç¨‹å‘起人
     */
    public static final String PROCESS_INITIATOR = "initiator";
    /**
     * è§’色候选组前缀
     */
    public static final String ROLE_GROUP_PREFIX = "ROLE";
    /**
     * éƒ¨é—¨å€™é€‰ç»„前缀
     */
    public static final String DEPT_GROUP_PREFIX = "DEPT";
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package org.ruoyi.flowable.common.enums;
/**
 * æµç¨‹æ„è§ç±»åž‹
 *
 * @author Xuan xuan
 * @date 2021/4/19
 */
public enum FlowComment {
    /**
     * è¯´æ˜Ž
     */
    NORMAL("1", "正常"),
    REBACK("2", "退回"),
    REJECT("3", "驳回"),
    DELEGATE("4", "委派"),
    TRANSFER("5", "转办"),
    STOP("6", "终止"),
    REVOKE("7", "撤回");
    /**
     * ç±»åž‹
     */
    private final String type;
    /**
     * è¯´æ˜Ž
     */
    private final String remark;
    FlowComment(String type, String remark) {
        this.type = type;
        this.remark = remark;
    }
    public String getType() {
        return type;
    }
    public String getRemark() {
        return remark;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package org.ruoyi.flowable.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * @author KonBAI
 * @createTime 2022/6/28 9:51
 */
@Getter
@AllArgsConstructor
public enum FormType {
    /**
     * æµç¨‹è¡¨å•
     */
    PROCESS(0),
    /**
     * å¤–置表单
     */
    EXTERNAL(1),
    /**
     * èŠ‚ç‚¹ç‹¬ç«‹è¡¨å•
     */
    INDEPENDENT(2);
    /**
     * è¡¨å•类型
     */
    private final Integer type;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package org.ruoyi.flowable.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.ruoyi.common.core.utils.StringUtils;
/**
 * @author konbai
 * @since 2023/3/9 00:45
 */
@Getter
@AllArgsConstructor
public enum ProcessStatus {
    /**
     * è¿›è¡Œä¸­ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     */
    RUNNING("running"),
    /**
     * å·²ç»ˆæ­¢
     */
    TERMINATED("terminated"),
    /**
     * å·²å®Œæˆ
     */
    COMPLETED("completed"),
    /**
     * å·²å–消
     */
    CANCELED("canceled");
    private final String status;
    public static ProcessStatus getProcessStatus(String str) {
        if (StringUtils.isNotBlank(str)) {
            for (ProcessStatus value : values()) {
                if (StringUtils.equalsIgnoreCase(str, value.getStatus())) {
                    return value;
                }
            }
        }
        return null;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.ruoyi.flowable.config;
import lombok.AllArgsConstructor;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.RuntimeService;
import org.ruoyi.flowable.listener.GlobalEventListener;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
/**
 * flowable全局监听配置
 *
 * @author ssc
 */
@Configuration
@AllArgsConstructor
public class GlobalEventListenerConfig implements ApplicationListener<ContextRefreshedEvent> {
    private final GlobalEventListener globalEventListener;
    private final RuntimeService runtimeService;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // æµç¨‹æ­£å¸¸ç»“束
        runtimeService.addEventListener(globalEventListener, FlowableEngineEventType.PROCESS_COMPLETED);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package org.ruoyi.flowable.config;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
/**
 * @author XuanXuan
 * @date 2021-04-03
 */
public class MyDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
    //设置高亮线的颜色  è¿™é‡Œæˆ‘设置成绿色
    protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN;
    public MyDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }
    public MyDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
        super(width, height, minX, minY, imageType);
    }
    /**
     * ç”»çº¿é¢œè‰²è®¾ç½®
     */
    @Override
    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType,
                               AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(CONNECTION_COLOR);
        if (connectionType.equals("association")) {
            g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            //设置线的颜色
            g.setPaint(originalPaint);
            g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }
        for (int i = 1; i < xPoints.length; i++) {
            Integer sourceX = xPoints[i - 1];
            Integer sourceY = yPoints[i - 1];
            Integer targetX = xPoints[i];
            Integer targetY = yPoints[i];
            Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
            g.draw(line);
        }
        if (isDefault) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }
        if (conditional) {
            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }
        if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
            drawArrowHead(line, scaleFactor);
        }
        if (associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
            drawArrowHead(line, scaleFactor);
        }
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * é«˜äº®èŠ‚ç‚¹è®¾ç½®
     */
    @Override
    public void drawHighLight(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        //设置高亮节点的颜色
        g.setPaint(HIGHLIGHT_COLOR);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package org.ruoyi.flowable.config;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
 * mybatis-plus配置类(下方注释有插件介绍)
 *
 * @author Lion Li
 */
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // åˆ†é¡µæ’ä»¶
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // ä¹è§‚锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        return interceptor;
    }
    /**
     * åˆ†é¡µæ’件,自动识别数据库类型
     */
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // è®¾ç½®æœ€å¤§å•页限制数量,默认 500 æ¡ï¼Œ-1 ä¸å—限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        // åˆ†é¡µåˆç†åŒ–
        paginationInnerInterceptor.setOverflow(true);
        return paginationInnerInterceptor;
    }
    /**
     * ä¹è§‚锁插件
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }
    /**
     * ä½¿ç”¨ç½‘卡信息绑定雪花生成器
     * é˜²æ­¢é›†ç¾¤é›ªèбID重复
     */
    @Bean
    public IdentifierGenerator idGenerator() {
        return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
    }
    /**
     * PaginationInnerInterceptor åˆ†é¡µæ’件,自动识别数据库类型
     * https://baomidou.com/pages/97710a/
     * OptimisticLockerInnerInterceptor ä¹è§‚锁插件
     * https://baomidou.com/pages/0d93c0/
     * MetaObjectHandler å…ƒå¯¹è±¡å­—段填充控制器
     * https://baomidou.com/pages/4c6bcf/
     * ISqlInjector sql注入器
     * https://baomidou.com/pages/42ea4a/
     * BlockAttackInnerInterceptor å¦‚果是对全表的删除或更新操作,就会终止该操作
     * https://baomidou.com/pages/f9a237/
     * IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截)
     * IdentifierGenerator è‡ªå®šä¹‰ä¸»é”®ç­–ç•¥
     * https://baomidou.com/pages/568eb2/
     * TenantLineInnerInterceptor å¤šç§Ÿæˆ·æ’ä»¶
     * https://baomidou.com/pages/aef2f2/
     * DynamicTableNameInnerInterceptor åŠ¨æ€è¡¨åæ’ä»¶
     * https://baomidou.com/pages/2a45ff/
     */
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package org.ruoyi.flowable.core;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
 * è¡¨å•属性类
 *
 * @author KonBAI
 * @createTime 2022/8/6 18:54
 */
@Data
public class FormConf {
    /**
     * æ ‡é¢˜
     */
    private String title;
    /**
     * è¡¨å•名
     */
    private String formRef;
    /**
     * è¡¨å•模型
     */
    private Map<String, Object>  formModel;
    /**
     * è¡¨å•尺寸
     */
    private String size;
    /**
     * æ ‡ç­¾å¯¹é½
     */
    private String labelPosition;
    /**
     * æ ‡ç­¾å®½åº¦
     */
    private Integer labelWidth;
    /**
     * æ ¡éªŒæ¨¡åž‹
     */
    private String formRules;
    /**
     * æ …格间隔
     */
    private Integer gutter;
    /**
     * ç¦ç”¨è¡¨å•
     */
    private Boolean disabled = false;
    /**
     * æ …格占据的列数
     */
    private Integer span;
    /**
     * è¡¨å•按钮
     */
    private Boolean formBtns = true;
    /**
     * è¡¨å•项
     */
    private List<Map<String, Object>> fields;
    /**
     * è¡¨å•数据
     */
    private Map<String, Object> formData;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package org.ruoyi.flowable.core.domain;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
 * æµç¨‹æŸ¥è¯¢å®žä½“对象
 *
 * @author KonBAI
 * @createTime 2022/6/11 01:15
 */
@Data
public class ProcessQuery {
    /**
     * æµç¨‹æ ‡è¯†
     */
    private String processKey;
    /**
     * æµç¨‹åç§°
     */
    private String processName;
    /**
     * æµç¨‹åˆ†ç±»
     */
    private String category;
    /**
     * çŠ¶æ€
     */
    private String state;
    /**
     * è¯·æ±‚参数
     */
    private Map<String, Object> params = new HashMap<>();
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
package org.ruoyi.flowable.core.domain.model;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.core.utils.sql.SqlUtil;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
 * åˆ†é¡µæŸ¥è¯¢å®žä½“ç±»
 *
 * @author Lion Li
 */
@Data
public class PageQuery implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * åˆ†é¡µå¤§å°
     */
    private Integer pageSize;
    /**
     * å½“前页数
     */
    private Integer pageNum;
    /**
     * æŽ’序列
     */
    private String orderByColumn;
    /**
     * æŽ’序的方向desc或者asc
     */
    private String isAsc;
    /**
     * å½“前记录起始索引 é»˜è®¤å€¼
     */
    public static final int DEFAULT_PAGE_NUM = 1;
    /**
     * æ¯é¡µæ˜¾ç¤ºè®°å½•æ•° é»˜è®¤å€¼ é»˜è®¤æŸ¥å…¨éƒ¨
     */
    public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
    public <T> Page<T> build() {
        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
        if (pageNum <= 0) {
            pageNum = DEFAULT_PAGE_NUM;
        }
        Page<T> page = new Page<>(pageNum, pageSize);
        List<OrderItem> orderItems = buildOrderItem();
        if (CollUtil.isNotEmpty(orderItems)) {
            page.addOrder(orderItems);
        }
        return page;
    }
    /**
     * æž„建排序
     *
     * æ”¯æŒçš„用法如下:
     * {isAsc:"asc",orderByColumn:"id"} order by id asc
     * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
     * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
     * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
     */
    private List<OrderItem> buildOrderItem() {
        if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
            return null;
        }
        String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
        orderBy = StringUtils.toUnderScoreCase(orderBy);
        // å…¼å®¹å‰ç«¯æŽ’序类型
        isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
        String[] orderByArr = orderBy.split(",");
        String[] isAscArr = isAsc.split(",");
        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
            throw new ServiceException("排序参数有误");
        }
        List<OrderItem> list = new ArrayList<>();
        // æ¯ä¸ªå­—段各自排序
        for (int i = 0; i < orderByArr.length; i++) {
            String orderByStr = orderByArr[i];
            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
            if ("asc".equals(isAscStr)) {
                list.add(OrderItem.asc(orderByStr));
            } else if ("desc".equals(isAscStr)) {
                list.add(OrderItem.desc(orderByStr));
            } else {
                throw new ServiceException("排序参数有误");
            }
        }
        return list;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,192 @@
package org.ruoyi.flowable.core.mapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.ruoyi.flowable.utils.BeanCopyUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * è‡ªå®šä¹‰ Mapper æŽ¥å£, å®žçް è‡ªå®šä¹‰æ‰©å±•
 *
 * @param <M> mapper æ³›åž‹
 * @param <T> table æ³›åž‹
 * @param <V> vo æ³›åž‹
 * @author Lion Li
 * @since 2021-05-13
 */
@SuppressWarnings("unchecked")
public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
    Log log = LogFactory.getLog(BaseMapperPlus.class);
    default Class<V> currentVoClass() {
        return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
    }
    default Class<T> currentModelClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
    }
    default Class<M> currentMapperClass() {
        return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
    }
    default List<T> selectList() {
        return this.selectList(new QueryWrapper<>());
    }
    /**
     * æ‰¹é‡æ’å…¥
     */
    default boolean insertBatch(Collection<T> entityList) {
        return Db.saveBatch(entityList);
    }
    /**
     * æ‰¹é‡æ›´æ–°
     */
    default boolean updateBatchById(Collection<T> entityList) {
        return Db.updateBatchById(entityList);
    }
    /**
     * æ‰¹é‡æ’入或更新
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList) {
        return Db.saveOrUpdateBatch(entityList);
    }
    /**
     * æ‰¹é‡æ’å…¥(包含限制条数)
     */
    default boolean insertBatch(Collection<T> entityList, int batchSize) {
        return Db.saveBatch(entityList, batchSize);
    }
    /**
     * æ‰¹é‡æ›´æ–°(包含限制条数)
     */
    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
        return Db.updateBatchById(entityList, batchSize);
    }
    /**
     * æ‰¹é‡æ’入或更新(包含限制条数)
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
        return Db.saveOrUpdateBatch(entityList, batchSize);
    }
    /**
     * æ’入或更新(包含限制条数)
     */
    default boolean insertOrUpdate(T entity) {
        return Db.saveOrUpdate(entity);
    }
    default V selectVoById(Serializable id) {
        return selectVoById(id, this.currentVoClass());
    }
    /**
     * æ ¹æ® ID æŸ¥è¯¢
     */
    default <C> C selectVoById(Serializable id, Class<C> voClass) {
        T obj = this.selectById(id);
        if (ObjectUtil.isNull(obj)) {
            return null;
        }
        return BeanCopyUtils.copy(obj, voClass);
    }
    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
        return selectVoBatchIds(idList, this.currentVoClass());
    }
    /**
     * æŸ¥è¯¢ï¼ˆæ ¹æ®ID æ‰¹é‡æŸ¥è¯¢ï¼‰
     */
    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
        List<T> list = this.selectBatchIds(idList);
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        return BeanCopyUtils.copyList(list, voClass);
    }
    default List<V> selectVoByMap(Map<String, Object> map) {
        return selectVoByMap(map, this.currentVoClass());
    }
    /**
     * æŸ¥è¯¢ï¼ˆæ ¹æ® columnMap æ¡ä»¶ï¼‰
     */
    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
        List<T> list = this.selectByMap(map);
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        return BeanCopyUtils.copyList(list, voClass);
    }
    default V selectVoOne(Wrapper<T> wrapper) {
        return selectVoOne(wrapper, this.currentVoClass());
    }
    /**
     * æ ¹æ® entity æ¡ä»¶ï¼ŒæŸ¥è¯¢ä¸€æ¡è®°å½•
     */
    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
        T obj = this.selectOne(wrapper);
        if (ObjectUtil.isNull(obj)) {
            return null;
        }
        return BeanCopyUtils.copy(obj, voClass);
    }
    default List<V> selectVoList(Wrapper<T> wrapper) {
        return selectVoList(wrapper, this.currentVoClass());
    }
    /**
     * æ ¹æ® entity æ¡ä»¶ï¼ŒæŸ¥è¯¢å…¨éƒ¨è®°å½•
     */
    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
        List<T> list = this.selectList(wrapper);
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        return BeanCopyUtils.copyList(list, voClass);
    }
    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
        return selectVoPage(page, wrapper, this.currentVoClass());
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢VO
     */
    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
        IPage<T> pageData = this.selectPage(page, wrapper);
        IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
        if (CollUtil.isEmpty(pageData.getRecords())) {
            return (P) voPage;
        }
        voPage.setRecords(BeanCopyUtils.copyList(pageData.getRecords(), voClass));
        return (P) voPage;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package org.ruoyi.flowable.core.page;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
 * è¡¨æ ¼åˆ†é¡µæ•°æ®å¯¹è±¡
 *
 * @author Lion Li
 */
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ€»è®°å½•æ•°
     */
    private long total;
    /**
     * åˆ—表数据
     */
    private List<T> rows;
    /**
     * æ¶ˆæ¯çŠ¶æ€ç 
     */
    private int code;
    /**
     * æ¶ˆæ¯å†…容
     */
    private String msg;
    /**
     * åˆ†é¡µ
     *
     * @param list  åˆ—表数据
     * @param total æ€»è®°å½•æ•°
     */
    public TableDataInfo(List<T> list, long total) {
        this.rows = list;
        this.total = total;
    }
    public static <T> TableDataInfo<T> build(IPage<T> page) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        rspData.setRows(page.getRecords());
        rspData.setTotal(page.getTotal());
        return rspData;
    }
    public static <T> TableDataInfo<T> build(List<T> list) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(list.size());
        return rspData;
    }
    public static <T> TableDataInfo<T> build() {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        return rspData;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package org.ruoyi.flowable.factory;
import jakarta.annotation.Resource;
import lombok.Getter;
import org.flowable.engine.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
/**
 * flowable å¼•擎注入封装
 * @author XuanXuan
 * @date 2021-04-03
 */
@Component
@Getter
public class FlowServiceFactory {
    @Resource
    protected RepositoryService repositoryService;
    @Resource
    protected RuntimeService runtimeService;
    @Resource
    protected IdentityService identityService;
    @Resource
    protected TaskService taskService;
    @Resource
    protected FormService formService;
    @Resource
    protected HistoryService historyService;
    @Resource
    protected ManagementService managementService;
    @Qualifier("processEngine")
    @Resource
    protected ProcessEngine processEngine;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,370 @@
package org.ruoyi.flowable.flow;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.bpmn.model.GraphicInfo;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import org.flowable.image.util.ReflectUtil;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
/**
 * @author XuanXuan
 * @date 2021/4/4 23:58
 */
public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
    //定义走过流程连线颜色为绿色
    protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN;
    //设置未走过流程的连接线颜色
    protected static Color CONNECTION_COLOR = Color.BLACK;
    //设置flows连接线字体颜色red
    protected static Color LABEL_COLOR = new Color(0, 0, 0);
    //高亮显示task框颜色
    protected static Color HIGHLIGHT_COLOR = Color.GREEN;
    protected static Color HIGHLIGHT_COLOR1 = Color.RED;
    public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
        this.initialize(imageType);
    }
    /**
     * é‡å†™ç»˜åˆ¶è¿žçº¿çš„æ–¹å¼,设置绘制颜色
     * @param xPoints
     * @param yPoints
     * @param conditional
     * @param isDefault
     * @param connectionType
     * @param associationDirection
     * @param highLighted
     * @param scaleFactor
     */
    @Override
    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(CONNECTION_COLOR);
        if (connectionType.equals("association")) {
            this.g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR);
            this.g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }
        for (int i = 1; i < xPoints.length; ++i) {
            int sourceX = xPoints[i - 1];
            int sourceY = yPoints[i - 1];
            int targetX = xPoints[i];
            int targetY = yPoints[i];
            java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, (double) sourceY, (double) targetX, (double) targetY);
            this.g.draw(line);
        }
        java.awt.geom.Line2D.Double line;
        if (isDefault) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
            this.drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }
        if (conditional) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
            this.drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }
        if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], (double) yPoints[xPoints.length - 1]);
            this.drawArrowHead(line, scaleFactor);
        }
        if (associationDirection.equals(AssociationDirection.BOTH)) {
            line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], (double) xPoints[0], (double) yPoints[0]);
            this.drawArrowHead(line, scaleFactor);
        }
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }
    /**
     * è®¾ç½®å­—体大小图标颜色
     * @param imageType
     */
    @Override
    public void initialize(String imageType) {
        if ("png".equalsIgnoreCase(imageType)) {
            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2);
        } else {
            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1);
        }
        this.g = this.processDiagram.createGraphics();
        if (!"png".equalsIgnoreCase(imageType)) {
            this.g.setBackground(new Color(255, 255, 255, 0));
            this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
        }
        this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        //修改图标颜色,修改图标字体大小
        this.g.setPaint(Color.black);
        Font font = new Font(this.activityFontName, 10, 14);
        this.g.setFont(font);
        this.fontMetrics = this.g.getFontMetrics();
        //修改连接线字体大小
        LABEL_FONT = new Font(this.labelFontName, 10, 15);
        ANNOTATION_FONT = new Font(this.annotationFontName, 0, 11);
        try {
            USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png", this.customClassLoader));
            SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", this.customClassLoader));
            SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", this.customClassLoader));
            RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", this.customClassLoader));
            SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", this.customClassLoader));
            MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", this.customClassLoader));
            BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", this.customClassLoader));
            SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", this.customClassLoader));
            DMN_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png", this.customClassLoader));
            CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", this.customClassLoader));
            MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", this.customClassLoader));
            HTTP_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/httpTask.png", this.customClassLoader));
            TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", this.customClassLoader));
            COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", this.customClassLoader));
            COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", this.customClassLoader));
            ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", this.customClassLoader));
            ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", this.customClassLoader));
            MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", this.customClassLoader));
            MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", this.customClassLoader));
            SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", this.customClassLoader));
            SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", this.customClassLoader));
        } catch (IOException var4) {
            LOGGER.warn("Could not load image for process diagram creation: {}", var4.getMessage());
        }
    }
    /**
     * è®¾ç½®è¿žæŽ¥çº¿å­—体
     * @param text
     * @param graphicInfo
     * @param centered
     */
    @Override
    public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) {
        float interline = 1.0f;
        // text
        if (text != null && text.length() > 0) {
            Paint originalPaint = g.getPaint();
            Font originalFont = g.getFont();
            g.setPaint(LABEL_COLOR);
            g.setFont(LABEL_FONT);
            int wrapWidth = 100;
            int textY = (int) graphicInfo.getY();
            // TODO: use drawMultilineText()
            AttributedString as = new AttributedString(text);
            as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
            as.addAttribute(TextAttribute.FONT, g.getFont());
            AttributedCharacterIterator aci = as.getIterator();
            FontRenderContext frc = new FontRenderContext(null, true, false);
            LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
            while (lbm.getPosition() < text.length()) {
                TextLayout tl = lbm.nextLayout(wrapWidth);
                textY += tl.getAscent();
                Rectangle2D bb = tl.getBounds();
                double tX = graphicInfo.getX();
                if (centered) {
                    tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                }
                tl.draw(g, (float) tX, textY);
                textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
            }
            // restore originals
            g.setFont(originalFont);
            g.setPaint(originalPaint);
        }
    }
    /**
     * é«˜äº®æ˜¾ç¤ºtask框完成的
     * @param x
     * @param y
     * @param width
     * @param height
     */
    @Override
    public void drawHighLight(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(HIGHLIGHT_COLOR);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * è‡ªå®šä¹‰task框当前的位置
     * @param x
     * @param y
     * @param width
     * @param height
     */
    public void drawHighLightNow(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(HIGHLIGHT_COLOR1);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * è‡ªå®šä¹‰ç»“束节点
     * @param x
     * @param y
     * @param width
     * @param height
     */
    public void drawHighLightEnd(int x, int y, int width, int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(HIGHLIGHT_COLOR);
        g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
        g.draw(rect);
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }
    /**
     * task框自定义文字
     * @param name
     * @param graphicInfo
     * @param thickBorder
     * @param scaleFactor
     */
    @Override
    protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) {
        Paint originalPaint = g.getPaint();
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();
        // Create a new gradient paint for every task box, gradient depends on x and y and is not relative
        g.setPaint(TASK_BOX_COLOR);
        int arcR = 6;
        if (thickBorder) {
            arcR = 3;
        }
        // shape
        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR);
        g.fill(rect);
        g.setPaint(TASK_BORDER_COLOR);
        if (thickBorder) {
            Stroke originalStroke = g.getStroke();
            g.setStroke(THICK_TASK_BORDER_STROKE);
            g.draw(rect);
            g.setStroke(originalStroke);
        } else {
            g.draw(rect);
        }
        g.setPaint(originalPaint);
        // text
        if (scaleFactor == 1.0 && name != null && name.length() > 0) {
            int boxWidth = width - (2 * TEXT_PADDING);
            int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
            int boxX = x + width / 2 - boxWidth / 2;
            int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;
            drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight);
        }
    }
    protected static Color EVENT_COLOR = new Color(255, 255, 255);
    /**
     * é‡å†™å¼€å§‹äº‹ä»¶
     * @param graphicInfo
     * @param image
     * @param scaleFactor
     */
    @Override
    public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) {
        Paint originalPaint = g.getPaint();
        g.setPaint(EVENT_COLOR);
        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
                graphicInfo.getWidth(), graphicInfo.getHeight());
        g.fill(circle);
        g.setPaint(EVENT_BORDER_COLOR);
        g.draw(circle);
        g.setPaint(originalPaint);
        if (image != null) {
            // calculate coordinates to center image
            int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / (2 * scaleFactor)));
            int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / (2 * scaleFactor)));
            g.drawImage(image, imageX, imageY,
                    (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
        }
    }
    /**
     * é‡å†™ç»“束事件
     * @param graphicInfo
     * @param scaleFactor
     */
    @Override
    public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(EVENT_COLOR);
        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
                graphicInfo.getWidth(), graphicInfo.getHeight());
        g.fill(circle);
        g.setPaint(EVENT_BORDER_COLOR);
//        g.setPaint(HIGHLIGHT_COLOR);
        if (scaleFactor == 1.0) {
            g.setStroke(END_EVENT_STROKE);
        } else {
            g.setStroke(new BasicStroke(2.0f));
        }
        g.draw(circle);
        g.setStroke(originalStroke);
        g.setPaint(originalPaint);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,405 @@
package org.ruoyi.flowable.flow;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import java.util.Iterator;
import java.util.List;
/**
 * @author XuanXuan
 * @date 2021/4/5 0:31
 */
public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator {
    @Override
    protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
        this.prepareBpmnModel(bpmnModel);
        DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
        Iterator var13 = bpmnModel.getPools().iterator();
        while (var13.hasNext()) {
            Pool process = (Pool) var13.next();
            GraphicInfo subProcesses = bpmnModel.getGraphicInfo(process.getId());
            processDiagramCanvas.drawPoolOrLane(process.getName(), subProcesses, scaleFactor);
        }
        var13 = bpmnModel.getProcesses().iterator();
        Process process1;
        Iterator subProcesses1;
        while (var13.hasNext()) {
            process1 = (Process) var13.next();
            subProcesses1 = process1.getLanes().iterator();
            while (subProcesses1.hasNext()) {
                Lane artifact = (Lane) subProcesses1.next();
                GraphicInfo subProcess = bpmnModel.getGraphicInfo(artifact.getId());
                processDiagramCanvas.drawPoolOrLane(artifact.getName(), subProcess, scaleFactor);
            }
        }
        var13 = bpmnModel.getProcesses().iterator();
        while (var13.hasNext()) {
            process1 = (Process) var13.next();
            subProcesses1 = process1.findFlowElementsOfType(FlowNode.class).iterator();
            while (subProcesses1.hasNext()) {
                FlowNode artifact1 = (FlowNode) subProcesses1.next();
                if (!this.isPartOfCollapsedSubProcess(artifact1, bpmnModel)) {
                    this.drawActivity(processDiagramCanvas, bpmnModel, artifact1, highLightedActivities, highLightedFlows, scaleFactor, Boolean.valueOf(drawSequenceFlowNameWithNoLabelDI));
                }
            }
        }
        var13 = bpmnModel.getProcesses().iterator();
        label75:
        while (true) {
            List subProcesses2;
            do {
                if (!var13.hasNext()) {
                    return processDiagramCanvas;
                }
                process1 = (Process) var13.next();
                subProcesses1 = process1.getArtifacts().iterator();
                while (subProcesses1.hasNext()) {
                    Artifact artifact2 = (Artifact) subProcesses1.next();
                    this.drawArtifact(processDiagramCanvas, bpmnModel, artifact2);
                }
                subProcesses2 = process1.findFlowElementsOfType(SubProcess.class, true);
            } while (subProcesses2 == null);
            Iterator artifact3 = subProcesses2.iterator();
            while (true) {
                GraphicInfo graphicInfo;
                SubProcess subProcess1;
                do {
                    do {
                        if (!artifact3.hasNext()) {
                            continue label75;
                        }
                        subProcess1 = (SubProcess) artifact3.next();
                        graphicInfo = bpmnModel.getGraphicInfo(subProcess1.getId());
                    } while (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded().booleanValue());
                } while (this.isPartOfCollapsedSubProcess(subProcess1, bpmnModel));
                Iterator var19 = subProcess1.getArtifacts().iterator();
                while (var19.hasNext()) {
                    Artifact subProcessArtifact = (Artifact) var19.next();
                    this.drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
                }
            }
        }
    }
    protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        double minX = 1.7976931348623157E308D;
        double maxX = 0.0D;
        double minY = 1.7976931348623157E308D;
        double maxY = 0.0D;
        GraphicInfo nrOfLanes;
        for (Iterator flowNodes = bpmnModel.getPools().iterator(); flowNodes.hasNext(); maxY = nrOfLanes.getY() + nrOfLanes.getHeight()) {
            Pool artifacts = (Pool) flowNodes.next();
            nrOfLanes = bpmnModel.getGraphicInfo(artifacts.getId());
            minX = nrOfLanes.getX();
            maxX = nrOfLanes.getX() + nrOfLanes.getWidth();
            minY = nrOfLanes.getY();
        }
        List var23 = gatherAllFlowNodes(bpmnModel);
        Iterator var24 = var23.iterator();
        label155:
        while (var24.hasNext()) {
            FlowNode var26 = (FlowNode) var24.next();
            GraphicInfo artifact = bpmnModel.getGraphicInfo(var26.getId());
            if (artifact.getX() + artifact.getWidth() > maxX) {
                maxX = artifact.getX() + artifact.getWidth();
            }
            if (artifact.getX() < minX) {
                minX = artifact.getX();
            }
            if (artifact.getY() + artifact.getHeight() > maxY) {
                maxY = artifact.getY() + artifact.getHeight();
            }
            if (artifact.getY() < minY) {
                minY = artifact.getY();
            }
            Iterator process = var26.getOutgoingFlows().iterator();
            while (true) {
                List l;
                do {
                    if (!process.hasNext()) {
                        continue label155;
                    }
                    SequenceFlow graphicInfoList = (SequenceFlow) process.next();
                    l = bpmnModel.getFlowLocationGraphicInfo(graphicInfoList.getId());
                } while (l == null);
                Iterator graphicInfo = l.iterator();
                while (graphicInfo.hasNext()) {
                    GraphicInfo graphicInfo1 = (GraphicInfo) graphicInfo.next();
                    if (graphicInfo1.getX() > maxX) {
                        maxX = graphicInfo1.getX();
                    }
                    if (graphicInfo1.getX() < minX) {
                        minX = graphicInfo1.getX();
                    }
                    if (graphicInfo1.getY() > maxY) {
                        maxY = graphicInfo1.getY();
                    }
                    if (graphicInfo1.getY() < minY) {
                        minY = graphicInfo1.getY();
                    }
                }
            }
        }
        List var25 = gatherAllArtifacts(bpmnModel);
        Iterator var27 = var25.iterator();
        GraphicInfo var37;
        while (var27.hasNext()) {
            Artifact var29 = (Artifact) var27.next();
            GraphicInfo var31 = bpmnModel.getGraphicInfo(var29.getId());
            if (var31 != null) {
                if (var31.getX() + var31.getWidth() > maxX) {
                    maxX = var31.getX() + var31.getWidth();
                }
                if (var31.getX() < minX) {
                    minX = var31.getX();
                }
                if (var31.getY() + var31.getHeight() > maxY) {
                    maxY = var31.getY() + var31.getHeight();
                }
                if (var31.getY() < minY) {
                    minY = var31.getY();
                }
            }
            List var33 = bpmnModel.getFlowLocationGraphicInfo(var29.getId());
            if (var33 != null) {
                Iterator var35 = var33.iterator();
                while (var35.hasNext()) {
                    var37 = (GraphicInfo) var35.next();
                    if (var37.getX() > maxX) {
                        maxX = var37.getX();
                    }
                    if (var37.getX() < minX) {
                        minX = var37.getX();
                    }
                    if (var37.getY() > maxY) {
                        maxY = var37.getY();
                    }
                    if (var37.getY() < minY) {
                        minY = var37.getY();
                    }
                }
            }
        }
        int var28 = 0;
        Iterator var30 = bpmnModel.getProcesses().iterator();
        while (var30.hasNext()) {
            Process var32 = (Process) var30.next();
            Iterator var34 = var32.getLanes().iterator();
            while (var34.hasNext()) {
                Lane var36 = (Lane) var34.next();
                ++var28;
                var37 = bpmnModel.getGraphicInfo(var36.getId());
                if (var37.getX() + var37.getWidth() > maxX) {
                    maxX = var37.getX() + var37.getWidth();
                }
                if (var37.getX() < minX) {
                    minX = var37.getX();
                }
                if (var37.getY() + var37.getHeight() > maxY) {
                    maxY = var37.getY() + var37.getHeight();
                }
                if (var37.getY() < minY) {
                    minY = var37.getY();
                }
            }
        }
        if (var23.isEmpty() && bpmnModel.getPools().isEmpty() && var28 == 0) {
            minX = 0.0D;
            minY = 0.0D;
        }
        return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }
    private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
    }
    private static void drawHighLightNow(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLightNow((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
    }
    private static void drawHighLightEnd(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLightEnd((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
    }
    @Override
    protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel,
                                FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) {
        ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass());
        if (drawInstruction != null) {
            drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);
            // Gather info on the multi instance marker
            boolean multiInstanceSequential = false;
            boolean multiInstanceParallel = false;
            boolean collapsed = false;
            if (flowNode instanceof Activity) {
                Activity activity = (Activity) flowNode;
                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
                if (multiInstanceLoopCharacteristics != null) {
                    multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
                    multiInstanceParallel = !multiInstanceSequential;
                }
            }
            // Gather info on the collapsed marker
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            if (flowNode instanceof SubProcess) {
                collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded();
            } else if (flowNode instanceof CallActivity) {
                collapsed = true;
            }
            if (scaleFactor == 1.0) {
                // Actually draw the markers
                processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(),
                        multiInstanceSequential, multiInstanceParallel, collapsed);
            }
            // Draw highlighted activities
            if (highLightedActivities.contains(flowNode.getId())) {
                if (highLightedActivities.get(highLightedActivities.size() - 1).equals(flowNode.getId())
                        && !"endenv".equals(flowNode.getId())) {
                    if ((flowNode.getId().contains("Event_"))) {
                        drawHighLightEnd((CustomProcessDiagramCanvas) processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
                    } else {
                        drawHighLightNow((CustomProcessDiagramCanvas) processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
                    }
                } else {
                    drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
                }
            }
        }
        // Outgoing transitions of activity
        for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
            boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId()));
            String defaultFlow = null;
            if (flowNode instanceof Activity) {
                defaultFlow = ((Activity) flowNode).getDefaultFlow();
            } else if (flowNode instanceof Gateway) {
                defaultFlow = ((Gateway) flowNode).getDefaultFlow();
            }
            boolean isDefault = false;
            if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
                isDefault = true;
            }
            boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && !(flowNode instanceof Gateway);
            String sourceRef = sequenceFlow.getSourceRef();
            String targetRef = sequenceFlow.getTargetRef();
            FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
            FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
            if (graphicInfoList != null && graphicInfoList.size() > 0) {
                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
                int xPoints[] = new int[graphicInfoList.size()];
                int yPoints[] = new int[graphicInfoList.size()];
                for (int i = 1; i < graphicInfoList.size(); i++) {
                    GraphicInfo graphicInfo = graphicInfoList.get(i);
                    GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
                    if (i == 1) {
                        xPoints[0] = (int) previousGraphicInfo.getX();
                        yPoints[0] = (int) previousGraphicInfo.getY();
                    }
                    xPoints[i] = (int) graphicInfo.getX();
                    yPoints[i] = (int) graphicInfo.getY();
                }
                processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor);
                // Draw sequenceflow label
                GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
                if (labelGraphicInfo != null) {
                    processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false);
                } else {
                    if (drawSequenceFlowNameWithNoLabelDI) {
                        GraphicInfo lineCenter = getLineCenter(graphicInfoList);
                        processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false);
                    }
                }
            }
        }
        // Nested elements
        if (flowNode instanceof FlowElementsContainer) {
            for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
                if (nestedFlowElement instanceof FlowNode && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) {
                    drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement,
                            highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
                }
            }
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
package org.ruoyi.flowable.flow;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * @author Xuan xuan
 * @date 2021/4/19 20:51
 */
public class FindNextNodeUtil {
    /**
     * èŽ·å–ä¸‹ä¸€æ­¥éª¤çš„ç”¨æˆ·ä»»åŠ¡
     *
     * @param repositoryService
     * @param map
     * @return
     */
    public static List<UserTask> getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, Map<String, Object> map) {
        List<UserTask> data = new ArrayList<>();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        Process mainProcess = bpmnModel.getMainProcess();
        Collection<FlowElement> flowElements = mainProcess.getFlowElements();
        String key = task.getTaskDefinitionKey();
        FlowElement flowElement = bpmnModel.getFlowElement(key);
        next(flowElements, flowElement, map, data);
        return data;
    }
    public static void next(Collection<FlowElement> flowElements, FlowElement flowElement, Map<String, Object> map, List<UserTask> nextUser) {
        //如果是结束节点
        if (flowElement instanceof EndEvent) {
            //如果是子任务的结束节点
            if (getSubProcess(flowElements, flowElement) != null) {
                flowElement = getSubProcess(flowElements, flowElement);
            }
        }
        //获取Task的出线信息--可以拥有多个
        List<SequenceFlow> outGoingFlows = null;
        if (flowElement instanceof Task) {
            outGoingFlows = ((Task) flowElement).getOutgoingFlows();
        } else if (flowElement instanceof Gateway) {
            outGoingFlows = ((Gateway) flowElement).getOutgoingFlows();
        } else if (flowElement instanceof StartEvent) {
            outGoingFlows = ((StartEvent) flowElement).getOutgoingFlows();
        } else if (flowElement instanceof SubProcess) {
            outGoingFlows = ((SubProcess) flowElement).getOutgoingFlows();
        } else if (flowElement instanceof CallActivity) {
            outGoingFlows = ((CallActivity) flowElement).getOutgoingFlows();
        }
        if (outGoingFlows != null && outGoingFlows.size() > 0) {
            //遍历所有的出线--找到可以正确执行的那一条
            for (SequenceFlow sequenceFlow : outGoingFlows) {
                //1.有表达式,且为true
                //2.无表达式
                String expression = sequenceFlow.getConditionExpression();
                if (expression == null || expressionResult(map, expression.substring(expression.lastIndexOf("{") + 1, expression.lastIndexOf("}")))) {
                    //出线的下一节点
                    String nextFlowElementID = sequenceFlow.getTargetRef();
                    if (checkSubProcess(nextFlowElementID, flowElements, nextUser)) {
                        continue;
                    }
                    //查询下一节点的信息
                    FlowElement nextFlowElement = getFlowElementById(nextFlowElementID, flowElements);
                    //调用流程
                    if (nextFlowElement instanceof CallActivity) {
                        CallActivity ca = (CallActivity) nextFlowElement;
                        if (ca.getLoopCharacteristics() != null) {
                            UserTask userTask = new UserTask();
                            userTask.setId(ca.getId());
                            userTask.setId(ca.getId());
                            userTask.setLoopCharacteristics(ca.getLoopCharacteristics());
                            userTask.setName(ca.getName());
                            nextUser.add(userTask);
                        }
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //用户任务
                    if (nextFlowElement instanceof UserTask) {
                        nextUser.add((UserTask) nextFlowElement);
                    }
                    //排他网关
                    else if (nextFlowElement instanceof ExclusiveGateway) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //并行网关
                    else if (nextFlowElement instanceof ParallelGateway) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //接收任务
                    else if (nextFlowElement instanceof ReceiveTask) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //服务任务
                    else if (nextFlowElement instanceof ServiceTask) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //子任务的起点
                    else if (nextFlowElement instanceof StartEvent) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                    //结束节点
                    else if (nextFlowElement instanceof EndEvent) {
                        next(flowElements, nextFlowElement, map, nextUser);
                    }
                }
            }
        }
    }
    /**
     * åˆ¤æ–­æ˜¯å¦æ˜¯å¤šå®žä¾‹å­æµç¨‹å¹¶ä¸”需要设置集合类型变量
     */
    public static boolean checkSubProcess(String Id, Collection<FlowElement> flowElements, List<UserTask> nextUser) {
        for (FlowElement flowElement1 : flowElements) {
            if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(Id)) {
                SubProcess sp = (SubProcess) flowElement1;
                if (sp.getLoopCharacteristics() != null) {
                    String inputDataItem = sp.getLoopCharacteristics().getInputDataItem();
                    UserTask userTask = new UserTask();
                    userTask.setId(sp.getId());
                    userTask.setLoopCharacteristics(sp.getLoopCharacteristics());
                    userTask.setName(sp.getName());
                    nextUser.add(userTask);
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * æŸ¥è¯¢ä¸€ä¸ªèŠ‚ç‚¹çš„æ˜¯å¦å­ä»»åŠ¡ä¸­çš„èŠ‚ç‚¹ï¼Œå¦‚æžœæ˜¯ï¼Œè¿”å›žå­ä»»åŠ¡
     *
     * @param flowElements å…¨æµç¨‹çš„节点集合
     * @param flowElement  å½“前节点
     * @return
     */
    public static FlowElement getSubProcess(Collection<FlowElement> flowElements, FlowElement flowElement) {
        for (FlowElement flowElement1 : flowElements) {
            if (flowElement1 instanceof SubProcess) {
                for (FlowElement flowElement2 : ((SubProcess) flowElement1).getFlowElements()) {
                    if (flowElement.equals(flowElement2)) {
                        return flowElement1;
                    }
                }
            }
        }
        return null;
    }
    /**
     * æ ¹æ®ID查询流程节点对象, å¦‚果是子任务,则返回子任务的开始节点
     *
     * @param Id           èŠ‚ç‚¹ID
     * @param flowElements æµç¨‹èŠ‚ç‚¹é›†åˆ
     * @return
     */
    public static FlowElement getFlowElementById(String Id, Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement.getId().equals(Id)) {
                //如果是子任务,则查询出子任务的开始节点
                if (flowElement instanceof SubProcess) {
                    return getStartFlowElement(((SubProcess) flowElement).getFlowElements());
                }
                return flowElement;
            }
            if (flowElement instanceof SubProcess) {
                FlowElement flowElement1 = getFlowElementById(Id, ((SubProcess) flowElement).getFlowElements());
                if (flowElement1 != null) {
                    return flowElement1;
                }
            }
        }
        return null;
    }
    /**
     * è¿”回流程的开始节点
     *
     * @param flowElements èŠ‚ç‚¹é›†åˆ
     * @description:
     */
    public static FlowElement getStartFlowElement(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                return flowElement;
            }
        }
        return null;
    }
    /**
     * æ ¡éªŒel表达式
     *
     * @param map
     * @param expression
     * @return
     */
    public static boolean expressionResult(Map<String, Object> map, String expression) {
        Expression exp = AviatorEvaluator.compile(expression);
        final Object execute = exp.execute(map);
        return Boolean.parseBoolean(String.valueOf(execute));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package org.ruoyi.flowable.flow;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
/**
 * @author XuanXuan
 * @date 2021/4/5 01:32
 */
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName("宋体");
        engineConfiguration.setLabelFontName("宋体");
        engineConfiguration.setAnnotationFontName("宋体");
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,706 @@
package org.ruoyi.flowable.flow;
import cn.hutool.core.util.ObjectUtil;
import org.ruoyi.common.core.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.*;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.task.api.history.HistoricTaskInstance;
import java.util.*;
/**
 * @author XuanXuan
 * @date 2021-04-03 23:57
 */
@Slf4j
public class FlowableUtils {
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
     * @param source
     * @return
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getIncomingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getIncomingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getIncomingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getIncomingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getIncomingFlows();
        }
        return sequenceFlows;
    }
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
     * @param source
     * @return
     */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getOutgoingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getOutgoingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getOutgoingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getOutgoingFlows();
        }
        return sequenceFlows;
    }
    /**
     * èŽ·å–å…¨éƒ¨èŠ‚ç‚¹åˆ—è¡¨ï¼ŒåŒ…å«å­æµç¨‹èŠ‚ç‚¹
     * @param flowElements
     * @param allElements
     * @return
     */
    public static Collection<FlowElement> getAllElements(Collection<FlowElement> flowElements, Collection<FlowElement> allElements) {
        allElements = allElements == null ? new ArrayList<>() : allElements;
        for (FlowElement flowElement : flowElements) {
            allElements.add(flowElement);
            if (flowElement instanceof SubProcess) {
                // ç»§ç»­æ·±å…¥å­æµç¨‹ï¼Œè¿›ä¸€æ­¥èŽ·å–å­æµç¨‹
                allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements);
            }
        }
        return allElements;
    }
    /**
     * è¿­ä»£èŽ·å–çˆ¶çº§ä»»åŠ¡èŠ‚ç‚¹åˆ—è¡¨ï¼Œå‘å‰æ‰¾
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList å·²æ‰¾åˆ°çš„用户任务节点
     * @return
     */
    public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // ç±»åž‹ä¸ºç”¨æˆ·èŠ‚ç‚¹ï¼Œåˆ™æ–°å¢žçˆ¶çº§èŠ‚ç‚¹
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
                    continue;
                }
                // ç±»åž‹ä¸ºå­æµç¨‹ï¼Œåˆ™æ·»åŠ å­æµç¨‹å¼€å§‹èŠ‚ç‚¹å‡ºå£å¤„ç›¸è¿žçš„èŠ‚ç‚¹
                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
                    // èŽ·å–å­æµç¨‹ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
                    List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }
    /**
     * æ ¹æ®æ­£åœ¨è¿è¡Œçš„任务节点,迭代获取子级任务节点列表,向后找
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param runTaskKeyList æ­£åœ¨è¿è¡Œçš„任务 Key,用于校验任务节点是否是正在运行的节点
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList éœ€è¦æ’¤å›žçš„用户任务列表
     * @return
     */
    public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果为用户任务类型,且任务节点的 Key æ­£åœ¨è¿è¡Œçš„任务中存在,添加
                if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                    continue;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }
    /**
     * è¿­ä»£èŽ·å–å­æµç¨‹ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList éœ€è¦æ’¤å›žçš„用户任务列表
     * @return
     */
    public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果为用户任务类型,且任务节点的 Key æ­£åœ¨è¿è¡Œçš„任务中存在,添加
                if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                    continue;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }
    /**
     * ä»ŽåŽå‘前寻路,获取所有脏线路上的点
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param passRoads å·²ç»ç»è¿‡çš„点集合
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targets ç›®æ ‡è„çº¿è·¯ç»ˆç‚¹
     * @param dirtyRoads ç¡®å®šä¸ºè„æ•°æ®çš„点,因为不需要重复,因此使用 set å­˜å‚¨
     * @return
     */
    public static Set<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads, Set<String> hasSequenceFlow, List<String> targets, Set<String> dirtyRoads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ–°å¢žç»è¿‡çš„路线
                passRoads.add(sequenceFlow.getSourceFlowElement().getId());
                // å¦‚果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线
                if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) {
                    dirtyRoads.addAll(passRoads);
                    continue;
                }
                // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
                    dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads);
                    // æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œtrue æ˜¯ï¼Œfalse å¦
                    Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null);
                    if (isInChildProcess) {
                        // å·²åœ¨å­æµç¨‹ä¸Šæ‰¾åˆ°ï¼Œè¯¥è·¯çº¿ç»“束
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, targets, dirtyRoads);
            }
        }
        return dirtyRoads;
    }
    /**
     * è¿­ä»£èŽ·å–å­æµç¨‹è„è·¯çº¿
     * è¯´æ˜Žï¼Œå‡å¦‚回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param dirtyRoads ç¡®å®šä¸ºè„æ•°æ®çš„点,因为不需要重复,因此使用 set å­˜å‚¨
     * @return
     */
    public static Set<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow, Set<String> dirtyRoads) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ·»åŠ è„è·¯çº¿
                dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId());
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads);
                }
                // ç»§ç»­è¿­ä»£
                dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, dirtyRoads);
            }
        }
        return dirtyRoads;
    }
    /**
     * åˆ¤æ–­è„è·¯çº¿ç»“束节点是否在子流程上
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targets åˆ¤æ–­è„è·¯çº¿èŠ‚ç‚¹æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œåªè¦å­˜åœ¨ä¸€ä¸ªï¼Œè¯´æ˜Žè„è·¯çº¿åªåˆ°å­æµç¨‹ä¸ºæ­¢
     * @param inChildProcess æ˜¯å¦å­˜åœ¨å­æµç¨‹ä¸Šï¼Œtrue æ˜¯ï¼Œfalse å¦
     * @return
     */
    public static Boolean dirtyTargetInChildProcess(FlowElement source, Set<String> hasSequenceFlow, List<String> targets, Boolean inChildProcess) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        inChildProcess = inChildProcess == null ? false : inChildProcess;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null && !inChildProcess) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果发现目标点在子流程上存在,说明只到子流程为止
                if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) {
                    inChildProcess = true;
                    break;
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess);
                }
                // ç»§ç»­è¿­ä»£
                inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, targets, inChildProcess);
            }
        }
        return inChildProcess;
    }
    /**
     * è¿­ä»£ä»ŽåŽå‘前扫描,判断目标节点相对于当前节点是否是串行
     * ä¸å­˜åœ¨ç›´æŽ¥å›žé€€åˆ°å­æµç¨‹ä¸­çš„æƒ…况,但存在从子流程出去到父流程情况
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param isSequential æ˜¯å¦ä¸²è¡Œ
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param targetKsy ç›®æ ‡èŠ‚ç‚¹
     * @return
     */
    public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set<String> hasSequenceFlow, Boolean isSequential) {
        isSequential = isSequential == null ? true : isSequential;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, isSequential);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // å¦‚果目标节点已被判断为并行,后面都不需要执行,直接返回
                if (isSequential == false) {
                    break;
                }
                // è¿™æ¡çº¿è·¯å­˜åœ¨ç›®æ ‡èŠ‚ç‚¹ï¼Œè¿™æ¡çº¿è·¯å®Œæˆï¼Œè¿›å…¥ä¸‹ä¸ªçº¿è·¯
                if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
                    continue;
                }
                if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
                    isSequential = false;
                    break;
                }
                // å¦åˆ™å°±ç»§ç»­è¿­ä»£
                isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, hasSequenceFlow, isSequential);
            }
        }
        return isSequential;
    }
    /**
     * ä»ŽåŽå‘前寻路,获取到达节点的所有路线
     * ä¸å­˜åœ¨ç›´æŽ¥å›žé€€åˆ°å­æµç¨‹ï¼Œä½†æ˜¯å­˜åœ¨å›žé€€åˆ°çˆ¶çº§æµç¨‹çš„æƒ…况
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param passRoads å·²ç»ç»è¿‡çš„点集合
     * @param roads è·¯çº¿
     * @return
     */
    public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        roads = roads == null ? new ArrayList<>() : roads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // å¦‚果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null && sequenceFlows.size() != 0) {
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                // æ·»åŠ ç»è¿‡è·¯çº¿
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
                }
                // ç»§ç»­è¿­ä»£
                roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
            }
        } else {
            // æ·»åŠ è·¯çº¿
            roads.add(passRoads);
        }
        return roads;
    }
    /**
     * åŽ†å²èŠ‚ç‚¹æ•°æ®æ¸…æ´—ï¼Œæ¸…æ´—æŽ‰åˆå›žæ»šå¯¼è‡´çš„è„æ•°æ®
     * @param allElements å…¨éƒ¨èŠ‚ç‚¹ä¿¡æ¯
     * @param historicTaskInstanceList åŽ†å²ä»»åŠ¡å®žä¾‹ä¿¡æ¯ï¼Œæ•°æ®é‡‡ç”¨å¼€å§‹æ—¶é—´å‡åº
     * @return
     */
    public static List<String> historicTaskInstanceClean(Collection<FlowElement> allElements, List<HistoricTaskInstance> historicTaskInstanceList) {
        // ä¼šç­¾èŠ‚ç‚¹æ”¶é›†
        List<String> multiTask = new ArrayList<>();
        allElements.forEach(flowElement -> {
            if (flowElement instanceof UserTask) {
                // å¦‚果该节点的行为为会签行为,说明该节点为会签节点
                if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) {
                    multiTask.add(flowElement.getId());
                }
            }
        });
        // å¾ªçŽ¯æ”¾å…¥æ ˆï¼Œæ ˆ LIFO:后进先出
        Stack<HistoricTaskInstance> stack = new Stack<>();
        historicTaskInstanceList.forEach(item -> stack.push(item));
        // æ¸…洗后的历史任务实例
        List<String> lastHistoricTaskInstanceList = new ArrayList<>();
        // ç½‘关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗
        // ä¸´æ—¶ç”¨æˆ·ä»»åŠ¡ key
        StringBuilder userTaskKey = null;
        // ä¸´æ—¶è¢«åˆ æŽ‰çš„任务 key,存在并行情况
        List<String> deleteKeyList = new ArrayList<>();
        // ä¸´æ—¶è„æ•°æ®çº¿è·¯
        List<Set<String>> dirtyDataLineList = new ArrayList<>();
        // ç”±æŸä¸ªç‚¹è·³åˆ°ä¼šç­¾ç‚¹,此时出现多个会签实例对应 1 ä¸ªè·³è½¬æƒ…况,需要把这些连续脏数据都找到
        // ä¼šç­¾ç‰¹æ®Šå¤„理下标
        int multiIndex = -1;
        // ä¼šç­¾ç‰¹æ®Šå¤„理 key
        StringBuilder multiKey = null;
        // ä¼šç­¾ç‰¹æ®Šå¤„理操作标识
        boolean multiOpera = false;
        while (!stack.empty()) {
            // ä»Žè¿™é‡Œå¼€å§‹ userTaskKey éƒ½è¿˜æ˜¯ä¸Šä¸ªæ ˆçš„ key
            // æ˜¯å¦æ˜¯è„æ•°æ®çº¿è·¯ä¸Šçš„点
            final boolean[] isDirtyData = {false};
            for (Set<String> oldDirtyDataLine : dirtyDataLineList) {
                if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) {
                    isDirtyData[0] = true;
                }
            }
            // åˆ é™¤åŽŸå› ä¸ä¸ºç©ºï¼Œè¯´æ˜Žä»Žè¿™æ¡æ•°æ®å¼€å§‹å›žè·³æˆ–è€…å›žé€€çš„
            // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内
            if (stack.peek().getDeleteReason() != null && !stack.peek().getDeleteReason().equals("MI_END")) {
                // å¯ä»¥ç†è§£ä¸ºè„çº¿è·¯èµ·ç‚¹
                String dirtyPoint = "";
                if (stack.peek().getDeleteReason().indexOf("Change activity to ") >= 0) {
                    dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
                }
                // ä¼šç­¾å›žé€€åˆ é™¤åŽŸå› æœ‰ç‚¹ä¸åŒ
                if (stack.peek().getDeleteReason().indexOf("Change parent activity to ") >= 0) {
                    dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
                }
                FlowElement dirtyTask = null;
                // èŽ·å–å˜æ›´èŠ‚ç‚¹çš„å¯¹åº”çš„å…¥å£å¤„è¿žçº¿
                // å¦‚果是网关并行回退情况,会变成两条脏数据路线,效果一样
                for (FlowElement flowElement : allElements) {
                    if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) {
                        dirtyTask = flowElement;
                    }
                }
                // èŽ·å–è„æ•°æ®çº¿è·¯
                Set<String> dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null);
                // è‡ªå·±æœ¬èº«ä¹Ÿæ˜¯è„çº¿è·¯ä¸Šçš„点,加进去
                dirtyDataLine.add(stack.peek().getTaskDefinitionKey());
                log.info(stack.peek().getTaskDefinitionKey() + "点脏路线集合:" + dirtyDataLine);
                // æ˜¯å…¨æ–°çš„需要添加的脏线路
                boolean isNewDirtyData = true;
                for (int i = 0; i < dirtyDataLineList.size(); i++) {
                    // å¦‚果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回
                    // è¿™æ—¶ï¼Œéƒ½ä»¥ä¹‹å‰çš„脏线路节点为标准,只需合并脏线路即可,也就是路线补全
                    if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) {
                        isNewDirtyData = false;
                        dirtyDataLineList.get(i).addAll(dirtyDataLine);
                    }
                }
                // å·²ç¡®å®šæ—¶å…¨æ–°çš„脏线路
                if (isNewDirtyData) {
                    // deleteKey å•一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey å…¶å®žæ˜¯ç”±å¤šä¸ªå€¼ç»„成
                    // æŒ‰ç…§é€»è¾‘,回退后立刻生成的实例记录就是回退的记录
                    // è‡³äºŽé©³å›žæ‰€ç”Ÿæˆçš„ Key,直接从删除原因中获取,因为存在驳回到并行的情况
                    deleteKeyList.add(dirtyPoint + ",");
                    dirtyDataLineList.add(dirtyDataLine);
                }
                // æ·»åŠ åŽï¼ŒçŽ°åœ¨è¿™ä¸ªç‚¹å˜æˆè„çº¿è·¯ä¸Šçš„ç‚¹äº†
                isDirtyData[0] = true;
            }
            // å¦‚果不是脏线路上的点,说明是有效数据,添加历史实例 Key
            if (!isDirtyData[0]) {
                lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey());
            }
            // æ ¡éªŒè„çº¿è·¯æ˜¯å¦ç»“束
            for (int i = 0; i < deleteKeyList.size(); i ++) {
                // å¦‚果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始
                if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey())
                        && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
                    multiIndex = i;
                    multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey());
                }
                // ä¼šç­¾è„æ•°æ®å¤„理,节点退回会签清空
                // å¦‚果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉
                if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) {
                    deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
                    multiKey = null;
                    // ç»“束进行下校验删除
                    multiOpera = true;
                }
                // å…¶ä»–脏数据处理
                // å‘现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息
                // è„æ•°æ®äº§ç”Ÿçš„æ–°å®žä¾‹ä¸­æ˜¯å¦åŒ…含这条数据
                if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
                    // åˆ é™¤åŒ¹é…åˆ°çš„部分
                    deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
                }
                // å¦‚果每组中的元素都以匹配过,说明脏数据结束
                if ("".equals(deleteKeyList.get(i))) {
                    // åŒæ—¶åˆ é™¤è„æ•°æ®
                    deleteKeyList.remove(i);
                    dirtyDataLineList.remove(i);
                    break;
                }
            }
            // ä¼šç­¾æ•°æ®å¤„理需要在循环外处理,否则可能导致溢出
            // ä¼šç­¾çš„æ•°æ®è‚¯å®šæ˜¯ä¹‹å‰æ”¾è¿›åŽ»çš„æ‰€ä»¥ç†è®ºä¸Šä¸ä¼šæº¢å‡ºï¼Œä½†è¿˜æ˜¯æ ¡éªŒä¸‹
            if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) {
                // åŒæ—¶åˆ é™¤è„æ•°æ®
                deleteKeyList.remove(multiIndex);
                dirtyDataLineList.remove(multiIndex);
                multiIndex = -1;
                multiOpera = false;
            }
            // pop() æ–¹æ³•与 peek() æ–¹æ³•不同,在返回值的同时,会把值从栈中移除
            // ä¿å­˜æ–°çš„ userTaskKey åœ¨ä¸‹ä¸ªå¾ªçŽ¯ä¸­ä½¿ç”¨
            userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey());
        }
        log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList);
        return lastHistoricTaskInstanceList;
    }
    /**
     * æ·±æœé€’归获取流程未通过的节点
     * @param bpmnModel æµç¨‹æ¨¡åž‹
     * @param unfinishedTaskSet æœªç»“束的任务节点
     * @param finishedSequenceFlowSet å·²ç»å®Œæˆçš„连线
     * @param finishedTaskSet å·²å®Œæˆçš„任务节点
     * @return
     */
    public static Set<String> dfsFindRejects(BpmnModel bpmnModel, Set<String> unfinishedTaskSet, Set<String> finishedSequenceFlowSet, Set<String> finishedTaskSet) {
        if (ObjectUtil.isNull(bpmnModel)) {
            throw new ServiceException("流程模型不存在");
        }
        Collection<FlowElement> allElements = getAllElements(bpmnModel.getMainProcess().getFlowElements(), null);
        Set<String> rejectedSet = new HashSet<>();
        for (FlowElement flowElement : allElements) {
            // ç”¨æˆ·èŠ‚ç‚¹ä¸”æœªç»“æŸå…ƒç´ 
            if (flowElement instanceof UserTask && unfinishedTaskSet.contains(flowElement.getId())) {
                List<String> hasSequenceFlow = iteratorFindFinishes(flowElement, null);
                List<String> rejects = iteratorFindRejects(flowElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, null);
                rejectedSet.addAll(rejects);
            }
        }
        return rejectedSet;
    }
    /**
     * è¿­ä»£èŽ·å–çˆ¶çº§èŠ‚ç‚¹åˆ—è¡¨ï¼Œå‘å‰æ‰¾
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的ID,用于判断线路是否重复
     * @return
     */
    public static List<String> iteratorFindFinishes(FlowElement source, List<String> hasSequenceFlow) {
        hasSequenceFlow = hasSequenceFlow == null ? new ArrayList<>() : hasSequenceFlow;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                FlowElement finishedElement = sequenceFlow.getSourceFlowElement();
                // ç±»åž‹ä¸ºå­æµç¨‹ï¼Œåˆ™æ·»åŠ å­æµç¨‹å¼€å§‹èŠ‚ç‚¹å‡ºå£å¤„ç›¸è¿žçš„èŠ‚ç‚¹
                if (finishedElement instanceof SubProcess) {
                    FlowElement firstElement = (StartEvent) ((SubProcess) finishedElement).getFlowElements().toArray()[0];
                    // èŽ·å–å­æµç¨‹çš„è¿žçº¿
                    hasSequenceFlow.addAll(iteratorFindFinishes(firstElement, null));
                }
                // ç»§ç»­è¿­ä»£
                hasSequenceFlow = iteratorFindFinishes(finishedElement, hasSequenceFlow);
            }
        }
        return hasSequenceFlow;
    }
    /**
     * æ ¹æ®æ­£åœ¨è¿è¡Œçš„任务节点,迭代获取子级任务节点列表,向后找
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param finishedSequenceFlowSet å·²ç»å®Œæˆçš„连线
     * @param finishedTaskSet å·²ç»å®Œæˆçš„任务节点
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param rejectedList æœªé€šè¿‡çš„元素
     * @return
     */
    public static List<String> iteratorFindRejects(FlowElement source, Set<String> finishedSequenceFlowSet, Set<String> finishedTaskSet
            , List<String> hasSequenceFlow, List<String> rejectedList) {
        hasSequenceFlow = hasSequenceFlow == null ? new ArrayList<>() : hasSequenceFlow;
        rejectedList = rejectedList == null ? new ArrayList<>() : rejectedList;
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows != null) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                FlowElement targetElement = sequenceFlow.getTargetFlowElement();
                // æ·»åŠ æœªå®Œæˆçš„èŠ‚ç‚¹
                if (finishedTaskSet.contains(targetElement.getId())) {
                    rejectedList.add(targetElement.getId());
                }
                // æ·»åŠ æœªå®Œæˆçš„è¿žçº¿
                if (finishedSequenceFlowSet.contains(sequenceFlow.getId())) {
                    rejectedList.add(sequenceFlow.getId());
                }
                // å¦‚果节点为子流程节点情况,则从节点中的第一个节点开始获取
                if (targetElement instanceof SubProcess) {
                    FlowElement firstElement = (FlowElement) (((SubProcess) targetElement).getFlowElements().toArray()[0]);
                    List<String> childList = iteratorFindRejects(firstElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, null);
                    // å¦‚果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                    if (childList != null && childList.size() > 0) {
                        rejectedList.addAll(childList);
                        continue;
                    }
                }
                // ç»§ç»­è¿­ä»£
                rejectedList = iteratorFindRejects(targetElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, rejectedList);
            }
        }
        return rejectedList;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package org.ruoyi.flowable.listener;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import org.ruoyi.flowable.common.constant.ProcessConstants;
import org.ruoyi.flowable.common.enums.ProcessStatus;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * Flowable å…¨å±€ç›‘听器
 *
 * @author konbai
 * @since 2023/3/8 22:45
 */
@Component
public class GlobalEventListener extends AbstractFlowableEngineEventListener {
    @Autowired
    private RuntimeService runtimeService;
    /**
     * æµç¨‹ç»“束监听器
     */
    @Override
    protected void processCompleted(FlowableEngineEntityEvent event) {
        String processInstanceId = event.getProcessInstanceId();
        Object variable = runtimeService.getVariable(processInstanceId, ProcessConstants.PROCESS_STATUS_KEY);
        ProcessStatus status = ProcessStatus.getProcessStatus(Convert.toStr(variable));
        if (ObjectUtil.isNotNull(status) && ProcessStatus.RUNNING == status) {
            runtimeService.setVariable(processInstanceId, ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.COMPLETED.getStatus());
        }
        super.processCompleted(event);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.ruoyi.flowable.listener;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.springframework.stereotype.Component;
/**
 * ç”¨æˆ·ä»»åŠ¡ç›‘å¬å™¨
 *
 * @author KonBAI
 * @since 2023/5/13
 */
@Component(value = "userTaskListener")
public class UserTaskListener implements TaskListener {
    /**
     * æ³¨å…¥å­—段(名称与流程设计时字段名称一致)
     */
    // private FixedValue field;
    @Override
    public void notify(DelegateTask delegateTask) {
        //TODO å®žçŽ°ä½ çš„ä»»åŠ¡ç›‘å¬å™¨é€»è¾‘
        System.out.println("执行任务监听器...");
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.ruoyi.common.core.utils.StreamUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.cglib.core.Converter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * bean深拷贝工具(基于 cglib æ€§èƒ½ä¼˜å¼‚)
 * <p>
 * é‡ç‚¹ cglib ä¸æ”¯æŒ æ‹·è´åˆ°é“¾å¼å¯¹è±¡
 * ä¾‹å¦‚: æºå¯¹è±¡ æ‹·è´åˆ° ç›®æ ‡(链式对象)
 * è¯·åŒºåˆ†å¥½`浅拷贝`和`深拷贝`再做使用
 *
 * @author Lion Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanCopyUtils {
    /**
     * å•对象基于class创建拷贝
     *
     * @param source æ•°æ®æ¥æºå®žä½“
     * @param desc   æè¿°å¯¹è±¡ è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> V copy(T source, Class<V> desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        final V target = ReflectUtil.newInstanceIfPossible(desc);
        return copy(source, target);
    }
    /**
     * å•对象基于对象创建拷贝
     *
     * @param source æ•°æ®æ¥æºå®žä½“
     * @param desc   è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> V copy(T source, V desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
        beanCopier.copy(source, desc, null);
        return desc;
    }
    /**
     * åˆ—表对象基于class创建拷贝
     *
     * @param sourceList æ•°æ®æ¥æºå®žä½“列表
     * @param desc       æè¿°å¯¹è±¡ è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
        if (ObjectUtil.isNull(sourceList)) {
            return null;
        }
        if (CollUtil.isEmpty(sourceList)) {
            return CollUtil.newArrayList();
        }
        return StreamUtils.toList(sourceList, source -> {
            V target = ReflectUtil.newInstanceIfPossible(desc);
            copy(source, target);
            return target;
        });
    }
    /**
     * bean拷贝到map
     *
     * @param bean æ•°æ®æ¥æºå®žä½“
     * @return map对象
     */
    @SuppressWarnings("unchecked")
    public static <T> Map<String, Object> copyToMap(T bean) {
        if (ObjectUtil.isNull(bean)) {
            return null;
        }
        return BeanMap.create(bean);
    }
    /**
     * map拷贝到bean
     *
     * @param map       æ•°æ®æ¥æº
     * @param beanClass beanç±»
     * @return bean对象
     */
    public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(beanClass)) {
            return null;
        }
        T bean = ReflectUtil.newInstanceIfPossible(beanClass);
        return mapToBean(map, bean);
    }
    /**
     * map拷贝到bean
     *
     * @param map  æ•°æ®æ¥æº
     * @param bean bean对象
     * @return bean对象
     */
    public static <T> T mapToBean(Map<String, Object> map, T bean) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(bean)) {
            return null;
        }
        BeanMap.create(bean).putAll(map);
        return bean;
    }
    /**
     * map拷贝到map
     *
     * @param map   æ•°æ®æ¥æº
     * @param clazz è¿”回的对象类型
     * @return map对象
     */
    public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(clazz)) {
            return null;
        }
        Map<String, V> copyMap = new LinkedHashMap<>(map.size());
        map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
        return copyMap;
    }
    /**
     * BeanCopier属性缓存<br>
     * ç¼“存用于防止多次反射造成的性能问题
     *
     * @author Looly
     * @since 5.4.1
     */
    public enum BeanCopierCache {
        /**
         * BeanCopier属性缓存单例
         */
        INSTANCE;
        private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
        /**
         * èŽ·å¾—ç±»ä¸Žè½¬æ¢å™¨ç”Ÿæˆçš„key在{@link BeanCopier}的Map中对应的元素
         *
         * @param srcClass    æºBean的类
         * @param targetClass ç›®æ ‡Bean的类
         * @param converter   è½¬æ¢å™¨
         * @return Map中对应的BeanCopier
         */
        public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
            final String key = genKey(srcClass, targetClass, converter);
            return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
        }
        /**
         * èŽ·å¾—ç±»ä¸Žè½¬æ¢å™¨ç”Ÿæˆçš„key
         *
         * @param srcClass    æºBean的类
         * @param targetClass ç›®æ ‡Bean的类
         * @param converter   è½¬æ¢å™¨
         * @return å±žæ€§åå’ŒMap映射的key
         */
        private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
            final StringBuilder key = StrUtil.builder()
                    .append(srcClass.getName()).append('#').append(targetClass.getName());
            if (null != converter) {
                key.append('#').append(converter.getClass().getName());
            }
            return key.toString();
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
 * JSON å·¥å…·ç±»
 *
 * @author èŠ‹é“æºç 
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {
    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
    public static ObjectMapper getObjectMapper() {
        return OBJECT_MAPPER;
    }
    public static String toJsonString(Object object) {
        if (ObjectUtil.isNull(object)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
    public static <T> T parseObject(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
        if (ArrayUtil.isEmpty(bytes)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(bytes, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, typeReference);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static Dict parseMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
        } catch (MismatchedInputException e) {
            // ç±»åž‹ä¸åŒ¹é…è¯´æ˜Žä¸æ˜¯json
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static List<Dict> parseArrayMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static <T> List<T> parseArray(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return new ArrayList<>();
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,374 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.util.io.StringStreamSource;
import java.util.*;
/**
 * @author KonBAI
 * @createTime 2022/3/26 19:04
 */
public class ModelUtils {
    private static final BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
    /**
     * xml转bpmnModel对象
     *
     * @param xml xml
     * @return bpmnModel对象
     */
    public static BpmnModel getBpmnModel(String xml) {
        return bpmnXMLConverter.convertToBpmnModel(new StringStreamSource(xml), false, false);
    }
    /**
     * bpmnModel转xml字符串
     *
     * @deprecated å­˜åœ¨ä¼šä¸¢å¤± bpmn è¿žçº¿é—®é¢˜
     * @param bpmnModel bpmnModel对象
     * @return xml字符串
     */
    @Deprecated
    public static String getBpmnXmlStr(BpmnModel bpmnModel) {
        return StrUtil.utf8Str(getBpmnXml(bpmnModel));
    }
    /**
     * bpmnModel转xml对象
     *
     * @deprecated å­˜åœ¨ä¸¢å¤± bpmn è¿žçº¿é—®é¢˜
     * @param bpmnModel bpmnModel对象
     * @return xml
     */
    @Deprecated
    public static byte[] getBpmnXml(BpmnModel bpmnModel) {
        return bpmnXMLConverter.convertToXML(bpmnModel);
    }
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
     *
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @return å…¥å£è¿žçº¿åˆ—表
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = new ArrayList<>();
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getIncomingFlows();
        }
        return sequenceFlows;
    }
    /**
     * æ ¹æ®èŠ‚ç‚¹ï¼ŒèŽ·å–å‡ºå£è¿žçº¿
     *
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @return å‡ºå£è¿žçº¿åˆ—表
     */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = new ArrayList<>();
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
        }
        return sequenceFlows;
    }
    /**
     * èŽ·å–å¼€å§‹èŠ‚ç‚¹
     *
     * @param model bpmnModel对象
     * @return å¼€å§‹èŠ‚ç‚¹ï¼ˆæœªæ‰¾åˆ°å¼€å§‹èŠ‚ç‚¹ï¼Œè¿”å›žnull)
     */
    public static StartEvent getStartEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        FlowElement startElement = process.getInitialFlowElement();
        if (startElement instanceof StartEvent) {
            return (StartEvent) startElement;
        }
        return getStartEvent(process.getFlowElements());
    }
    /**
     * èŽ·å–å¼€å§‹èŠ‚ç‚¹
     *
     * @param flowElements æµç¨‹å…ƒç´ é›†åˆ
     * @return å¼€å§‹èŠ‚ç‚¹ï¼ˆæœªæ‰¾åˆ°å¼€å§‹èŠ‚ç‚¹ï¼Œè¿”å›žnull)
     */
    public static StartEvent getStartEvent(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                return (StartEvent) flowElement;
            }
        }
        return null;
    }
    /**
     * èŽ·å–ç»“æŸèŠ‚ç‚¹
     *
     * @param model bpmnModel对象
     * @return ç»“束节点(未找到开始节点,返回null)
     */
    public static EndEvent getEndEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        return getEndEvent(process.getFlowElements());
    }
    /**
     * èŽ·å–ç»“æŸèŠ‚ç‚¹
     *
     * @param flowElements æµç¨‹å…ƒç´ é›†åˆ
     * @return ç»“束节点(未找到开始节点,返回null)
     */
    public static EndEvent getEndEvent(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof EndEvent) {
                return (EndEvent) flowElement;
            }
        }
        return null;
    }
    public static UserTask getUserTaskByKey(BpmnModel model, String taskKey) {
        Process process = model.getMainProcess();
        FlowElement flowElement = process.getFlowElement(taskKey);
        if (flowElement instanceof UserTask) {
            return (UserTask) flowElement;
        }
        return null;
    }
    /**
     * èŽ·å–æµç¨‹å…ƒç´ ä¿¡æ¯
     *
     * @param model bpmnModel对象
     * @param flowElementId å…ƒç´ ID
     * @return å…ƒç´ ä¿¡æ¯
     */
    public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
        Process process = model.getMainProcess();
        return process.getFlowElement(flowElementId);
    }
    /**
     * èŽ·å–å…ƒç´ è¡¨å•Key(限开始节点和用户节点可用)
     *
     * @param flowElement å…ƒç´ 
     * @return è¡¨å•Key
     */
    public static String getFormKey(FlowElement flowElement) {
        if (flowElement != null) {
            if (flowElement instanceof StartEvent) {
                return ((StartEvent) flowElement).getFormKey();
            } else if (flowElement instanceof UserTask) {
                return ((UserTask) flowElement).getFormKey();
            }
        }
        return null;
    }
    /**
     * èŽ·å–å¼€å§‹èŠ‚ç‚¹å±žæ€§å€¼
     * @param model bpmnModel对象
     * @param name å±žæ€§å
     * @return å±žæ€§å€¼
     */
    public static String getStartEventAttributeValue(BpmnModel model, String name) {
        StartEvent startEvent = getStartEvent(model);
        return getElementAttributeValue(startEvent, name);
    }
    /**
     * èŽ·å–ç»“æŸèŠ‚ç‚¹å±žæ€§å€¼
     * @param model bpmnModel对象
     * @param name å±žæ€§å
     * @return å±žæ€§å€¼
     */
    public static String getEndEventAttributeValue(BpmnModel model, String name) {
        EndEvent endEvent = getEndEvent(model);
        return getElementAttributeValue(endEvent, name);
    }
    /**
     * èŽ·å–ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹å±žæ€§å€¼
     * @param model bpmnModel对象
     * @param taskKey ä»»åŠ¡Key
     * @param name å±žæ€§å
     * @return å±žæ€§å€¼
     */
    public static String getUserTaskAttributeValue(BpmnModel model, String taskKey, String name) {
        UserTask userTask = getUserTaskByKey(model, taskKey);
        return getElementAttributeValue(userTask, name);
    }
    /**
     * èŽ·å–å…ƒç´ å±žæ€§å€¼
     * @param baseElement æµç¨‹å…ƒç´ 
     * @param name å±žæ€§å
     * @return å±žæ€§å€¼
     */
    public static String getElementAttributeValue(BaseElement baseElement, String name) {
        if (baseElement != null) {
            List<ExtensionAttribute> attributes = baseElement.getAttributes().get(name);
            if (attributes != null && !attributes.isEmpty()) {
                attributes.iterator().next().getValue();
                Iterator<ExtensionAttribute> attrIterator = attributes.iterator();
                if(attrIterator.hasNext()) {
                    ExtensionAttribute attribute = attrIterator.next();
                    return attribute.getValue();
                }
            }
        }
        return null;
    }
    public static boolean isMultiInstance(BpmnModel model, String taskKey) {
        UserTask userTask = getUserTaskByKey(model, taskKey);
        if (ObjectUtil.isNotNull(userTask)) {
            return userTask.hasMultiInstanceLoopCharacteristics();
        }
        return false;
    }
    /**
     * èŽ·å–æ‰€æœ‰ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
     *
     * @param model bpmnModel对象
     * @return ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹åˆ—è¡¨
     */
    public static Collection<UserTask> getAllUserTaskEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        return getAllUserTaskEvent(flowElements, null);
    }
    /**
     * èŽ·å–æ‰€æœ‰ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹
     * @param flowElements æµç¨‹å…ƒç´ é›†åˆ
     * @param allElements æ‰€æœ‰æµç¨‹å…ƒç´ é›†åˆ
     * @return ç”¨æˆ·ä»»åŠ¡èŠ‚ç‚¹åˆ—è¡¨
     */
    public static Collection<UserTask> getAllUserTaskEvent(Collection<FlowElement> flowElements, Collection<UserTask> allElements) {
        allElements = allElements == null ? new ArrayList<>() : allElements;
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask) {
                allElements.add((UserTask) flowElement);
            }
            if (flowElement instanceof SubProcess) {
                // ç»§ç»­æ·±å…¥å­æµç¨‹ï¼Œè¿›ä¸€æ­¥èŽ·å–å­æµç¨‹
                allElements = getAllUserTaskEvent(((SubProcess) flowElement).getFlowElements(), allElements);
            }
        }
        return allElements;
    }
    /**
     * æŸ¥æ‰¾èµ·å§‹èŠ‚ç‚¹ä¸‹ä¸€ä¸ªç”¨æˆ·ä»»åŠ¡åˆ—è¡¨åˆ—è¡¨
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @return ç»“æžœ
     */
    public static List<UserTask> findNextUserTasks(FlowElement source) {
        return findNextUserTasks(source, null, null);
    }
    /**
     * æŸ¥æ‰¾èµ·å§‹èŠ‚ç‚¹ä¸‹ä¸€ä¸ªç”¨æˆ·ä»»åŠ¡åˆ—è¡¨åˆ—è¡¨
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param hasSequenceFlow å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @param userTaskList ç”¨æˆ·ä»»åŠ¡åˆ—è¡¨
     * @return ç»“æžœ
     */
    public static List<UserTask> findNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
        userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
        // èŽ·å–å‡ºå£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (!sequenceFlows.isEmpty()) {
            for (SequenceFlow sequenceFlow : sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                hasSequenceFlow.add(sequenceFlow.getId());
                FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
                if (targetFlowElement instanceof UserTask) {
                    // è‹¥èŠ‚ç‚¹ä¸ºç”¨æˆ·ä»»åŠ¡ï¼ŒåŠ å…¥åˆ°ç»“æžœåˆ—è¡¨ä¸­
                    userTaskList.add((UserTask) targetFlowElement);
                } else {
                    // è‹¥èŠ‚ç‚¹éžç”¨æˆ·ä»»åŠ¡ï¼Œç»§ç»­é€’å½’æŸ¥æ‰¾ä¸‹ä¸€ä¸ªèŠ‚ç‚¹
                    findNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
                }
            }
        }
        return userTaskList;
    }
    /**
     * è¿­ä»£ä»ŽåŽå‘前扫描,判断目标节点相对于当前节点是否是串行
     * ä¸å­˜åœ¨ç›´æŽ¥å›žé€€åˆ°å­æµç¨‹ä¸­çš„æƒ…况,但存在从子流程出去到父流程情况
     * @param source èµ·å§‹èŠ‚ç‚¹
     * @param target ç›®æ ‡èŠ‚ç‚¹
     * @param visitedElements å·²ç»ç»è¿‡çš„连线的 ID,用于判断线路是否重复
     * @return ç»“æžœ
     */
    public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
        visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
        if (source instanceof StartEvent && isInEventSubprocess(source)) {
            return false;
        }
        // æ ¹æ®ç±»åž‹ï¼ŒèŽ·å–å…¥å£è¿žçº¿
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null && sequenceFlows.size() > 0) {
            // å¾ªçŽ¯æ‰¾åˆ°ç›®æ ‡å…ƒç´ 
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // å¦‚果发现连线重复,说明循环了,跳过这个循环
                if (visitedElements.contains(sequenceFlow.getId())) {
                    continue;
                }
                // æ·»åŠ å·²ç»èµ°è¿‡çš„è¿žçº¿
                visitedElements.add(sequenceFlow.getId());
                FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
                // è¿™æ¡çº¿è·¯å­˜åœ¨ç›®æ ‡èŠ‚ç‚¹ï¼Œè¿™æ¡çº¿è·¯å®Œæˆï¼Œè¿›å…¥ä¸‹ä¸ªçº¿è·¯
                if (target.getId().equals(sourceFlowElement.getId())) {
                    continue;
                }
                // å¦‚果目标节点为并行网关,则不继续
                if (sourceFlowElement instanceof ParallelGateway) {
                    return false;
                }
                // å¦åˆ™å°±ç»§ç»­è¿­ä»£
                boolean isSequential = isSequentialReachable(sourceFlowElement, target, visitedElements);
                if (!isSequential) {
                    return false;
                }
            }
        }
        return true;
    }
    protected static boolean isInEventSubprocess(FlowElement flowElement) {
        FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
        while (flowElementsContainer != null) {
            if (flowElementsContainer instanceof EventSubProcess) {
                return true;
            }
            if (flowElementsContainer instanceof FlowElement) {
                flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
            } else {
                flowElementsContainer = null;
            }
        }
        return false;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.convert.Convert;
import org.ruoyi.flowable.core.FormConf;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹è¡¨å•工具类
 *
 * @author KonBAI
 * @createTime 2022/8/7 17:09
 */
public class ProcessFormUtils {
    private static final String CONFIG = "__config__";
    private static final String MODEL = "__vModel__";
    /**
     * å¡«å……表单项内容
     *
     * @param formConf è¡¨å•配置信息
     * @param data è¡¨å•内容
     */
    public static void fillFormData(FormConf formConf, Map<String, Object> data) {
        for (Map<String, Object> field : formConf.getFields()) {
            recursiveFillField(field, data);
        }
    }
    @SuppressWarnings("unchecked")
    private static void recursiveFillField(final Map<String, Object> field, final Map<String, Object> data) {
        if (!field.containsKey(CONFIG)) {
            return;
        }
        Map<String, Object> configMap = (Map<String, Object>) field.get(CONFIG);
        if (configMap.containsKey("children")) {
            List<Map<String, Object>> childrens = (List<Map<String, Object>>) configMap.get("children");
            for (Map<String, Object> children : childrens) {
                recursiveFillField(children, data);
            }
        }
        String modelKey = Convert.toStr(field.get(MODEL));
        Object value = data.get(modelKey);
        if (value != null) {
            configMap.put("defaultValue", value);
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,125 @@
package org.ruoyi.flowable.utils;
import org.ruoyi.common.core.utils.DateUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.flowable.common.engine.api.query.Query;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import java.util.Collections;
import java.util.Map;
/**
 * æµç¨‹å·¥å…·ç±»
 *
 * @author konbai
 * @since 2022/12/11 03:35
 */
public class ProcessUtils {
    public static void buildProcessSearch(Query<?, ?> query, ProcessQuery process) {
        if (query instanceof ProcessDefinitionQuery) {
            buildProcessDefinitionSearch((ProcessDefinitionQuery) query, process);
        } else if (query instanceof TaskQuery) {
            buildTaskSearch((TaskQuery) query, process);
        } else if (query instanceof HistoricTaskInstanceQuery) {
            buildHistoricTaskInstanceSearch((HistoricTaskInstanceQuery) query, process);
        } else if (query instanceof HistoricProcessInstanceQuery) {
            buildHistoricProcessInstanceSearch((HistoricProcessInstanceQuery) query, process);
        }
    }
    /**
     * æž„建流程定义搜索
     */
    public static void buildProcessDefinitionSearch(ProcessDefinitionQuery query, ProcessQuery process) {
        // æµç¨‹æ ‡è¯†
        if (StringUtils.isNotBlank(process.getProcessKey())) {
            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
        }
        // æµç¨‹åç§°
        if (StringUtils.isNotBlank(process.getProcessName())) {
            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
        }
        // æµç¨‹åˆ†ç±»
        if (StringUtils.isNotBlank(process.getCategory())) {
            query.processDefinitionCategory(process.getCategory());
        }
        // æµç¨‹çŠ¶æ€
        if (StringUtils.isNotBlank(process.getState())) {
            if (SuspensionState.ACTIVE.toString().equals(process.getState())) {
                query.active();
            } else if (SuspensionState.SUSPENDED.toString().equals(process.getState())) {
                query.suspended();
            }
        }
    }
    /**
     * æž„建任务搜索
     */
    public static void buildTaskSearch(TaskQuery query, ProcessQuery process) {
        Map<String, Object> params = process.getParams();
        if (StringUtils.isNotBlank(process.getProcessKey())) {
            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
        }
        if (StringUtils.isNotBlank(process.getProcessName())) {
            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
        }
        if (params.get("beginTime") != null && params.get("endTime") != null) {
            query.taskCreatedAfter(DateUtils.parseDate(params.get("beginTime")));
            query.taskCreatedBefore(DateUtils.parseDate(params.get("endTime")));
        }
        // æµç¨‹åˆ†ç±»
        if (StringUtils.isNotBlank(process.getCategory())) {
            query.processCategoryIn(Collections.singleton(process.getCategory()));
        }
    }
    private static void buildHistoricTaskInstanceSearch(HistoricTaskInstanceQuery query, ProcessQuery process) {
        Map<String, Object> params = process.getParams();
        if (StringUtils.isNotBlank(process.getProcessKey())) {
            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
        }
        if (StringUtils.isNotBlank(process.getProcessName())) {
            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
        }
        if (params.get("beginTime") != null && params.get("endTime") != null) {
            query.taskCompletedAfter(DateUtils.parseDate(params.get("beginTime")));
            query.taskCompletedBefore(DateUtils.parseDate(params.get("endTime")));
        }
        // æµç¨‹åˆ†ç±»
        if (StringUtils.isNotBlank(process.getCategory())) {
            query.processCategoryIn(Collections.singleton(process.getCategory()));
        }
    }
    /**
     * æž„建历史流程实例搜索
     */
    public static void buildHistoricProcessInstanceSearch(HistoricProcessInstanceQuery query, ProcessQuery process) {
        Map<String, Object> params = process.getParams();
        // æµç¨‹æ ‡è¯†
        if (StringUtils.isNotBlank(process.getProcessKey())) {
            query.processDefinitionKey(process.getProcessKey());
        }
        // æµç¨‹åç§°
        if (StringUtils.isNotBlank(process.getProcessName())) {
            query.processDefinitionName(process.getProcessName());
        }
        // æµç¨‹åç§°
        if (StringUtils.isNotBlank(process.getCategory())) {
            query.processDefinitionCategory(process.getCategory());
        }
        if (params.get("beginTime") != null && params.get("endTime") != null) {
            query.startedAfter(DateUtils.parseDate(params.get("beginTime")));
            query.startedBefore(DateUtils.parseDate(params.get("endTime")));
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,253 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import org.ruoyi.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
 * stream æµå·¥å…·ç±»
 *
 * @author Lion Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StreamUtils {
    /**
     * å°†collection过滤
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param function   è¿‡æ»¤æ–¹æ³•
     * @return è¿‡æ»¤åŽçš„list
     */
    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
        if (CollUtil.isEmpty(collection)) {
            return CollUtil.newArrayList();
        }
        return collection.stream().filter(function).collect(Collectors.toList());
    }
    /**
     * å°†collection拼接
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param function   æ‹¼æŽ¥æ–¹æ³•
     * @return æ‹¼æŽ¥åŽçš„list
     */
    public static <E> String join(Collection<E> collection, Function<E, String> function) {
        return join(collection, function, ",");
    }
    /**
     * å°†collection拼接
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param function   æ‹¼æŽ¥æ–¹æ³•
     * @param delimiter  æ‹¼æŽ¥ç¬¦
     * @return æ‹¼æŽ¥åŽçš„list
     */
    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
        if (CollUtil.isEmpty(collection)) {
            return StringUtils.EMPTY;
        }
        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
    }
    /**
     * å°†collection排序
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param comparing  æŽ’序方法
     * @return æŽ’序后的list
     */
    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
        if (CollUtil.isEmpty(collection)) {
            return CollUtil.newArrayList();
        }
        return collection.stream().sorted(comparing).collect(Collectors.toList());
    }
    /**
     * å°†collection转化为类型不变的map<br>
     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param key        V类型转化为K类型的lambda方法
     * @param <V>        collection中的泛型
     * @param <K>        map中的key类型
     * @return è½¬åŒ–后的map
     */
    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
        if (CollUtil.isEmpty(collection)) {
            return MapUtil.newHashMap();
        }
        return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
    }
    /**
     * å°†Collection转化为map(value类型与collection的泛型不同)<br>
     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param key        E类型转化为K类型的lambda方法
     * @param value      E类型转化为V类型的lambda方法
     * @param <E>        collection中的泛型
     * @param <K>        map中的key类型
     * @param <V>        map中的value类型
     * @return è½¬åŒ–后的map
     */
    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
        if (CollUtil.isEmpty(collection)) {
            return MapUtil.newHashMap();
        }
        return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
    }
    /**
     * å°†collection按照规则(比如有相同的班级id)分类成map<br>
     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
     *
     * @param collection éœ€è¦åˆ†ç±»çš„集合
     * @param key        åˆ†ç±»çš„规则
     * @param <E>        collection中的泛型
     * @param <K>        map中的key类型
     * @return åˆ†ç±»åŽçš„map
     */
    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
        if (CollUtil.isEmpty(collection)) {
            return MapUtil.newHashMap();
        }
        return collection
                .stream()
                .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
    }
    /**
     * å°†collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
     *
     * @param collection éœ€è¦åˆ†ç±»çš„集合
     * @param key1       ç¬¬ä¸€ä¸ªåˆ†ç±»çš„规则
     * @param key2       ç¬¬äºŒä¸ªåˆ†ç±»çš„规则
     * @param <E>        é›†åˆå…ƒç´ ç±»åž‹
     * @param <K>        ç¬¬ä¸€ä¸ªmap中的key类型
     * @param <U>        ç¬¬äºŒä¸ªmap中的key类型
     * @return åˆ†ç±»åŽçš„map
     */
    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
        if (CollUtil.isEmpty(collection)) {
            return MapUtil.newHashMap();
        }
        return collection
                .stream()
                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
    }
    /**
     * å°†collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
     *
     * @param collection éœ€è¦åˆ†ç±»çš„集合
     * @param key1       ç¬¬ä¸€ä¸ªåˆ†ç±»çš„规则
     * @param key2       ç¬¬äºŒä¸ªåˆ†ç±»çš„规则
     * @param <T>        ç¬¬ä¸€ä¸ªmap中的key类型
     * @param <U>        ç¬¬äºŒä¸ªmap中的key类型
     * @param <E>        collection中的泛型
     * @return åˆ†ç±»åŽçš„map
     */
    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
        if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
            return MapUtil.newHashMap();
        }
        return collection
                .stream()
                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
    }
    /**
     * å°†collection转化为List集合,但是两者的泛型不同<br>
     * <B>{@code Collection<E>  ------>  List<T> } </B>
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param function   collection中的泛型转化为list泛型的lambda表达式
     * @param <E>        collection中的泛型
     * @param <T>        List中的泛型
     * @return è½¬åŒ–后的list
     */
    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
        if (CollUtil.isEmpty(collection)) {
            return CollUtil.newArrayList();
        }
        return collection
                .stream()
                .map(function)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
    /**
     * å°†collection转化为Set集合,但是两者的泛型不同<br>
     * <B>{@code Collection<E>  ------>  Set<T> } </B>
     *
     * @param collection éœ€è¦è½¬åŒ–的集合
     * @param function   collection中的泛型转化为set泛型的lambda表达式
     * @param <E>        collection中的泛型
     * @param <T>        Set中的泛型
     * @return è½¬åŒ–后的Set
     */
    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
        if (CollUtil.isEmpty(collection) || function == null) {
            return CollUtil.newHashSet();
        }
        return collection
                .stream()
                .map(function)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }
    /**
     * åˆå¹¶ä¸¤ä¸ªç›¸åŒkey类型的map
     *
     * @param map1  ç¬¬ä¸€ä¸ªéœ€è¦åˆå¹¶çš„ map
     * @param map2  ç¬¬äºŒä¸ªéœ€è¦åˆå¹¶çš„ map
     * @param merge åˆå¹¶çš„lambda,将key  value1 value2合并成最终的类型,注意value可能为空的情况
     * @param <K>   map中的key类型
     * @param <X>   ç¬¬ä¸€ä¸ª map的value类型
     * @param <Y>   ç¬¬äºŒä¸ª map的value类型
     * @param <V>   æœ€ç»ˆmap的value类型
     * @return åˆå¹¶åŽçš„map
     */
    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
        if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
            return MapUtil.newHashMap();
        } else if (MapUtil.isEmpty(map1)) {
            map1 = MapUtil.newHashMap();
        } else if (MapUtil.isEmpty(map2)) {
            map2 = MapUtil.newHashMap();
        }
        Set<K> key = new HashSet<>();
        key.addAll(map1.keySet());
        key.addAll(map2.keySet());
        Map<K, V> map = new HashMap<>();
        for (K t : key) {
            X x = map1.get(t);
            Y y = map2.get(t);
            V z = merge.apply(x, y);
            if (z != null) {
                map.put(t, z);
            }
        }
        return map;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package org.ruoyi.flowable.utils;
import cn.hutool.core.util.ObjectUtil;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.flowable.common.constant.TaskConstants;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
/**
 * å·¥ä½œæµä»»åŠ¡å·¥å…·ç±»
 *
 * @author konbai
 * @createTime 2022/4/24 12:42
 */
public class TaskUtils {
    public static String getUserId() {
        LoginUser user = LoginHelper.getLoginUser();
        if (ObjectUtil.isNotNull(user)) {
            return  String.valueOf(user.getUserId());
        }
        return "";
    }
    /**
     * èŽ·å–ç”¨æˆ·ç»„ä¿¡æ¯
     *
     * @return candidateGroup
     */
    public static List<String> getCandidateGroup() {
        List<String> list = new ArrayList<>();
        LoginUser user = LoginHelper.getLoginUser();
        if (ObjectUtil.isNotNull(user)) {
            if (ObjectUtil.isNotEmpty(user.getRoles())) {
                user.getRoles().forEach(role -> list.add(TaskConstants.ROLE_GROUP_PREFIX + role.getRoleId() ));
            }
            if (ObjectUtil.isNotNull(user.getDeptId())) {
                list.add(TaskConstants.DEPT_GROUP_PREFIX + user.getDeptId());
            }
        }
        return list;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
package org.ruoyi.flowable.workflow.controller;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.WfCategory;
import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
import org.ruoyi.flowable.workflow.service.IWfCategoryService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import org.ruoyi.common.excel.utils.ExcelUtil;
/**
 * æµç¨‹åˆ†ç±»Controller
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/category")
public class WfCategoryController extends BaseController {
    private final IWfCategoryService categoryService;
    /**
     * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
     */
    @GetMapping("/list")
    public TableDataInfo<WfCategoryVo> list(WfCategory category) {
        List<WfCategoryVo> list = categoryService.queryList(category);
        return TableDataInfo.build(list);
    }
    /**
     * æŸ¥è¯¢å…¨éƒ¨çš„æµç¨‹åˆ†ç±»åˆ—表
     */
    @GetMapping("/listAll")
    public R<List<WfCategoryVo>> listAll(WfCategory category) {
        return R.ok(categoryService.queryList(category));
    }
    /**
     * å¯¼å‡ºæµç¨‹åˆ†ç±»åˆ—表
     */
    @PostMapping("/export")
    public void export(@Validated WfCategory category, HttpServletResponse response) {
        List<WfCategoryVo> list = categoryService.queryList(category);
        List<WfCategoryVo> util = MapstructUtils.convert(list,WfCategoryVo.class);
        ExcelUtil.exportExcel(util, "流程分类", WfCategoryVo.class, response);
    }
    /**
     * èŽ·å–æµç¨‹åˆ†ç±»è¯¦ç»†ä¿¡æ¯
     * @param categoryId åˆ†ç±»ä¸»é”®
     */
    @GetMapping("/{categoryId}")
    public R<WfCategoryVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable("categoryId") Long categoryId) {
        return R.ok(categoryService.queryById(categoryId));
    }
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     */
    @PostMapping
    public R add(@Validated @RequestBody WfCategory category) {
        if (!categoryService.checkCategoryCodeUnique(category)) {
            return R.fail("新增流程分类'" + category.getCategoryName() + "'失败,流程编码已存在");
        }
        return R.ok(categoryService.insertCategory(category));
    }
    /**
     * ä¿®æ”¹æµç¨‹åˆ†ç±»
     */
    @PutMapping()
    public R edit(@Validated @RequestBody WfCategory category) {
        if (!categoryService.checkCategoryCodeUnique(category)) {
            return R.fail("修改流程分类'" + category.getCategoryName() + "'失败,流程编码已存在");
        }
        return toAjax(categoryService.updateCategory(category));
    }
    /**
     * åˆ é™¤æµç¨‹åˆ†ç±»
     * @param categoryIds åˆ†ç±»ä¸»é”®ä¸²
     */
    @DeleteMapping("/{categoryIds}")
    public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] categoryIds) {
        return toAjax(categoryService.deleteWithValidByIds(Arrays.asList(categoryIds), true));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
package org.ruoyi.flowable.workflow.controller;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.utils.JsonUtils;
import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
import org.ruoyi.flowable.workflow.service.IWfDeployService;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
/**
 * æµç¨‹éƒ¨ç½²
 *
 * @author KonBAI
 * @createTime 2022/3/24 20:57
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/deploy")
public class WfDeployController extends BaseController {
    private final IWfDeployService deployService;
    private final IWfDeployFormService deployFormService;
    /**
     * æŸ¥è¯¢æµç¨‹éƒ¨ç½²åˆ—表
     */
    @GetMapping("/list")
    public TableDataInfo<WfDeployVo> list(ProcessQuery processQuery, PageQuery pageQuery) {
        return deployService.queryPageList(processQuery, pageQuery);
    }
    /**
     * æŸ¥è¯¢æµç¨‹éƒ¨ç½²ç‰ˆæœ¬åˆ—表
     */
    @GetMapping("/publishList")
    public TableDataInfo<WfDeployVo> publishList(@RequestParam String processKey, PageQuery pageQuery) {
        return deployService.queryPublishList(processKey, pageQuery);
    }
    /**
     * æ¿€æ´»æˆ–挂起流程
     *
     * @param state çŠ¶æ€ï¼ˆactive:激活 suspended:挂起)
     * @param definitionId æµç¨‹å®šä¹‰ID
     */
    @PutMapping(value = "/changeState")
    public R<Void> changeState(@RequestParam String state, @RequestParam String definitionId) {
        deployService.updateState(definitionId, state);
        return R.ok("操作成功");
    }
    /**
     * è¯»å–xml文件
     * @param definitionId æµç¨‹å®šä¹‰ID
     * @return
     */
    @GetMapping("/bpmnXml/{definitionId}")
    public R<String> getBpmnXml(@PathVariable(value = "definitionId") String definitionId) {
        return R.ok( deployService.queryBpmnXmlById(definitionId),null);
    }
    /**
     * åˆ é™¤æµç¨‹æ¨¡åž‹
     * @param deployIds æµç¨‹éƒ¨ç½²ids
     */
    @DeleteMapping("/{deployIds}")
    public R<String> remove(@NotEmpty(message = "主键不能为空") @PathVariable String[] deployIds) {
        deployService.deleteByIds(Arrays.asList(deployIds));
        return R.ok(null,"操作成功");
    }
    /**
     * æŸ¥è¯¢æµç¨‹éƒ¨ç½²å…³è”表单信息
     *
     * @param deployId æµç¨‹éƒ¨ç½²id
     */
    @GetMapping("/form/{deployId}")
    public R<?> start(@PathVariable(value = "deployId") String deployId) {
        WfFormVo formVo = deployFormService.selectDeployFormByDeployId(deployId);
        if (Objects.isNull(formVo)) {
            return R.fail("请先配置流程表单");
        }
        return R.ok(JsonUtils.parseObject(formVo.getContent(), Map.class));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package org.ruoyi.flowable.workflow.controller;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.validate.QueryGroup;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
import org.ruoyi.flowable.workflow.service.IWfFormService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
 * æµç¨‹è¡¨å•Controller
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@RequiredArgsConstructor
@RestController
@RequestMapping("/form")
public class WfFormController extends BaseController {
    private final IWfFormService formService;
    private final IWfDeployFormService deployFormService;
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•列表
     */
    @GetMapping("/list")
    public TableDataInfo<WfFormVo> list(@Validated(QueryGroup.class) WfFormBo bo, PageQuery pageQuery) {
        return formService.queryPageList(bo, pageQuery);
    }
    /**
     * å¯¼å‡ºæµç¨‹è¡¨å•列表
     */
    @PostMapping("/export")
    public void export(@Validated WfFormBo bo, HttpServletResponse response) {
        List<WfFormVo> list = formService.queryList(bo);
        List<WfFormVo> convert = MapstructUtils.convert(list, WfFormVo.class);
        ExcelUtil.exportExcel( convert, "流程表单", WfFormVo.class, response);
    }
    /**
     * èŽ·å–æµç¨‹è¡¨å•è¯¦ç»†ä¿¡æ¯
     * @param formId ä¸»é”®
     */
    @GetMapping(value = "/{formId}")
    public R getInfo(@NotNull(message = "主键不能为空") @PathVariable("formId") Long formId) {
        return R.ok(formService.queryById(formId));
    }
    /**
     * æ–°å¢žæµç¨‹è¡¨å•
     */
    @PostMapping
    public R add(@RequestBody WfFormBo bo) {
        return toAjax(formService.insertForm(bo));
    }
    /**
     * ä¿®æ”¹æµç¨‹è¡¨å•
     */
    @PutMapping
    public R edit(@RequestBody WfFormBo bo) {
        return toAjax(formService.updateForm(bo));
    }
    /**
     * åˆ é™¤æµç¨‹è¡¨å•
     * @param formIds ä¸»é”®ä¸²
     */
    @DeleteMapping("/{formIds}")
    public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] formIds) {
        return toAjax(formService.deleteWithValidByIds(Arrays.asList(formIds)) ? 1 : 0);
    }
    /**
     * æŒ‚载流程表单
     */
    @PostMapping("/addDeployForm")
    public R addDeployForm(@RequestBody WfDeployForm deployForm) {
        return toAjax(deployFormService.insertWfDeployForm(deployForm));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package org.ruoyi.flowable.workflow.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.service.IWfInstanceService;
import org.springframework.web.bind.annotation.*;
/**
 * å·¥ä½œæµæµç¨‹å®žä¾‹ç®¡ç†
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/instance")
public class WfInstanceController {
    private final IWfInstanceService instanceService;
    /**
     * æ¿€æ´»æˆ–挂起流程实例
     *
     * @param state 1:激活,2:挂起
     * @param instanceId æµç¨‹å®žä¾‹ID
     */
    @PostMapping(value = "/updateState")
    public R updateState(@RequestParam Integer state, @RequestParam String instanceId) {
        instanceService.updateState(state, instanceId);
        return R.ok(null,"操作成功");
    }
    /**
     * ç»“束流程实例
     *
     * @param bo æµç¨‹ä»»åŠ¡ä¸šåŠ¡å¯¹è±¡
     */
    @PostMapping(value = "/stopProcessInstance")
    public R stopProcessInstance(@RequestBody WfTaskBo bo) {
        instanceService.stopProcessInstance(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * åˆ é™¤æµç¨‹å®žä¾‹
     *
     * @param instanceId æµç¨‹å®žä¾‹ID
     * @param deleteReason åˆ é™¤åŽŸå› 
     */
    @Deprecated
    @DeleteMapping(value = "/delete")
    public R delete(@RequestParam String instanceId, String deleteReason) {
        instanceService.delete(instanceId, deleteReason);
        return R.ok(null,"操作成功");
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®žä¾‹è¯¦æƒ…信息
     *
     * @param procInsId æµç¨‹å®žä¾‹ID
     * @param deployId æµç¨‹éƒ¨ç½²ID
     */
    @GetMapping("/detail")
    public R detail(String procInsId, String deployId) {
        return R.ok(instanceService.queryDetailProcess(procInsId, deployId));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
package org.ruoyi.flowable.workflow.controller;
import cn.hutool.core.bean.BeanUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.WfCategory;
import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
import org.ruoyi.flowable.workflow.domain.vo.WfModelExportVo;
import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
import org.ruoyi.flowable.workflow.service.IWfCategoryService;
import org.ruoyi.flowable.workflow.service.IWfModelService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * å·¥ä½œæµæµç¨‹æ¨¡åž‹ç®¡ç†
 *
 * @author KonBAI
 * @createTime 2022/6/21 9:09
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/model")
public class WfModelController extends BaseController {
    private final IWfModelService modelService;
    private final IWfCategoryService categoryService;
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹åˆ—表
     *
     * @param modelBo æµç¨‹æ¨¡åž‹å¯¹è±¡
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping("/list")
    public TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery) {
        return modelService.list(modelBo, pageQuery);
    }
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹åˆ—表
     *
     * @param modelBo æµç¨‹æ¨¡åž‹å¯¹è±¡
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping("/historyList")
    public TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery) {
        return modelService.historyList(modelBo, pageQuery);
    }
    /**
     * èŽ·å–æµç¨‹æ¨¡åž‹è¯¦ç»†ä¿¡æ¯
     *
     * @param modelId æ¨¡åž‹ä¸»é”®
     */
    @GetMapping(value = "/{modelId}")
    public R<WfModelVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable("modelId") String modelId) {
        return R.ok(modelService.getModel(modelId));
    }
    /**
     * èŽ·å–æµç¨‹è¡¨å•è¯¦ç»†ä¿¡æ¯
     *
     * @param modelId æ¨¡åž‹ä¸»é”®
     */
    @GetMapping(value = "/bpmnXml/{modelId}")
    public R<String> getBpmnXml(@NotNull(message = "主键不能为空") @PathVariable("modelId") String modelId) {
        return R.ok(modelService.queryBpmnXmlById(modelId),"操作成功");
    }
    /**
     * æ–°å¢žæµç¨‹æ¨¡åž‹
     */
    @PostMapping
    public R<Void> add(@Validated(AddGroup.class) @RequestBody WfModelBo modelBo) {
        modelService.insertModel(modelBo);
        return R.ok("操作成功");
    }
    /**
     * ä¿®æ”¹æµç¨‹æ¨¡åž‹
     */
    @PutMapping
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody WfModelBo modelBo) {
        modelService.updateModel(modelBo);
        return R.ok("操作成功");
    }
    /**
     * ä¿å­˜æµç¨‹æ¨¡åž‹
     */
    @PostMapping("/save")
    public R<String> save(@RequestBody WfModelBo modelBo) {
        modelService.saveModel(modelBo);
        return R.ok(null,"操作成功");
    }
    /**
     * è®¾ä¸ºæœ€æ–°æµç¨‹æ¨¡åž‹
     * @param modelId
     * @return
     */
    @PostMapping("/latest")
    public R<?> latest(@RequestParam String modelId) {
        modelService.latestModel(modelId);
        return R.ok(null,"操作成功");
    }
    /**
     * åˆ é™¤æµç¨‹æ¨¡åž‹
     *
     * @param modelIds æµç¨‹æ¨¡åž‹ä¸»é”®ä¸²
     */
    @DeleteMapping("/{modelIds}")
    public R<String> remove(@NotEmpty(message = "主键不能为空") @PathVariable String[] modelIds) {
        modelService.deleteByIds(Arrays.asList(modelIds));
        return R.ok(null,"操作成功");
    }
    /**
     * éƒ¨ç½²æµç¨‹æ¨¡åž‹
     *
     * @param modelId æµç¨‹æ¨¡åž‹ä¸»é”®
     */
    @PostMapping("/deploy")
    public R deployModel(@RequestParam String modelId) {
        return toAjax(modelService.deployModel(modelId));
    }
    /**
     * å¯¼å‡ºæµç¨‹æ¨¡åž‹æ•°æ®
     */
    @PostMapping("/export")
    public void export(WfModelBo modelBo, HttpServletResponse response) {
        List<WfModelVo> list =  modelService.list(modelBo);
        List<WfModelExportVo> listVo = BeanUtil.copyToList(list, WfModelExportVo.class);
        List<WfCategoryVo> categoryVos = categoryService.queryList(new WfCategory());
        Map<String, String> categoryMap = categoryVos.stream()
                .collect(Collectors.toMap(WfCategoryVo::getCode, WfCategoryVo::getCategoryName));
        for (WfModelExportVo exportVo : listVo) {
            exportVo.setCategoryName(categoryMap.get(exportVo.getCategory()));
        }
        List<WfModelExportVo> convert = MapstructUtils.convert(listVo, WfModelExportVo.class);
        ExcelUtil.exportExcel(convert, "流程模型数据", WfModelExportVo.class, response);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,239 @@
package org.ruoyi.flowable.workflow.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
import org.ruoyi.flowable.workflow.domain.vo.*;
import org.ruoyi.flowable.workflow.service.IWfCopyService;
import org.ruoyi.flowable.workflow.service.IWfProcessService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
 * å·¥ä½œæµæµç¨‹ç®¡ç†
 *
 * @author KonBAI
 * @createTime 2022/3/24 18:54
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/process")
public class WfProcessController extends BaseController {
    private final IWfProcessService processService;
    private final IWfCopyService copyService;
    /**
     * æŸ¥è¯¢å¯å‘起流程列表
     *
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping(value = "/list")
    public TableDataInfo<WfDefinitionVo> startProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        return processService.selectPageStartProcessList(processQuery, pageQuery);
    }
    /**
     * æˆ‘拥有的流程
     */
    @GetMapping(value = "/ownList")
    public TableDataInfo<WfTaskVo> ownProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        return processService.selectPageOwnProcessList(processQuery, pageQuery);
    }
    /**
     * èŽ·å–å¾…åŠžåˆ—è¡¨
     */
    @GetMapping(value = "/todoList")
    public TableDataInfo<WfTaskVo> todoProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        return processService.selectPageTodoProcessList(processQuery, pageQuery);
    }
    /**
     * èŽ·å–å¾…ç­¾åˆ—è¡¨
     *
     * @param processQuery æµç¨‹ä¸šåŠ¡å¯¹è±¡
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping(value = "/claimList")
    public TableDataInfo<WfTaskVo> claimProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        return processService.selectPageClaimProcessList(processQuery, pageQuery);
    }
    /**
     * èŽ·å–å·²åŠžåˆ—è¡¨
     *
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping(value = "/finishedList")
    public TableDataInfo<WfTaskVo> finishedProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        return processService.selectPageFinishedProcessList(processQuery, pageQuery);
    }
    /**
     * èŽ·å–æŠ„é€åˆ—è¡¨
     *
     * @param copyBo æµç¨‹æŠ„送对象
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    @GetMapping(value = "/copyList")
    public TableDataInfo<WfCopyVo> copyProcessList(WfCopyBo copyBo, PageQuery pageQuery) {
        // èŽ·å–å½“å‰çš„ç”¨æˆ·
        LoginUser loginUser = LoginHelper.getLoginUser();
        if (ObjectUtil.isNotNull(loginUser))
        {
            copyBo.setUserId(loginUser.getUserId());
        }
        return copyService.selectPageList(copyBo, pageQuery);
    }
    /**
     * å¯¼å‡ºå¯å‘起流程列表
     */
    @PostMapping("/startExport")
    public void startExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
        List<WfDefinitionVo> list = processService.selectStartProcessList(processQuery);
        List<WfDefinitionVo> convert = MapstructUtils.convert(list, WfDefinitionVo.class);
        ExcelUtil.exportExcel(convert, "可发起流程", WfDefinitionVo.class, response);
    }
    /**
     * å¯¼å‡ºæˆ‘拥有流程列表
     */
    @PostMapping("/ownExport")
    public void ownExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
        List<WfTaskVo> list = processService.selectOwnProcessList(processQuery);
        List<WfOwnTaskExportVo> listVo = BeanUtil.copyToList(list, WfOwnTaskExportVo.class);
        for (WfOwnTaskExportVo exportVo : listVo) {
            exportVo.setStatus(ObjectUtil.isNull(exportVo.getFinishTime()) ? "进行中" : "已完成");
        }
        List<WfOwnTaskExportVo> convert = MapstructUtils.convert(listVo, WfOwnTaskExportVo.class);
        ExcelUtil.exportExcel(convert, "我拥有的流程", WfOwnTaskExportVo.class, response);
    }
    /**
     * å¯¼å‡ºå¾…办流程列表
     */
    @PostMapping("/todoExport")
    public void todoExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
        List<WfTaskVo> list = processService.selectTodoProcessList(processQuery);
        List<WfTodoTaskExportVo> listVo = BeanUtil.copyToList(list, WfTodoTaskExportVo.class);
        List<WfTodoTaskExportVo> convert = MapstructUtils.convert(listVo, WfTodoTaskExportVo.class);
        ExcelUtil.exportExcel(convert, "待办流程", WfTodoTaskExportVo.class, response);
    }
    /**
     * å¯¼å‡ºå¾…签流程列表
     */
    @PostMapping("/claimExport")
    public void claimExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
        List<WfTaskVo> list = processService.selectClaimProcessList(processQuery);
        List<WfClaimTaskExportVo> listVo = BeanUtil.copyToList(list, WfClaimTaskExportVo.class);
        List<WfClaimTaskExportVo> convert = MapstructUtils.convert(listVo, WfClaimTaskExportVo.class);
        ExcelUtil.exportExcel(convert, "待签流程", WfClaimTaskExportVo.class, response);
    }
    /**
     * å¯¼å‡ºå·²åŠžæµç¨‹åˆ—è¡¨
     */
    @PostMapping("/finishedExport")
    public void finishedExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
        List<WfTaskVo> list = processService.selectFinishedProcessList(processQuery);
        List<WfFinishedTaskExportVo> listVo = BeanUtil.copyToList(list, WfFinishedTaskExportVo.class);
        List<WfFinishedTaskExportVo> convert = MapstructUtils.convert(listVo, WfFinishedTaskExportVo.class);
        ExcelUtil.exportExcel(convert, "已办流程", WfFinishedTaskExportVo.class, response);
    }
    /**
     * å¯¼å‡ºæŠ„送流程列表
     */
    @PostMapping("/copyExport")
    public void copyExport(WfCopyBo copyBo, HttpServletResponse response) {
        // èŽ·å–å½“å‰çš„ç”¨æˆ·
        LoginUser loginUser = LoginHelper.getLoginUser();
        if (ObjectUtil.isNotNull(loginUser))
        {
            copyBo.setUserId(loginUser.getUserId());
        }
        List<WfCopyVo> list = copyService.selectList(copyBo);
        List<WfCopyVo> convert = MapstructUtils.convert(list, WfCopyVo.class);
        ExcelUtil.exportExcel(convert, "抄送流程", WfCopyVo.class, response);
    }
    /**
     * æŸ¥è¯¢æµç¨‹éƒ¨ç½²å…³è”表单信息
     *
     * @param definitionId æµç¨‹å®šä¹‰id
     * @param deployId æµç¨‹éƒ¨ç½²id
     */
    @GetMapping("/getProcessForm")
    public R<?> getForm(@RequestParam(value = "definitionId") String definitionId,
                        @RequestParam(value = "deployId") String deployId,
                        @RequestParam(value = "procInsId", required = false) String procInsId) {
        return R.ok(processService.selectFormContent(definitionId, deployId, procInsId));
    }
    /**
     * æ ¹æ®æµç¨‹å®šä¹‰id启动流程实例
     *
     * @param processDefId æµç¨‹å®šä¹‰id
     * @param variables å˜é‡é›†åˆ,json对象
     */
    @PostMapping("/start/{processDefId}")
    public R start(@PathVariable(value = "processDefId") String processDefId, @RequestBody Map<String, Object> variables) {
        processService.startProcessByDefId(processDefId, variables);
        return R.ok("流程启动成功");
    }
    /**
     * åˆ é™¤æµç¨‹å®žä¾‹
     *
     * @param instanceIds æµç¨‹å®žä¾‹ID串
     */
    @DeleteMapping("/instance/{instanceIds}")
    public R<Void> delete(@PathVariable String[] instanceIds) {
        processService.deleteProcessByIds(instanceIds);
        return R.ok("操作成功");
    }
    /**
     * è¯»å–xml文件
     * @param processDefId æµç¨‹å®šä¹‰ID
     */
    @GetMapping("/bpmnXml/{processDefId}")
    public R<String> getBpmnXml(@PathVariable(value = "processDefId") String processDefId) {
        return R.ok(processService.queryBpmnXmlById(processDefId),null );
    }
    /**
     * æŸ¥è¯¢æµç¨‹è¯¦æƒ…信息
     *
     * @param procInsId æµç¨‹å®žä¾‹ID
     * @param taskId ä»»åŠ¡ID
     */
    @GetMapping("/detail")
    public R detail(String procInsId, String taskId) {
        return R.ok(processService.queryProcessDetail(procInsId, taskId));
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
package org.ruoyi.flowable.workflow.controller;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.service.IWfTaskService;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
 * å·¥ä½œæµä»»åŠ¡ç®¡ç†
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/task")
public class WfTaskController {
    private final IWfTaskService flowTaskService;
    /**
     * å–消流程
     */
    @PostMapping(value = "/stopProcess")
    public R stopProcess(@RequestBody WfTaskBo bo) {
        flowTaskService.stopProcess(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * æ’¤å›žæµç¨‹
     */
    @PostMapping(value = "/revokeProcess")
    public R revokeProcess(@RequestBody WfTaskBo bo) {
        flowTaskService.revokeProcess(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * èŽ·å–æµç¨‹å˜é‡
     * @param taskId æµç¨‹ä»»åŠ¡Id
     */
    @GetMapping(value = "/processVariables/{taskId}")
    public R processVariables(@PathVariable(value = "taskId") String taskId) {
        return R.ok(flowTaskService.getProcessVariables(taskId));
    }
    /**
     * å®¡æ‰¹ä»»åŠ¡
     */
    @PostMapping(value = "/complete")
    public R complete(@RequestBody WfTaskBo bo) {
        flowTaskService.complete(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * æ‹’绝任务
     */
    @PostMapping(value = "/reject")
    public R taskReject(@RequestBody WfTaskBo taskBo) {
        flowTaskService.taskReject(taskBo);
        return R.ok(null,"操作成功");
    }
    /**
     * é€€å›žä»»åŠ¡
     */
    @PostMapping(value = "/return")
    public R taskReturn(@RequestBody WfTaskBo bo) {
        flowTaskService.taskReturn(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * èŽ·å–æ‰€æœ‰å¯å›žé€€çš„èŠ‚ç‚¹
     */
    @PostMapping(value = "/returnList")
    public R findReturnTaskList(@RequestBody WfTaskBo bo) {
        return R.ok(flowTaskService.findReturnTaskList(bo));
    }
    /**
     * åˆ é™¤ä»»åŠ¡
     */
    @DeleteMapping(value = "/delete")
    public R delete(@RequestBody WfTaskBo bo) {
        flowTaskService.deleteTask(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * è®¤é¢†/签收任务
     */
    @PostMapping(value = "/claim")
    public R claim(@RequestBody WfTaskBo bo) {
        flowTaskService.claim(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * å–消认领/签收任务
     */
    @PostMapping(value = "/unClaim")
    public R unClaim(@RequestBody WfTaskBo bo) {
        flowTaskService.unClaim(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * å§”派任务
     */
    @PostMapping(value = "/delegate")
    public R delegate(@RequestBody WfTaskBo bo) {
        if (ObjectUtil.hasNull(bo.getTaskId(), bo.getUserId())) {
            return R.fail("参数错误!");
        }
        flowTaskService.delegateTask(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * è½¬åŠžä»»åŠ¡
     */
    @PostMapping(value = "/transfer")
    public R transfer(@RequestBody WfTaskBo bo) {
        if (ObjectUtil.hasNull(bo.getTaskId(), bo.getUserId())) {
            return R.fail("参数错误!");
        }
        flowTaskService.transferTask(bo);
        return R.ok(null,"操作成功");
    }
    /**
     * ç”Ÿæˆæµç¨‹å›¾
     *
     * @param processId ä»»åŠ¡ID
     */
    @RequestMapping("/diagram/{processId}")
    public void genProcessDiagram(HttpServletResponse response,
                                  @PathVariable("processId") String processId) {
        InputStream inputStream = flowTaskService.diagram(processId);
        OutputStream os = null;
        BufferedImage image = null;
        try {
            image = ImageIO.read(inputStream);
            response.setContentType("image/png");
            os = response.getOutputStream();
            if (image != null) {
                ImageIO.write(image, "png", os);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.flush();
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.ruoyi.flowable.workflow.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
/**
 * æµç¨‹åˆ†ç±»å¯¹è±¡ wf_category
 *
 * @author KonBAI
 * @date 2022-01-15
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_category")
public class WfCategory extends BaseEntity {
    private static final long serialVersionUID=1L;
    /**
     * åˆ†ç±»ID
     */
    @TableId(value = "category_id", type = IdType.AUTO)
    private Long categoryId;
    /**
     * åˆ†ç±»åç§°
     */
    @NotBlank(message = "分类名称不能为空")
    private String categoryName;
    /**
     * åˆ†ç±»ç¼–码
     */
    @NotBlank(message = "分类编码不能为空")
    private String code;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     */
    @TableLogic
    private String delFlag;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package org.ruoyi.flowable.workflow.domain;
import com.baomidou.mybatisplus.annotation.IdType;
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.ruoyi.core.domain.BaseEntity;
/**
 * æµç¨‹æŠ„送对象 wf_copy
 *
 * @author KonBAI
 * @date 2022-05-19
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_copy")
public class WfCopy extends BaseEntity {
    private static final long serialVersionUID=1L;
    /**
     * æŠ„送主键
     */
    @TableId(value = "copy_id", type = IdType.AUTO)
    private Long copyId;
    /**
     * æŠ„送标题
     */
    private String title;
    /**
     * æµç¨‹ä¸»é”®
     */
    private String processId;
    /**
     * æµç¨‹åç§°
     */
    private String processName;
    /**
     * æµç¨‹åˆ†ç±»ä¸»é”®
     */
    private String categoryId;
    /**
     * éƒ¨ç½²ä¸»é”®
     */
    private String deploymentId;
    /**
     * æµç¨‹å®žä¾‹ä¸»é”®
     */
    private String instanceId;
    /**
     * ä»»åС䏻键
     */
    private String taskId;
    /**
     * ç”¨æˆ·ä¸»é”®
     */
    private Long userId;
    /**
     * å‘起人Id
     */
    private Long originatorId;
    /**
     * å‘起人名称
     */
    private String originatorName;
    /**
     * åˆ é™¤æ ‡å¿—(0代表存在 2代表删除)
     */
    @TableLogic
    private String delFlag;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.ruoyi.flowable.workflow.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
 * æµç¨‹å®žä¾‹å…³è”表单对象 sys_instance_form
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Data
@TableName("wf_deploy_form")
public class WfDeployForm {
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹éƒ¨ç½²ä¸»é”®
     */
    private String deployId;
    /**
     * è¡¨å•Key
     */
    private String formKey;
    /**
     * èŠ‚ç‚¹Key
     */
    private String nodeKey;
    /**
     * è¡¨å•名称
     */
    private String formName;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * è¡¨å•内容
     */
    private String content;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package org.ruoyi.flowable.workflow.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
/**
 * æµç¨‹è¡¨å•对象 wf_form
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("wf_form")
public class WfForm extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * è¡¨å•主键
     */
    @TableId(value = "form_id", type = IdType.AUTO)
    private Long formId;
    /**
     * è¡¨å•名称
     */
    private String formName;
    /**
     * è¡¨å•内容
     */
    private String content;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
package org.ruoyi.flowable.workflow.domain.base;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * Entity基类
 *
 * @author ruoyi
 */
public class BaseEntity implements Serializable
{
    private static final long serialVersionUID = 1L;
    /** æœç´¢å€¼ */
    @JsonIgnore
    @TableField(exist = false)
    private String searchValue;
    /** åˆ›å»ºè€… */
    private String createBy;
    /** åˆ›å»ºæ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /** æ›´æ–°è€… */
    private String updateBy;
    /** æ›´æ–°æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    /** å¤‡æ³¨ */
    private String remark;
    /** è¯·æ±‚参数 */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @TableField(exist = false)
    private Map<String, Object> params;
    public String getSearchValue()
    {
        return searchValue;
    }
    public void setSearchValue(String searchValue)
    {
        this.searchValue = searchValue;
    }
    public String getCreateBy()
    {
        return createBy;
    }
    public void setCreateBy(String createBy)
    {
        this.createBy = createBy;
    }
    public Date getCreateTime()
    {
        return createTime;
    }
    public void setCreateTime(Date createTime)
    {
        this.createTime = createTime;
    }
    public String getUpdateBy()
    {
        return updateBy;
    }
    public void setUpdateBy(String updateBy)
    {
        this.updateBy = updateBy;
    }
    public Date getUpdateTime()
    {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime)
    {
        this.updateTime = updateTime;
    }
    public String getRemark()
    {
        return remark;
    }
    public void setRemark(String remark)
    {
        this.remark = remark;
    }
    public Map<String, Object> getParams()
    {
        if (params == null)
        {
            params = new HashMap<>();
        }
        return params;
    }
    public void setParams(Map<String, Object> params)
    {
        this.params = params;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package org.ruoyi.flowable.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.core.domain.BaseEntity;
/**
 * æµç¨‹æŠ„送业务对象 wf_copy
 *
 * @author ruoyi
 * @date 2022-05-19
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class WfCopyBo extends BaseEntity {
    /**
     * æŠ„送主键
     */
    @NotNull(message = "抄送主键不能为空", groups = { EditGroup.class })
    private Long copyId;
    /**
     * æŠ„送标题
     */
    @NotNull(message = "抄送标题不能为空", groups = { AddGroup.class, EditGroup.class })
    private String title;
    /**
     * æµç¨‹ä¸»é”®
     */
    @NotBlank(message = "流程主键不能为空", groups = { AddGroup.class, EditGroup.class })
    private String processId;
    /**
     * æµç¨‹åç§°
     */
    @NotBlank(message = "流程名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String processName;
    /**
     * æµç¨‹åˆ†ç±»ä¸»é”®
     */
    @NotBlank(message = "流程分类主键不能为空", groups = { AddGroup.class, EditGroup.class })
    private String categoryId;
    /**
     * ä»»åС䏻键
     */
    @NotBlank(message = "任务主键不能为空", groups = { AddGroup.class, EditGroup.class })
    private String taskId;
    /**
     * ç”¨æˆ·ä¸»é”®
     */
    @NotNull(message = "用户主键不能为空", groups = { AddGroup.class, EditGroup.class })
    private Long userId;
    /**
     * å‘起人Id
     */
    @NotNull(message = "发起人主键不能为空", groups = { AddGroup.class, EditGroup.class })
    private Long originatorId;
    /**
     * å‘起人名称
     */
    @NotNull(message = "发起人名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String originatorName;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package org.ruoyi.flowable.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
/**
 * æµç¨‹è®¾è®¡ä¸šåŠ¡å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Data
public class WfDesignerBo {
    /**
     * æµç¨‹åç§°
     */
    @NotNull(message = "流程名称", groups = { AddGroup.class, EditGroup.class })
    private String name;
    /**
     * æµç¨‹åˆ†ç±»
     */
    @NotBlank(message = "流程分类", groups = { AddGroup.class, EditGroup.class })
    private String category;
    /**
     * XML字符串
     */
    @NotBlank(message = "XML字符串", groups = { AddGroup.class, EditGroup.class })
    private String xml;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package org.ruoyi.flowable.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.core.domain.BaseEntity;
/**
 * æµç¨‹è¡¨å•业务对象
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class WfFormBo extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * è¡¨å•主键
     */
    @NotNull(message = "表单ID不能为空", groups = { EditGroup.class })
    private Long formId;
    /**
     * è¡¨å•名称
     */
    @NotBlank(message = "表单名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String formName;
    /**
     * è¡¨å•内容
     */
    @NotBlank(message = "表单内容不能为空", groups = { AddGroup.class, EditGroup.class })
    private String content;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package org.ruoyi.flowable.workflow.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
/**
 * æµç¨‹æ¨¡åž‹å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/6/21 9:16
 */
@Data
public class WfModelBo {
    /**
     * æ¨¡åž‹ä¸»é”®
     */
    @NotNull(message = "模型主键不能为空", groups = { EditGroup.class })
    private String modelId;
    /**
     * æ¨¡åž‹åç§°
     */
    @NotNull(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class })
    private String modelName;
    /**
     * æ¨¡åž‹Key
     */
    @NotNull(message = "模型Key不能为空", groups = { AddGroup.class, EditGroup.class })
    private String modelKey;
    /**
     * æµç¨‹åˆ†ç±»
     */
    @NotBlank(message = "流程分类不能为空", groups = { AddGroup.class, EditGroup.class })
    private String category;
    /**
     * æè¿°
     */
    private String description;
    /**
     * è¡¨å•类型
     */
    private Integer formType;
    /**
     * è¡¨å•主键
     */
    private Long formId;
    /**
     * æµç¨‹xml
     */
    private String bpmnXml;
    /**
     * æ˜¯å¦ä¿å­˜ä¸ºæ–°ç‰ˆæœ¬
     */
    private Boolean newVersion;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package org.ruoyi.flowable.workflow.domain.bo;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹ä»»åŠ¡ä¸šåŠ¡å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Data
public class WfTaskBo {
    /**
     * ä»»åŠ¡Id
     */
    private String taskId;
    /**
     * ä»»åŠ¡åç§°
     */
    private String taskName;
    /**
     * ç”¨æˆ·Id
     */
    private String userId;
    /**
     * ä»»åŠ¡æ„è§
     */
    private String comment;
    /**
     * æµç¨‹å®žä¾‹Id
     */
    private String procInsId;
    /**
     * èŠ‚ç‚¹
     */
    private String targetKey;
    /**
     * æµç¨‹å˜é‡ä¿¡æ¯
     */
    private Map<String, Object> variables;
    /**
     * å®¡æ‰¹äºº
     */
    private String assignee;
    /**
     * å€™é€‰äºº
     */
    private List<String> candidateUsers;
    /**
     * å®¡æ‰¹ç»„
     */
    private List<String> candidateGroups;
    /**
     * æŠ„送用户Id
     */
    private String copyUserIds;
    /**
     * ä¸‹ä¸€èŠ‚ç‚¹å®¡æ‰¹äºº
     */
    private String nextUserIds;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.flowable.workflow.domain.dto;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Data
@Builder
public class WfCommentDto implements Serializable {
    /**
     * æ„è§ç±»åˆ« 0 æ­£å¸¸æ„è§  1 é€€å›žæ„è§ 2 é©³å›žæ„è§
     */
    private String type;
    /**
     * æ„è§å†…容
     */
    private String comment;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package org.ruoyi.flowable.workflow.domain.dto;
import lombok.Data;
/**
 * @author KonBAI
 * @createTime 2022/6/21 9:16
 */
@Data
public class WfMetaInfoDto {
    /**
     * åˆ›å»ºè€…(username)
     */
    private String createUser;
    /**
     * æµç¨‹æè¿°
     */
    private String description;
    /**
     * è¡¨å•类型
     */
    private Integer formType;
    /**
     * è¡¨å•编号
     */
    private Long formId;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package org.ruoyi.flowable.workflow.domain.dto;
import lombok.Data;
import org.ruoyi.system.domain.SysRole;
import org.ruoyi.system.domain.SysUser;
import java.io.Serializable;
import java.util.List;
/**
 * åŠ¨æ€äººå‘˜ã€ç»„
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Data
public class WfNextDto implements Serializable {
    private String type;
    private String vars;
    private List<SysUser> userList;
    private List<SysRole> roleList;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
 * æµç¨‹åˆ†ç±»è§†å›¾å¯¹è±¡ flow_category
 *
 * @author KonBAI
 * @date 2022-01-15
 */
@Data
@ExcelIgnoreUnannotated
public class WfCategoryVo {
    private static final long serialVersionUID = 1L;
    /**
     * åˆ†ç±»ID
     */
    @ExcelProperty(value = "分类ID")
    private Long categoryId;
    /**
     * åˆ†ç±»åç§°
     */
    @ExcelProperty(value = "分类名称")
    private String categoryName;
    /**
     * åˆ†ç±»ç¼–码
     */
    @ExcelProperty(value = "分类编码")
    private String code;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * å¾…签流程对象导出VO
 *
 * @author konbai
 */
@Data
@NoArgsConstructor
public class WfClaimTaskExportVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ç¼–å·
     */
    @ExcelProperty(value = "任务编号")
    private String taskId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String procDefName;
    /**
     * ä»»åŠ¡èŠ‚ç‚¹
     */
    @ExcelProperty(value = "任务节点")
    private String taskName;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "流程版本")
    private int procDefVersion;
    /**
     * æµç¨‹å‘起人名称
     */
    @ExcelProperty(value = "流程发起人")
    private String startUserName;
    /**
     * æŽ¥æ”¶æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "接收时间")
    private Date createTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package org.ruoyi.flowable.workflow.domain.vo;
import lombok.Data;
import java.util.Date;
/**
 * æµç¨‹æ‰¹å¤è§†å›¾å¯¹è±¡
 *
 * @author konbai
 * @createTime 2022/4/4 02:03
 */
@Data
public class WfCommentVo {
    /**
     * å®¡æ‰¹ç±»åˆ«
     */
    private String type;
    /**
     * æ‰¹å¤å†…容
     */
    private String message;
    /**
     * æ‰¹å¤æ—¶é—´
     */
    private Date time;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
/**
 * æµç¨‹æŠ„送视图对象 wf_copy
 *
 * @author ruoyi
 * @date 2022-05-19
 */
@Data
@ExcelIgnoreUnannotated
public class WfCopyVo {
    private static final long serialVersionUID = 1L;
    /**
     * æŠ„送主键
     */
    @ExcelProperty(value = "抄送主键")
    private Long copyId;
    /**
     * æŠ„送标题
     */
    @ExcelProperty(value = "抄送标题")
    private String title;
    /**
     * æµç¨‹ä¸»é”®
     */
    @ExcelProperty(value = "流程主键")
    private String processId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String processName;
    /**
     * æµç¨‹åˆ†ç±»ä¸»é”®
     */
    @ExcelProperty(value = "流程分类主键")
    private String categoryId;
    /**
     * éƒ¨ç½²ä¸»é”®
     */
    @ExcelProperty(value = "部署主键")
    private String deploymentId;
    /**
     * æµç¨‹å®žä¾‹ä¸»é”®
     */
    @ExcelProperty(value = "流程实例主键")
    private String instanceId;
    /**
     * ä»»åС䏻键
     */
    @ExcelProperty(value = "任务主键")
    private String taskId;
    /**
     * ç”¨æˆ·ä¸»é”®
     */
    @ExcelProperty(value = "用户主键")
    private Long userId;
    /**
     * å‘起人Id
     */
    @ExcelProperty(value = "发起人主键")
    private Long originatorId;
    /**
     * å‘起人名称
     */
    @ExcelProperty(value = "发起人名称")
    private String originatorName;
    /**
     * æŠ„送时间(创建时间)
     */
    @ExcelProperty(value = "抄送时间")
    private Date createTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
/**
 * æµç¨‹å®šä¹‰è§†å›¾å¯¹è±¡ workflow_definition
 *
 * @author KonBAI
 * @date 2022-01-17
 */
@Data
@ExcelIgnoreUnannotated
public class WfDefinitionVo {
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰ID
     */
    @ExcelProperty(value = "流程定义ID")
    private String definitionId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String processName;
    /**
     * æµç¨‹Key
     */
    @ExcelProperty(value = "流程Key")
    private String processKey;
    /**
     * åˆ†ç±»ç¼–码
     */
    @ExcelProperty(value = "分类编码")
    private String category;
    /**
     * ç‰ˆæœ¬
     */
    @ExcelProperty(value = "版本")
    private Integer version;
    /**
     * è¡¨å•ID
     */
    @ExcelProperty(value = "表单ID")
    private Long formId;
    /**
     * è¡¨å•名称
     */
    @ExcelProperty(value = "表单名称")
    private String formName;
    /**
     * éƒ¨ç½²ID
     */
    @ExcelProperty(value = "部署ID")
    private String deploymentId;
    /**
     * æµç¨‹æ˜¯å¦æš‚停(true:挂起 false:激活 ï¼‰
     */
    @ExcelProperty(value = "流程是否挂起")
    private Boolean suspended;
    /**
     * éƒ¨ç½²æ—¶é—´
     */
    @ExcelProperty(value = "部署时间")
    private Date deploymentTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package org.ruoyi.flowable.workflow.domain.vo;
import lombok.Data;
/**
 * éƒ¨ç½²å®žä¾‹å’Œè¡¨å•关联视图对象
 *
 * @author KonBAI
 * @createTime 2022/7/17 18:29
 */
@Data
public class WfDeployFormVo {
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹éƒ¨ç½²ä¸»é”®
     */
    private String deployId;
    /**
     * è¡¨å•Key
     */
    private String formKey;
    /**
     * èŠ‚ç‚¹Key
     */
    private String nodeKey;
    /**
     * è¡¨å•名称
     */
    private String formName;
    /**
     * èŠ‚ç‚¹åç§°
     */
    private String nodeName;
    /**
     * è¡¨å•内容
     */
    private String content;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
/**
 * æµç¨‹éƒ¨ç½²è§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @date 2022-06-30
 */
@Data
@ExcelIgnoreUnannotated
public class WfDeployVo {
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®šä¹‰ID
     */
    @ExcelProperty(value = "流程定义ID")
    private String definitionId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String processName;
    /**
     * æµç¨‹Key
     */
    @ExcelProperty(value = "流程Key")
    private String processKey;
    /**
     * åˆ†ç±»ç¼–码
     */
    @ExcelProperty(value = "分类编码")
    private String category;
    /**
     * ç‰ˆæœ¬
     */
    private Integer version;
    /**
     * è¡¨å•ID
     */
    @ExcelProperty(value = "表单ID")
    private Long formId;
    /**
     * è¡¨å•名称
     */
    @ExcelProperty(value = "表单名称")
    private String formName;
    /**
     * éƒ¨ç½²ID
     */
    @ExcelProperty(value = "部署ID")
    private String deploymentId;
    /**
     * æµç¨‹å®šä¹‰çŠ¶æ€: 1:激活 , 2:中止
     */
    @ExcelProperty(value = "流程定义状态: 1:激活 , 2:中止")
    private Boolean suspended;
    /**
     * éƒ¨ç½²æ—¶é—´
     */
    @ExcelProperty(value = "部署时间")
    private Date deploymentTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package org.ruoyi.flowable.workflow.domain.vo;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
import org.ruoyi.flowable.core.FormConf;
import java.util.List;
/**
 * æµç¨‹è¯¦æƒ…视图对象
 *
 * @author KonBAI
 * @createTime 2022/8/7 15:01
 */
@Data
public class WfDetailVo {
    /**
     * ä»»åŠ¡è¡¨å•ä¿¡æ¯
     */
    private FormConf taskFormData;
    /**
     * åŽ†å²æµç¨‹èŠ‚ç‚¹ä¿¡æ¯
     */
    private List<WfProcNodeVo> historyProcNodeList;
    /**
     * æµç¨‹è¡¨å•列表
     */
    private List<FormConf> processFormList;
    /**
     * æµç¨‹XML
     */
    private String bpmnXml;
    private WfViewerVo flowViewer;
    /**
     * æ˜¯å¦å­˜åœ¨ä»»åŠ¡è¡¨å•ä¿¡æ¯
     * @return true:存在;false:不存在
     */
    public Boolean isExistTaskForm() {
        return ObjectUtil.isNotEmpty(this.taskFormData);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * å·²åŠžæµç¨‹å¯¹è±¡å¯¼å‡ºVO
 *
 * @author konbai
 */
@Data
@NoArgsConstructor
public class WfFinishedTaskExportVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ç¼–å·
     */
    @ExcelProperty(value = "任务编号")
    private String taskId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String procDefName;
    /**
     * ä»»åŠ¡èŠ‚ç‚¹
     */
    @ExcelProperty(value = "任务节点")
    private String taskName;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "流程版本")
    private int procDefVersion;
    /**
     * æµç¨‹å‘起人名称
     */
    @ExcelProperty(value = "流程发起人")
    private String startUserName;
    /**
     * æŽ¥æ”¶æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "接收时间")
    private Date createTime;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "审批时间")
    private Date finishTime;
    /**
     * ä»»åŠ¡è€—æ—¶
     */
    @ExcelProperty(value = "任务耗时")
    private String duration;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
 * æµç¨‹åˆ†ç±»è§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Data
@ExcelIgnoreUnannotated
public class WfFormVo {
    private static final long serialVersionUID = 1L;
    /**
     * è¡¨å•主键
     */
    @ExcelProperty(value = "表单ID")
    private Long formId;
    /**
     * è¡¨å•名称
     */
    @ExcelProperty(value = "表单名称")
    private String formName;
    /**
     * è¡¨å•内容
     */
    @ExcelProperty(value = "表单内容")
    private String content;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * æµç¨‹æ¨¡åž‹å¯¹è±¡å¯¼å‡ºVO
 *
 * @author konbai
 */
@Data
@NoArgsConstructor
public class WfModelExportVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ¨¡åž‹ID
     */
    @ExcelProperty(value = "模型ID")
    private String modelId;
    /**
     * æ¨¡åž‹Key
     */
    @ExcelProperty(value = "模型Key")
    private String modelKey;
    /**
     * æ¨¡åž‹åç§°
     */
    @ExcelProperty(value = "模型名称")
    private String modelName;
    /**
     * åˆ†ç±»ç¼–码
     */
    @ExcelProperty(value = "分类编码")
    private String category;
    /**
     * æµç¨‹åˆ†ç±»
     */
    @ExcelProperty(value = "流程分类")
    private String categoryName;
    /**
     * æ¨¡åž‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "模型版本")
    private Integer version;
    /**
     * æ¨¡åž‹æè¿°
     */
    @ExcelProperty(value = "模型描述")
    private String description;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ExcelProperty(value = "创建时间")
    private Date createTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package org.ruoyi.flowable.workflow.domain.vo;
import lombok.Data;
import java.util.Date;
/**
 * æµç¨‹æ¨¡åž‹è§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/6/21 9:16
 */
@Data
public class WfModelVo {
    /**
     * æ¨¡åž‹ID
     */
    private String modelId;
    /**
     * æ¨¡åž‹åç§°
     */
    private String modelName;
    /**
     * æ¨¡åž‹Key
     */
    private String modelKey;
    /**
     * åˆ†ç±»ç¼–码
     */
    private String category;
    /**
     * ç‰ˆæœ¬
     */
    private Integer version;
    /**
     * è¡¨å•类型
     */
    private Integer formType;
    /**
     * è¡¨å•ID
     */
    private Long formId;
    /**
     * æ¨¡åž‹æè¿°
     */
    private String description;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
    /**
     * æµç¨‹xml
     */
    private String bpmnXml;
    /**
     * è¡¨å•内容
     */
    private String content;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * æˆ‘拥有流程对象导出VO
 *
 * @author konbai
 */
@Data
@NoArgsConstructor
public class WfOwnTaskExportVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æµç¨‹å®žä¾‹ID
     */
    @ExcelProperty(value = "流程编号")
    private String procInsId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String procDefName;
    /**
     * æµç¨‹ç±»åˆ«
     */
    @ExcelProperty(value = "流程类别")
    private String category;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "流程版本")
    private int procDefVersion;
    /**
     * æäº¤æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "提交时间")
    private Date createTime;
    /**
     * æµç¨‹çŠ¶æ€
     */
    @ExcelProperty(value = "流程状态")
    private String status;
    /**
     * ä»»åŠ¡è€—æ—¶
     */
    @ExcelProperty(value = "任务耗时")
    private String duration;
    /**
     * å½“前节点
     */
    @ExcelProperty(value = "当前节点")
    private String taskName;
    /**
     * ä»»åŠ¡å®Œæˆæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date finishTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.flowable.engine.task.Comment;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
 * å·¥ä½œæµèŠ‚ç‚¹å…ƒç´ è§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/9/11 22:04
 */
@Data
@ExcelIgnoreUnannotated
public class WfProcNodeVo implements Serializable {
    /**
     * æµç¨‹ID
     */
    private String procDefId;
    /**
     * æ´»åЍID
     */
    private String activityId;
    /**
     * æ´»åŠ¨åç§°
     */
    private String activityName;
    /**
     * æ´»åŠ¨ç±»åž‹
     */
    private String activityType;
    /**
     * æ´»åŠ¨è€—æ—¶
     */
    private String duration;
    /**
     * æ‰§è¡ŒäººId
     */
    private Long assigneeId;
    /**
     * æ‰§è¡Œäººåç§°
     */
    private String assigneeName;
    /**
     * å€™é€‰æ‰§è¡Œäºº
     */
    private String candidate;
    /**
     * ä»»åŠ¡æ„è§
     */
    private List<Comment> commentList;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * ç»“束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,134 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.flowable.engine.task.Comment;
import org.ruoyi.flowable.workflow.domain.dto.WfCommentDto;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
 * å·¥ä½œæµä»»åŠ¡è§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@Data
@ExcelIgnoreUnannotated
public class WfTaskVo implements Serializable {
    /**
     * ä»»åŠ¡ç¼–å·
     */
    private String taskId;
    /**
     * ä»»åŠ¡åç§°
     */
    private String taskName;
    /**
     * ä»»åŠ¡Key
     */
    private String taskDefKey;
    /**
     * ä»»åŠ¡æ‰§è¡ŒäººId
     */
    private Long assigneeId;
    /**
     * éƒ¨é—¨åç§°
     */
    @Deprecated
    private String deptName;
    /**
     * æµç¨‹å‘起人部门名称
     */
    @Deprecated
    private String startDeptName;
    /**
     * ä»»åŠ¡æ‰§è¡Œäººåç§°
     */
    private String assigneeName;
    /**
     * æµç¨‹å‘起人Id
     */
    private Long startUserId;
    /**
     * æµç¨‹å‘起人名称
     */
    private String startUserName;
    /**
     * æµç¨‹ç±»åž‹
     */
    private String category;
    /**
     * æµç¨‹å˜é‡ä¿¡æ¯
     */
    private Object procVars;
    /**
     * å±€éƒ¨å˜é‡ä¿¡æ¯
     */
    private Object taskLocalVars;
    /**
     * æµç¨‹éƒ¨ç½²ç¼–号
     */
    private String deployId;
    /**
     * æµç¨‹ID
     */
    private String procDefId;
    /**
     * æµç¨‹key
     */
    private String procDefKey;
    /**
     * æµç¨‹å®šä¹‰åç§°
     */
    private String procDefName;
    /**
     * æµç¨‹å®šä¹‰å†…置使用版本
     */
    private int procDefVersion;
    /**
     * æµç¨‹å®žä¾‹ID
     */
    private String procInsId;
    /**
     * åŽ†å²æµç¨‹å®žä¾‹ID
     */
    private String hisProcInsId;
    /**
     * ä»»åŠ¡è€—æ—¶
     */
    private String duration;
    /**
     * ä»»åŠ¡æ„è§
     */
    private WfCommentDto comment;
    /**
     * ä»»åŠ¡æ„è§
     */
    private List<Comment> commentList;
    /**
     * å€™é€‰æ‰§è¡Œäºº
     */
    private String candidate;
    /**
     * ä»»åŠ¡åˆ›å»ºæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * ä»»åŠ¡å®Œæˆæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date finishTime;
    /**
     * æµç¨‹çŠ¶æ€
     */
    private String processStatus;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * å¾…办流程对象导出VO
 *
 * @author konbai
 */
@Data
@NoArgsConstructor
public class WfTodoTaskExportVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä»»åŠ¡ç¼–å·
     */
    @ExcelProperty(value = "任务编号")
    private String taskId;
    /**
     * æµç¨‹åç§°
     */
    @ExcelProperty(value = "流程名称")
    private String procDefName;
    /**
     * ä»»åŠ¡èŠ‚ç‚¹
     */
    @ExcelProperty(value = "任务节点")
    private String taskName;
    /**
     * æµç¨‹ç‰ˆæœ¬
     */
    @ExcelProperty(value = "流程版本")
    private int procDefVersion;
    /**
     * æµç¨‹å‘起人名称
     */
    @ExcelProperty(value = "流程发起人")
    private String startUserName;
    /**
     * æŽ¥æ”¶æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "接收时间")
    private Date createTime;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package org.ruoyi.flowable.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
 * ä»»åŠ¡è¿½è¸ªè§†å›¾å¯¹è±¡
 *
 * @author KonBAI
 * @createTime 2022/1/8 19:42
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ExcelIgnoreUnannotated
public class WfViewerVo {
    /**
     * èŽ·å–æµç¨‹å®žä¾‹çš„åŽ†å²èŠ‚ç‚¹ï¼ˆåŽ»é‡ï¼‰
     */
    private Set<String> finishedTaskSet;
    /**
     * å·²å®Œæˆ
     */
    private Set<String> finishedSequenceFlowSet;
    /**
     * èŽ·å–æµç¨‹å®žä¾‹å½“å‰æ­£åœ¨å¾…åŠžçš„èŠ‚ç‚¹ï¼ˆåŽ»é‡ï¼‰
     */
    private Set<String> unfinishedTaskSet;
    /**
     * å·²æ‹’绝
     */
    private Set<String> rejectedTaskSet;
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package org.ruoyi.flowable.workflow.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.common.constant.ProcessConstants;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.bo.SysUserBo;
import org.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * å¤šå®žä¾‹å¤„理类
 *
 * @author KonBAI
 */
@AllArgsConstructor
@Component("multiInstanceHandler")
public class MultiInstanceHandler {
    @Autowired
    private ISysUserService remoteUserService;
    public Set<String> getUserIds(DelegateExecution execution) {
        Set<String> candidateUserIds = new LinkedHashSet<>();
        FlowElement flowElement = execution.getCurrentFlowElement();
        if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {
            UserTask userTask = (UserTask) flowElement;
            String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
            if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
                // æ·»åŠ å€™é€‰ç”¨æˆ·id
                candidateUserIds.addAll(userTask.getCandidateUsers());
            } else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
                // èŽ·å–ç»„çš„ID,角色ID集合或部门ID集合
                List<Long> groups = userTask.getCandidateGroups().stream()
                        .map(item -> Long.parseLong(item.substring(4)))
                        .collect(Collectors.toList());
                List<Long> userIds = new ArrayList<>();
                if ("ROLES".equals(dataType)) {
                    //todo é€šè¿‡è§’色id,获取所有用户id集合
                    R<List<SysUser>> userList = remoteUserService.selectUserByRoleIds(groups);
                    if(!ObjectUtil.isNull(userList) && !ObjectUtil.isNull(userList.getData())) {
                        List<SysUser> userListData = userList.getData();
                        for(SysUser user : userListData) {
                            userIds.add(user.getUserId());
                        }
                    }
                } else if ("DEPTS".equals(dataType)) {
                    //todo é€šè¿‡éƒ¨é—¨id,获取所有用户id集合
                    R<List<SysUser>> userList = remoteUserService.selectUserByDeptIds(groups);
                    if(!ObjectUtil.isNull(userList) && ! ObjectUtil.isNull(userList.getData())) {
                        List<SysUser> userListData = userList.getData();
                        for(SysUser user : userListData) {
                            userIds.add(user.getUserId());
                        }
                    }
                }
                // æ·»åŠ å€™é€‰ç”¨æˆ·id
                userIds.forEach(id -> candidateUserIds.add(String.valueOf(id)));
            }
        }
        return candidateUserIds;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.ruoyi.flowable.workflow.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
import org.ruoyi.flowable.workflow.domain.WfCategory;
import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
/**
 * æµç¨‹åˆ†ç±»Mapper接口
 *
 * @author KonBAI
 * @date 2022-01-15
 */
@Mapper
public interface WfCategoryMapper extends BaseMapperPlus<WfCategoryMapper, WfCategory, WfCategoryVo> {
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package org.ruoyi.flowable.workflow.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
import org.ruoyi.flowable.workflow.domain.WfCopy;
import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
/**
 * æµç¨‹æŠ„送Mapper接口
 *
 * @author KonBAI
 * @date 2022-05-19
 */
@Mapper
public interface WfCopyMapper extends BaseMapperPlus<WfCopyMapper, WfCopy, WfCopyVo> {
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package org.ruoyi.flowable.workflow.mapper;
import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.vo.WfDeployFormVo;
import org.apache.ibatis.annotations.Mapper;
/**
 * æµç¨‹å®žä¾‹å…³è”表单Mapper接口
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Mapper
public interface WfDeployFormMapper extends BaseMapperPlus<WfDeployFormMapper, WfDeployForm, WfDeployFormVo> {
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package org.ruoyi.flowable.workflow.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
import org.ruoyi.flowable.workflow.domain.WfForm;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * æµç¨‹è¡¨å•Mapper接口
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@Mapper
public interface WfFormMapper extends BaseMapperPlus<WfFormMapper, WfForm, WfFormVo> {
    List<WfFormVo> selectFormVoList(@Param(Constants.WRAPPER) Wrapper<WfForm> queryWrapper);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.workflow.domain.WfCategory;
import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
import java.util.Collection;
import java.util.List;
/**
 * æµç¨‹åˆ†ç±»Service接口
 *
 * @author KonBAI
 * @date 2022-01-15
 */
public interface IWfCategoryService {
    /**
     * æŸ¥è¯¢å•个
     * @return
     */
    WfCategoryVo queryById(Long categoryId);
    /**
     * æŸ¥è¯¢åˆ—表
     */
    List<WfCategoryVo> queryList(WfCategory category);
    /**
     * æ–°å¢žæµç¨‹åˆ†ç±»
     *
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return ç»“æžœ
     */
    int insertCategory(WfCategory category);
    /**
     * ç¼–辑流程分类
     * @param category æµç¨‹åˆ†ç±»ä¿¡æ¯
     * @return ç»“æžœ
     */
    int updateCategory(WfCategory category);
    /**
     * æ ¡éªŒå¹¶åˆ é™¤æ•°æ®
     * @param ids ä¸»é”®é›†åˆ
     * @param isValid æ˜¯å¦æ ¡éªŒ,true-删除前校验,false-不校验
     * @return ç»“æžœ
     */
    int deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
    /**
     * æ ¡éªŒåˆ†ç±»ç¼–码是否唯一
     *
     * @param category æµç¨‹åˆ†ç±»
     * @return ç»“æžœ
     */
    boolean checkCategoryCodeUnique(WfCategory category);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
import java.util.List;
/**
 * æµç¨‹æŠ„送Service接口
 *
 * @author KonBAI
 * @date 2022-05-19
 */
public interface IWfCopyService {
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送
     *
     * @param copyId æµç¨‹æŠ„送主键
     * @return æµç¨‹æŠ„送
     */
    WfCopyVo queryById(Long copyId);
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送列表
     *
     * @param wfCopy æµç¨‹æŠ„送
     * @return æµç¨‹æŠ„送集合
     */
    TableDataInfo<WfCopyVo> selectPageList(WfCopyBo wfCopy, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送列表
     *
     * @param wfCopy æµç¨‹æŠ„送
     * @return æµç¨‹æŠ„送集合
     */
    List<WfCopyVo> selectList(WfCopyBo wfCopy);
    /**
     * æŠ„送
     * @param taskBo
     * @return
     */
    Boolean makeCopy(WfTaskBo taskBo);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.flowable.bpmn.model.BpmnModel;
/**
 * æµç¨‹å®žä¾‹å…³è”表单Service接口
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
public interface IWfDeployFormService {
    /**
     * æ–°å¢žæµç¨‹å®žä¾‹å…³è”表单
     *
     * @param wfDeployForm æµç¨‹å®žä¾‹å…³è”表单
     * @return ç»“æžœ
     */
    int insertWfDeployForm(WfDeployForm wfDeployForm);
    /**
     * ä¿å­˜æµç¨‹å®žä¾‹å…³è”表单
     * @param deployId éƒ¨ç½²ID
     * @param bpmnModel bpmnModel对象
     * @return
     */
    boolean saveInternalDeployForm(String deployId, BpmnModel bpmnModel);
    /**
     * æŸ¥è¯¢æµç¨‹æŒ‚着的表单
     *
     * @param deployId
     * @return
     */
    @Deprecated
    WfFormVo selectDeployFormByDeployId(String deployId);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
import java.util.List;
/**
 * @author KonBAI
 * @createTime 2022/6/30 9:03
 */
public interface IWfDeployService {
    TableDataInfo<WfDeployVo> queryPageList(ProcessQuery processQuery, PageQuery pageQuery);
    TableDataInfo<WfDeployVo> queryPublishList(String processKey, PageQuery pageQuery);
    void updateState(String definitionId, String stateCode);
    String queryBpmnXmlById(String definitionId);
    void deleteByIds(List<String> deployIds);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import java.util.Collection;
import java.util.List;
/**
 * è¡¨å•
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
public interface IWfFormService {
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•
     *
     * @param formId æµç¨‹è¡¨å•ID
     * @return æµç¨‹è¡¨å•
     */
    WfFormVo queryById(Long formId);
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•列表
     *
     * @param bo æµç¨‹è¡¨å•
     * @return æµç¨‹è¡¨å•集合
     */
    TableDataInfo<WfFormVo> queryPageList(WfFormBo bo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•列表
     *
     * @param bo æµç¨‹è¡¨å•
     * @return æµç¨‹è¡¨å•集合
     */
    List<WfFormVo> queryList(WfFormBo bo);
    /**
     * æ–°å¢žæµç¨‹è¡¨å•
     *
     * @param bo æµç¨‹è¡¨å•
     * @return ç»“æžœ
     */
    int insertForm(WfFormBo bo);
    /**
     * ä¿®æ”¹æµç¨‹è¡¨å•
     *
     * @param bo æµç¨‹è¡¨å•
     * @return ç»“æžœ
     */
    int updateForm(WfFormBo bo);
    /**
     * æ‰¹é‡åˆ é™¤æµç¨‹è¡¨å•
     *
     * @param formIds éœ€è¦åˆ é™¤çš„æµç¨‹è¡¨å•ID
     * @return ç»“æžœ
     */
    Boolean deleteWithValidByIds(Collection<Long> formIds);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.flowable.engine.history.HistoricProcessInstance;
import java.util.Map;
/**
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
public interface IWfInstanceService {
    /**
     * ç»“束流程实例
     *
     * @param vo
     */
    void stopProcessInstance(WfTaskBo vo);
    /**
     * æ¿€æ´»æˆ–挂起流程实例
     *
     * @param state      çŠ¶æ€
     * @param instanceId æµç¨‹å®žä¾‹ID
     */
    void updateState(Integer state, String instanceId);
    /**
     * åˆ é™¤æµç¨‹å®žä¾‹ID
     *
     * @param instanceId   æµç¨‹å®žä¾‹ID
     * @param deleteReason åˆ é™¤åŽŸå› 
     */
    void delete(String instanceId, String deleteReason);
    /**
     * æ ¹æ®å®žä¾‹ID查询历史实例数据
     *
     * @param processInstanceId
     * @return
     */
    HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId);
    /**
     * æŸ¥è¯¢æµç¨‹è¯¦æƒ…信息
     * @param procInsId æµç¨‹å®žä¾‹ID
     * @param deployId æµç¨‹éƒ¨ç½²ID
     */
    Map<String, Object> queryDetailProcess(String procInsId, String deployId);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
import java.util.Collection;
import java.util.List;
/**
 * @author KonBAI
 * @createTime 2022/6/21 9:11
 */
public interface IWfModelService {
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹åˆ—表
     */
    TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹åˆ—表
     */
    List<WfModelVo> list(WfModelBo modelBo);
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹åˆ—表
     */
    TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æµç¨‹æ¨¡åž‹è¯¦æƒ…信息
     */
    WfModelVo getModel(String modelId);
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•详细信息
     */
    String queryBpmnXmlById(String modelId);
    /**
     * æ–°å¢žæ¨¡åž‹ä¿¡æ¯
     */
    void insertModel(WfModelBo modelBo);
    /**
     * ä¿®æ”¹æ¨¡åž‹ä¿¡æ¯
     */
    void updateModel(WfModelBo modelBo);
    /**
     * ä¿å­˜æµç¨‹æ¨¡åž‹ä¿¡æ¯
     */
    void saveModel(WfModelBo modelBo);
    /**
     * è®¾ä¸ºæœ€æ–°æµç¨‹æ¨¡åž‹
     */
    void latestModel(String modelId);
    /**
     * åˆ é™¤æµç¨‹æ¨¡åž‹
     */
    void deleteByIds(Collection<String> ids);
    /**
     * éƒ¨ç½²æµç¨‹æ¨¡åž‹
     */
    boolean deployModel(String modelId);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.core.FormConf;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.vo.WfDefinitionVo;
import org.ruoyi.flowable.workflow.domain.vo.WfDetailVo;
import org.ruoyi.flowable.workflow.domain.vo.WfTaskVo;
import java.util.List;
import java.util.Map;
/**
 * @author KonBAI
 * @createTime 2022/3/24 18:57
 */
public interface IWfProcessService {
    /**
     * æŸ¥è¯¢å¯å‘起流程列表
     * @param pageQuery åˆ†é¡µå‚æ•°
     * @return
     */
    TableDataInfo<WfDefinitionVo> selectPageStartProcessList(ProcessQuery processQuery, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å¯å‘起流程列表
     */
    List<WfDefinitionVo> selectStartProcessList(ProcessQuery processQuery);
    /**
     * æŸ¥è¯¢æˆ‘的流程列表
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    TableDataInfo<WfTaskVo> selectPageOwnProcessList(ProcessQuery processQuery, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢æˆ‘的流程列表
     */
    List<WfTaskVo> selectOwnProcessList(ProcessQuery processQuery);
    /**
     * æŸ¥è¯¢ä»£åŠžä»»åŠ¡åˆ—è¡¨
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢ä»£åŠžä»»åŠ¡åˆ—è¡¨
     */
    List<WfTaskVo> selectTodoProcessList(ProcessQuery processQuery);
    /**
     * æŸ¥è¯¢å¾…签任务列表
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    TableDataInfo<WfTaskVo> selectPageClaimProcessList(ProcessQuery processQuery, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å¾…签任务列表
     */
    List<WfTaskVo> selectClaimProcessList(ProcessQuery processQuery);
    /**
     * æŸ¥è¯¢å·²åŠžä»»åŠ¡åˆ—è¡¨
     * @param pageQuery åˆ†é¡µå‚æ•°
     */
    TableDataInfo<WfTaskVo> selectPageFinishedProcessList(ProcessQuery processQuery, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢å·²åŠžä»»åŠ¡åˆ—è¡¨
     */
    List<WfTaskVo> selectFinishedProcessList(ProcessQuery processQuery);
    /**
     * æŸ¥è¯¢æµç¨‹éƒ¨ç½²å…³è”表单信息
     * @param definitionId æµç¨‹å®šä¹‰ID
     * @param deployId éƒ¨ç½²ID
     */
    FormConf selectFormContent(String definitionId, String deployId, String procInsId);
    /**
     * å¯åŠ¨æµç¨‹å®žä¾‹
     * @param procDefId æµç¨‹å®šä¹‰ID
     * @param variables æ‰©å±•参数
     */
    void startProcessByDefId(String procDefId, Map<String, Object> variables);
    /**
     * é€šè¿‡DefinitionKey启动流程
     * @param procDefKey æµç¨‹å®šä¹‰Key
     * @param variables æ‰©å±•参数
     */
    void startProcessByDefKey(String procDefKey, Map<String, Object> variables);
    /**
     * åˆ é™¤æµç¨‹å®žä¾‹
     */
    void deleteProcessByIds(String[] instanceIds);
    /**
     * è¯»å–xml文件
     * @param processDefId æµç¨‹å®šä¹‰ID
     */
    String queryBpmnXmlById(String processDefId);
    /**
     * æŸ¥è¯¢æµç¨‹ä»»åŠ¡è¯¦æƒ…ä¿¡æ¯
     * @param procInsId æµç¨‹å®žä¾‹ID
     * @param taskId ä»»åŠ¡ID
     */
    WfDetailVo queryProcessDetail(String procInsId, String taskId);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package org.ruoyi.flowable.workflow.service;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.runtime.ProcessInstance;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
public interface IWfTaskService {
    /**
     * å®¡æ‰¹ä»»åŠ¡
     *
     * @param task è¯·æ±‚实体参数
     */
    void complete(WfTaskBo task);
    /**
     * æ‹’绝任务
     *
     * @param taskBo
     */
    void taskReject(WfTaskBo taskBo);
    /**
     * é€€å›žä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    void taskReturn(WfTaskBo bo);
    /**
     * èŽ·å–æ‰€æœ‰å¯å›žé€€çš„èŠ‚ç‚¹
     *
     * @param bo
     * @return
     */
    List<FlowElement> findReturnTaskList(WfTaskBo bo);
    /**
     * åˆ é™¤ä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    void deleteTask(WfTaskBo bo);
    /**
     * è®¤é¢†/签收任务
     *
     * @param bo è¯·æ±‚实体参数
     */
    void claim(WfTaskBo bo);
    /**
     * å–消认领/签收任务
     *
     * @param bo è¯·æ±‚实体参数
     */
    void unClaim(WfTaskBo bo);
    /**
     * å§”派任务
     *
     * @param bo è¯·æ±‚实体参数
     */
    void delegateTask(WfTaskBo bo);
    /**
     * è½¬åŠžä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    void transferTask(WfTaskBo bo);
    /**
     * å–消申请
     * @param bo
     * @return
     */
    void stopProcess(WfTaskBo bo);
    /**
     * æ’¤å›žæµç¨‹
     * @param bo
     * @return
     */
    void revokeProcess(WfTaskBo bo);
    /**
     * èŽ·å–æµç¨‹è¿‡ç¨‹å›¾
     * @param processId
     * @return
     */
    InputStream diagram(String processId);
    /**
     * èŽ·å–æµç¨‹å˜é‡
     * @param taskId ä»»åŠ¡ID
     * @return æµç¨‹å˜é‡
     */
    Map<String, Object> getProcessVariables(String taskId);
    /**
     * å¯åŠ¨ç¬¬ä¸€ä¸ªä»»åŠ¡
     * @param processInstance æµç¨‹å®žä¾‹
     * @param variables æµç¨‹å‚æ•°
     */
    void startFirstTask(ProcessInstance processInstance, Map<String, Object> variables);
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.workflow.domain.WfCategory;
import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
import org.ruoyi.flowable.workflow.mapper.WfCategoryMapper;
import org.ruoyi.flowable.workflow.service.IWfCategoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹åˆ†ç±»Service业务层处理
 *
 * @author KonBAI
 * @date 2022-01-15
 */
@RequiredArgsConstructor
@Service
public class WfCategoryServiceImpl implements IWfCategoryService {
    private final WfCategoryMapper baseMapper;
    @Override
    public WfCategoryVo queryById(Long categoryId){
        return baseMapper.selectVoById(categoryId);
    }
    @Override
    public List<WfCategoryVo> queryList(WfCategory category) {
        LambdaQueryWrapper<WfCategory> lqw = buildQueryWrapper(category);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<WfCategory> buildQueryWrapper(WfCategory category) {
        Map<String, Object> params = category.getParams();
        LambdaQueryWrapper<WfCategory> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(category.getCategoryName()), WfCategory::getCategoryName, category.getCategoryName());
        lqw.eq(StringUtils.isNotBlank(category.getCode()), WfCategory::getCode, category.getCode());
        return lqw;
    }
    @Override
    public int insertCategory(WfCategory categoryBo) {
        WfCategory add = BeanUtil.toBean(categoryBo, WfCategory.class);
        return baseMapper.insert(add);
    }
    @Override
    public int updateCategory(WfCategory categoryBo) {
        WfCategory update = BeanUtil.toBean(categoryBo, WfCategory.class);
        return baseMapper.updateById(update);
    }
    @Override
    public int deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids);
    }
    /**
     * æ ¡éªŒåˆ†ç±»ç¼–码是否唯一
     *
     * @param category æµç¨‹åˆ†ç±»
     * @return ç»“æžœ
     */
    @Override
    public boolean checkCategoryCodeUnique(WfCategory category) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<WfCategory>()
            .eq(WfCategory::getCode, category.getCode())
            .ne(ObjectUtil.isNotNull(category.getCategoryId()), WfCategory::getCategoryId, category.getCategoryId()));
        return !exist;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
package org.ruoyi.flowable.workflow.service.impl;
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.annotation.Resource;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.ObjectUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.WfCopy;
import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
import org.ruoyi.flowable.workflow.mapper.WfCopyMapper;
import org.ruoyi.flowable.workflow.service.IWfCopyService;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹æŠ„送Service业务层处理
 *
 * @author KonBAI
 * @date 2022-05-19
 */
@RequiredArgsConstructor
@Service
public class WfCopyServiceImpl implements IWfCopyService {
    private final WfCopyMapper baseMapper;
    private final HistoryService historyService;
    @Resource
    protected RepositoryService repositoryService;
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送
     *
     * @param copyId æµç¨‹æŠ„送主键
     * @return æµç¨‹æŠ„送
     */
    @Override
    public WfCopyVo queryById(Long copyId){
        return baseMapper.selectVoById(copyId);
    }
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送列表
     *
     * @param bo æµç¨‹æŠ„送
     * @return æµç¨‹æŠ„送
     */
    @Override
    public TableDataInfo<WfCopyVo> selectPageList(WfCopyBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<WfCopy> lqw = buildQueryWrapper(bo);
        lqw.orderByDesc(WfCopy::getCreateTime);
        Page<WfCopyVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }
    /**
     * æŸ¥è¯¢æµç¨‹æŠ„送列表
     *
     * @param bo æµç¨‹æŠ„送
     * @return æµç¨‹æŠ„送
     */
    @Override
    public List<WfCopyVo> selectList(WfCopyBo bo) {
        LambdaQueryWrapper<WfCopy> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<WfCopy> buildQueryWrapper(WfCopyBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<WfCopy> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getUserId() != null, WfCopy::getUserId, bo.getUserId());
        lqw.like(StringUtils.isNotBlank(bo.getProcessName()), WfCopy::getProcessName, bo.getProcessName());
        lqw.like(StringUtils.isNotBlank(bo.getOriginatorName()), WfCopy::getOriginatorName, bo.getOriginatorName());
        return lqw;
    }
    @Override
    public Boolean makeCopy(WfTaskBo taskBo) {
        if (StringUtils.isBlank(taskBo.getCopyUserIds())) {
            // è‹¥æŠ„送用户为空,则不需要处理,返回成功
            return true;
        }
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
            .processInstanceId(taskBo.getProcInsId()).singleResult();
        String[] ids = taskBo.getCopyUserIds().split(",");
        List<WfCopy> copyList = new ArrayList<>(ids.length);
        // èŽ·å–å½“å‰çš„ç”¨æˆ·
        LoginUser loginUser = LoginHelper.getLoginUser();
        Long originatorId = null;
        String originatorName = null;
        if (ObjectUtils.isNotEmpty(loginUser))
        {
            originatorId = loginUser.getUserId();
            originatorName = loginUser.getNickName();
        }
        // æµç¨‹éƒ¨ç½²å®žä¾‹ä¿¡æ¯
        Deployment deployment = repositoryService.createDeploymentQuery()
                .deploymentId(historicProcessInstance.getDeploymentId()).singleResult();
        String title = historicProcessInstance.getProcessDefinitionName() + "-" + taskBo.getTaskName();
        for (String id : ids) {
            Long userId = Long.valueOf(id);
            WfCopy copy = new WfCopy();
            copy.setTitle(title);
            copy.setProcessId(historicProcessInstance.getProcessDefinitionId());
            copy.setProcessName(historicProcessInstance.getProcessDefinitionName());
            copy.setDeploymentId(historicProcessInstance.getDeploymentId());
            copy.setInstanceId(taskBo.getProcInsId());
            copy.setTaskId(taskBo.getTaskId());
            copy.setUserId(userId);
            copy.setOriginatorId(originatorId);
            copy.setOriginatorName(originatorName);
            copy.setCreateTime(new Date());
            copy.setCategoryId(deployment.getCategory());
            copyList.add(copy);
        }
        return baseMapper.insertBatch(copyList);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,134 @@
package org.ruoyi.flowable.workflow.service.impl;
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.toolkit.Wrappers;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.utils.ModelUtils;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.WfForm;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
import org.ruoyi.flowable.workflow.mapper.WfFormMapper;
import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
 * æµç¨‹å®žä¾‹å…³è”表单Service业务层处理
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@RequiredArgsConstructor
@Service
public class WfDeployFormServiceImpl implements IWfDeployFormService {
    private final WfDeployFormMapper baseMapper;
    private final WfFormMapper formMapper;
    /**
     * æ–°å¢žæµç¨‹å®žä¾‹å…³è”表单
     *
     * @param deployForm æµç¨‹å®žä¾‹å…³è”表单
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertWfDeployForm(WfDeployForm deployForm) {
        // åˆ é™¤éƒ¨ç½²æµç¨‹å’Œè¡¨å•的关联关系
        baseMapper.delete(new LambdaQueryWrapper<WfDeployForm>().eq(WfDeployForm::getDeployId, deployForm.getDeployId()));
        // æ–°å¢žéƒ¨ç½²æµç¨‹å’Œè¡¨å•关系
        return baseMapper.insert(deployForm);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveInternalDeployForm(String deployId, BpmnModel bpmnModel) {
        List<WfDeployForm> deployFormList = new ArrayList<>();
        // èŽ·å–å¼€å§‹èŠ‚ç‚¹
        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
        if (ObjectUtil.isNull(startEvent)) {
            throw new RuntimeException("开始节点不存在,请检查流程设计是否有误!");
        }
        // ä¿å­˜å¼€å§‹èŠ‚ç‚¹è¡¨å•ä¿¡æ¯
        WfDeployForm startDeployForm = buildDeployForm(deployId, startEvent);
        if (ObjectUtil.isNotNull(startDeployForm)) {
            deployFormList.add(startDeployForm);
        }
        // ä¿å­˜ç”¨æˆ·èŠ‚ç‚¹è¡¨å•ä¿¡æ¯
        Collection<UserTask> userTasks = ModelUtils.getAllUserTaskEvent(bpmnModel);
        if (CollUtil.isNotEmpty(userTasks)) {
            for (UserTask userTask : userTasks) {
                WfDeployForm userTaskDeployForm = buildDeployForm(deployId, userTask);
                if (ObjectUtil.isNotNull(userTaskDeployForm)) {
                    deployFormList.add(userTaskDeployForm);
                }
            }
        }
        // æ‰¹é‡æ–°å¢žéƒ¨ç½²æµç¨‹å’Œè¡¨å•关联信息
        return baseMapper.insertBatch(deployFormList);
    }
    /**
     * æŸ¥è¯¢æµç¨‹æŒ‚着的表单
     *
     * @param deployId
     * @return
     */
    @Override
    public WfFormVo selectDeployFormByDeployId(String deployId) {
        QueryWrapper<WfForm> wrapper = Wrappers.query();
        wrapper.eq("t2.deploy_id", deployId);
        List<WfFormVo> list = formMapper.selectFormVoList(wrapper);
        if (ObjectUtil.isNotEmpty(list)) {
            if (list.size() != 1) {
                throw new ServiceException("表单信息查询错误");
            } else {
                return list.get(0);
            }
        } else {
            return null;
        }
    }
    /**
     * æž„建部署表单关联信息对象
     * @param deployId éƒ¨ç½²ID
     * @param node èŠ‚ç‚¹ä¿¡æ¯
     * @return éƒ¨ç½²è¡¨å•关联对象。若无表单信息(formKey),则返回null
     */
    private WfDeployForm buildDeployForm(String deployId, FlowNode node) {
        String formKey = ModelUtils.getFormKey(node);
        if (StringUtils.isEmpty(formKey)) {
            return null;
        }
        Long formId = Convert.toLong(StringUtils.substringAfter(formKey, "key_"));
        WfForm wfForm = formMapper.selectById(formId);
        if (ObjectUtil.isNull(wfForm)) {
            throw new ServiceException("表单信息查询错误");
        }
        WfDeployForm deployForm = new WfDeployForm();
        deployForm.setDeployId(deployId);
        deployForm.setFormKey(formKey);
        deployForm.setNodeKey(node.getId());
        deployForm.setFormName(wfForm.getFormName());
        deployForm.setNodeName(node.getName());
        deployForm.setContent(wfForm.getContent());
        return deployForm;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.utils.ProcessUtils;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
import org.ruoyi.flowable.workflow.service.IWfDeployService;
import lombok.RequiredArgsConstructor;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author KonBAI
 * @createTime 2022/6/30 9:04
 */
@RequiredArgsConstructor
@Service
public class WfDeployServiceImpl implements IWfDeployService {
    private final RepositoryService repositoryService;
    private final WfDeployFormMapper deployFormMapper;
    @Override
    public TableDataInfo<WfDeployVo> queryPageList(ProcessQuery processQuery, PageQuery pageQuery) {
        // æµç¨‹å®šä¹‰åˆ—表数据查询
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
            .latestVersion()
            .orderByProcessDefinitionKey()
            .asc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
        long pageTotal = processDefinitionQuery.count();
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<ProcessDefinition> definitionList = processDefinitionQuery.listPage(offset, pageQuery.getPageSize());
        List<WfDeployVo> deployVoList = new ArrayList<>(definitionList.size());
        for (ProcessDefinition processDefinition : definitionList) {
            String deploymentId = processDefinition.getDeploymentId();
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            WfDeployVo vo = new WfDeployVo();
            vo.setDefinitionId(processDefinition.getId());
            vo.setProcessKey(processDefinition.getKey());
            vo.setProcessName(processDefinition.getName());
            vo.setVersion(processDefinition.getVersion());
            vo.setCategory(processDefinition.getCategory());
            vo.setDeploymentId(processDefinition.getDeploymentId());
            vo.setSuspended(processDefinition.isSuspended());
            // æµç¨‹éƒ¨ç½²ä¿¡æ¯
            vo.setCategory(deployment.getCategory());
            vo.setDeploymentTime(deployment.getDeploymentTime());
            deployVoList.add(vo);
        }
        Page<WfDeployVo> page = new Page<>();
        page.setRecords(deployVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }
    @Override
    public TableDataInfo<WfDeployVo> queryPublishList(String processKey, PageQuery pageQuery) {
        // åˆ›å»ºæŸ¥è¯¢æ¡ä»¶
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
            .processDefinitionKey(processKey)
            .orderByProcessDefinitionVersion()
            .desc();
        long pageTotal = processDefinitionQuery.count();
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        // æ ¹æ®æŸ¥è¯¢æ¡ä»¶ï¼ŒæŸ¥è¯¢æ‰€æœ‰ç‰ˆæœ¬
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery
            .listPage(offset, pageQuery.getPageSize());
        List<WfDeployVo> deployVoList = processDefinitionList.stream().map(item -> {
            WfDeployVo vo = new WfDeployVo();
            vo.setDefinitionId(item.getId());
            vo.setProcessKey(item.getKey());
            vo.setProcessName(item.getName());
            vo.setVersion(item.getVersion());
            vo.setCategory(item.getCategory());
            vo.setDeploymentId(item.getDeploymentId());
            vo.setSuspended(item.isSuspended());
            return vo;
        }).collect(Collectors.toList());
        Page<WfDeployVo> page = new Page<>();
        page.setRecords(deployVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }
    /**
     * æ¿€æ´»æˆ–挂起流程
     *
     * @param state çŠ¶æ€
     * @param definitionId æµç¨‹å®šä¹‰ID
     */
    @Override
    public void updateState(String definitionId, String state) {
        if (SuspensionState.ACTIVE.toString().equals(state)) {
            // æ¿€æ´»
            repositoryService.activateProcessDefinitionById(definitionId, true, null);
        } else if (SuspensionState.SUSPENDED.toString().equals(state)) {
            // æŒ‚èµ·
            repositoryService.suspendProcessDefinitionById(definitionId, true, null);
        }
    }
    @Override
    public String queryBpmnXmlById(String definitionId) {
        InputStream inputStream = repositoryService.getProcessModel(definitionId);
        try {
            return IoUtil.readUtf8(inputStream);
        } catch (IORuntimeException exception) {
            throw new RuntimeException("加载xml文件异常");
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByIds(List<String> deployIds) {
        for (String deployId : deployIds) {
            repositoryService.deleteDeployment(deployId, true);
            deployFormMapper.delete(new LambdaQueryWrapper<WfDeployForm>().eq(WfDeployForm::getDeployId, deployId));
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.workflow.domain.WfForm;
import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.mapper.WfFormMapper;
import org.ruoyi.flowable.workflow.service.IWfFormService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * æµç¨‹è¡¨å•Service业务层处理
 *
 * @author KonBAI
 * @createTime 2022/3/7 22:07
 */
@RequiredArgsConstructor
@Service
public class WfFormServiceImpl implements IWfFormService {
    private final WfFormMapper baseMapper;
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•
     *
     * @param formId æµç¨‹è¡¨å•ID
     * @return æµç¨‹è¡¨å•
     */
    @Override
    public WfFormVo queryById(Long formId) {
        return baseMapper.selectVoById(formId);
    }
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•列表
     *
     * @param bo æµç¨‹è¡¨å•
     * @return æµç¨‹è¡¨å•
     */
    @Override
    public TableDataInfo<WfFormVo> queryPageList(WfFormBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<WfForm> lqw = buildQueryWrapper(bo);
        Page<WfFormVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }
    /**
     * æŸ¥è¯¢æµç¨‹è¡¨å•列表
     *
     * @param bo æµç¨‹è¡¨å•
     * @return æµç¨‹è¡¨å•
     */
    @Override
    public List<WfFormVo> queryList(WfFormBo bo) {
        LambdaQueryWrapper<WfForm> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    /**
     * æ–°å¢žæµç¨‹è¡¨å•
     *
     * @param bo æµç¨‹è¡¨å•
     * @return ç»“æžœ
     */
    @Override
    public int insertForm(WfFormBo bo) {
        WfForm wfForm = new WfForm();
        wfForm.setFormName(bo.getFormName());
        wfForm.setContent(bo.getContent());
        wfForm.setRemark(bo.getRemark());
        return baseMapper.insert(wfForm);
    }
    /**
     * ä¿®æ”¹æµç¨‹è¡¨å•
     *
     * @param bo æµç¨‹è¡¨å•
     * @return ç»“æžœ
     */
    @Override
    public int updateForm(WfFormBo bo) {
        return baseMapper.update(new WfForm(), new LambdaUpdateWrapper<WfForm>()
            .set(StrUtil.isNotBlank(bo.getFormName()), WfForm::getFormName, bo.getFormName())
            .set(StrUtil.isNotBlank(bo.getContent()), WfForm::getContent, bo.getContent())
            .set(StrUtil.isNotBlank(bo.getRemark()), WfForm::getRemark, bo.getRemark())
            .eq(WfForm::getFormId, bo.getFormId()));
    }
    /**
     * æ‰¹é‡åˆ é™¤æµç¨‹è¡¨å•
     *
     * @param ids éœ€è¦åˆ é™¤çš„æµç¨‹è¡¨å•ID
     * @return ç»“æžœ
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }
    private LambdaQueryWrapper<WfForm> buildQueryWrapper(WfFormBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<WfForm> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getFormName()), WfForm::getFormName, bo.getFormName());
        return lqw;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,232 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.common.constant.TaskConstants;
import org.ruoyi.flowable.factory.FlowServiceFactory;
import org.ruoyi.flowable.utils.JsonUtils;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.domain.vo.WfTaskVo;
import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
import org.ruoyi.flowable.workflow.service.IWfInstanceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.ruoyi.system.domain.SysDept;
import org.ruoyi.system.domain.SysRole;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.vo.SysDeptVo;
import org.ruoyi.system.domain.vo.SysRoleVo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.service.ISysDeptService;
import org.ruoyi.system.service.ISysRoleService;
import org.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
 * å·¥ä½œæµæµç¨‹å®žä¾‹ç®¡ç†
 *
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class WfInstanceServiceImpl extends FlowServiceFactory implements IWfInstanceService {
    private final IWfDeployFormService deployFormService;
    private final ISysUserService userService;
    private final ISysRoleService roleService;
    private final ISysDeptService deptService;
    @Autowired
    private ISysUserService remoteUserService;
    /**
     * ç»“束流程实例
     *
     * @param vo
     */
    @Override
    public void stopProcessInstance(WfTaskBo vo) {
        String taskId = vo.getTaskId();
    }
    /**
     * æ¿€æ´»æˆ–挂起流程实例
     *
     * @param state      çŠ¶æ€
     * @param instanceId æµç¨‹å®žä¾‹ID
     */
    @Override
    public void updateState(Integer state, String instanceId) {
        // æ¿€æ´»
        if (state == 1) {
            runtimeService.activateProcessInstanceById(instanceId);
        }
        // æŒ‚èµ·
        if (state == 2) {
            runtimeService.suspendProcessInstanceById(instanceId);
        }
    }
    /**
     * åˆ é™¤æµç¨‹å®žä¾‹ID
     *
     * @param instanceId   æµç¨‹å®žä¾‹ID
     * @param deleteReason åˆ é™¤åŽŸå› 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String instanceId, String deleteReason) {
        // æŸ¥è¯¢åŽ†å²æ•°æ®
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId);
        if (historicProcessInstance.getEndTime() != null) {
            historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
            return;
        }
        // åˆ é™¤æµç¨‹å®žä¾‹
        runtimeService.deleteProcessInstance(instanceId, deleteReason);
        // åˆ é™¤åŽ†å²æµç¨‹å®žä¾‹
        historyService.deleteHistoricProcessInstance(instanceId);
    }
    /**
     * æ ¹æ®å®žä¾‹ID查询历史实例数据
     *
     * @param processInstanceId
     * @return
     */
    @Override
    public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new FlowableObjectNotFoundException("流程实例不存在: " + processInstanceId);
        }
        return historicProcessInstance;
    }
    /**
     * æµç¨‹åŽ†å²æµè½¬è®°å½•
     *
     * @param procInsId æµç¨‹å®žä¾‹Id
     * @return
     */
    @Override
    public Map<String, Object> queryDetailProcess(String procInsId, String deployId) {
        Map<String, Object> map = new HashMap<>();
        if (StringUtils.isNotBlank(procInsId)) {
            List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(procInsId)
                .orderByHistoricTaskInstanceStartTime().desc()
                .list();
            List<Comment> commentList = taskService.getProcessInstanceComments(procInsId);
            List<WfTaskVo> taskVoList = new ArrayList<>(taskInstanceList.size());
            taskInstanceList.forEach(taskInstance -> {
                WfTaskVo taskVo = new WfTaskVo();
                taskVo.setProcDefId(taskInstance.getProcessDefinitionId());
                taskVo.setTaskId(taskInstance.getId());
                taskVo.setTaskDefKey(taskInstance.getTaskDefinitionKey());
                taskVo.setTaskName(taskInstance.getName());
                taskVo.setCreateTime(taskInstance.getStartTime());
                taskVo.setFinishTime(taskInstance.getEndTime());
                if (StringUtils.isNotBlank(taskInstance.getAssignee())) {
                    Long userId = Long.parseLong(taskInstance.getAssignee());
                    SysUserVo user = userService.selectUserById(userId);
                    if(!ObjectUtil.isNull(user)) {
                        taskVo.setAssigneeId(userId);
                        taskVo.setAssigneeName(user.getNickName());
                    }
                }
                // å±•示审批人员
                List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(taskInstance.getId());
                StringBuilder stringBuilder = new StringBuilder();
                for (HistoricIdentityLink identityLink : linksForTask) {
                    if ("candidate".equals(identityLink.getType())) {
                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
                            Long userId = Long.parseLong(identityLink.getUserId());
                            String nickName ="";
                            SysUserVo user = userService.selectUserById(userId);
                            if(!ObjectUtil.isNull(user)) {
                                nickName=user.getNickName();
                            }
                            stringBuilder.append(nickName).append(",");
                        }
                        if (StringUtils.isNotBlank(identityLink.getGroupId())) {
                            if (identityLink.getGroupId().startsWith(TaskConstants.ROLE_GROUP_PREFIX)) {
                                Long roleId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.ROLE_GROUP_PREFIX));
                                //finish do æŸ¥è¯¢è§’色名称
                                SysRoleVo roleR=roleService.selectRoleById(roleId);
                                if (!ObjectUtil.isNull(roleR)) {
                                    stringBuilder.append(roleR.getRoleName()).append(",");
                                }
                            } else if (identityLink.getGroupId().startsWith(TaskConstants.DEPT_GROUP_PREFIX)) {
                                Long deptId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.DEPT_GROUP_PREFIX));
                                //finish do æŸ¥è¯¢éƒ¨é—¨åç§°
                                SysDeptVo deptR=deptService.selectDeptById(deptId);
                                if (!ObjectUtil.isNull(deptR)) {
                                    stringBuilder.append(deptR.getDeptName()).append(",");
                                }
                            }
                        }
                    }
                }
                if (StringUtils.isNotBlank(stringBuilder)) {
                    taskVo.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1));
                }
                if (ObjectUtil.isNotNull(taskInstance.getDurationInMillis())) {
                    taskVo.setDuration(DateUtil.formatBetween(taskInstance.getDurationInMillis(), BetweenFormatter.Level.SECOND));
                }
                // èŽ·å–æ„è§è¯„è®ºå†…å®¹
                if (CollUtil.isNotEmpty(commentList)) {
                    List<Comment> comments = new ArrayList<>();
                    // commentList.stream().filter(comment -> taskInstance.getId().equals(comment.getTaskId())).collect(Collectors.toList());
                    for (Comment comment : commentList) {
                        if (comment.getTaskId().equals(taskInstance.getId())) {
                            comments.add(comment);
                            // taskVo.setComment(WfCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build());
                        }
                    }
                    taskVo.setCommentList(comments);
                }
                taskVoList.add(taskVo);
            });
            map.put("flowList", taskVoList);
//            // æŸ¥è¯¢å½“前任务是否完成
//            List<Task> taskList = taskService.createTaskQuery().processInstanceId(procInsId).list();
//            if (CollectionUtils.isNotEmpty(taskList)) {
//                map.put("finished", true);
//            } else {
//                map.put("finished", false);
//            }
        }
        // ç¬¬ä¸€æ¬¡ç”³è¯·èŽ·å–åˆå§‹åŒ–è¡¨å•
        if (StringUtils.isNotBlank(deployId)) {
            WfFormVo formVo = deployFormService.selectDeployFormByDeployId(deployId);
            if (Objects.isNull(formVo)) {
                throw new ServiceException("请先配置流程表单");
            }
            map.put("formData", JsonUtils.parseObject(formVo.getContent(), Map.class));
        }
        return map;
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,364 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.flowable.common.constant.ProcessConstants;
import org.ruoyi.flowable.common.enums.FormType;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.factory.FlowServiceFactory;
import org.ruoyi.flowable.utils.JsonUtils;
import org.ruoyi.flowable.utils.ModelUtils;
import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
import org.ruoyi.flowable.workflow.domain.dto.WfMetaInfoDto;
import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
import org.ruoyi.flowable.workflow.service.IWfFormService;
import org.ruoyi.flowable.workflow.service.IWfModelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
 * @author KonBAI
 * @createTime 2022/6/21 9:11
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class WfModelServiceImpl extends FlowServiceFactory implements IWfModelService {
    private final IWfFormService formService;
    private final IWfDeployFormService deployFormService;
    @Override
    public TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery) {
        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
        // æž„建查询条件
        if (StringUtils.isNotBlank(modelBo.getModelKey())) {
            modelQuery.modelKey(modelBo.getModelKey());
        }
        if (StringUtils.isNotBlank(modelBo.getModelName())) {
            modelQuery.modelNameLike("%" + modelBo.getModelName() + "%");
        }
        if (StringUtils.isNotBlank(modelBo.getCategory())) {
            modelQuery.modelCategory(modelBo.getCategory());
        }
        // æ‰§è¡ŒæŸ¥è¯¢
        long pageTotal = modelQuery.count();
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<Model> modelList = modelQuery.listPage(offset, pageQuery.getPageSize());
        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
        modelList.forEach(model -> {
            WfModelVo modelVo = new WfModelVo();
            modelVo.setModelId(model.getId());
            modelVo.setModelName(model.getName());
            modelVo.setModelKey(model.getKey());
            modelVo.setCategory(model.getCategory());
            modelVo.setCreateTime(model.getCreateTime());
            modelVo.setVersion(model.getVersion());
            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
            if (metaInfo != null) {
                modelVo.setDescription(metaInfo.getDescription());
                modelVo.setFormType(metaInfo.getFormType());
                modelVo.setFormId(metaInfo.getFormId());
            }
            modelVoList.add(modelVo);
        });
        Page<WfModelVo> page = new Page<>();
        page.setRecords(modelVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfModelVo> list(WfModelBo modelBo) {
        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
        // æž„建查询条件
        if (StringUtils.isNotBlank(modelBo.getModelKey())) {
            modelQuery.modelKey(modelBo.getModelKey());
        }
        if (StringUtils.isNotBlank(modelBo.getModelName())) {
            modelQuery.modelNameLike("%" + modelBo.getModelName() + "%");
        }
        if (StringUtils.isNotBlank(modelBo.getCategory())) {
            modelQuery.modelCategory(modelBo.getCategory());
        }
        List<Model> modelList = modelQuery.list();
        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
        modelList.forEach(model -> {
            WfModelVo modelVo = new WfModelVo();
            modelVo.setModelId(model.getId());
            modelVo.setModelName(model.getName());
            modelVo.setModelKey(model.getKey());
            modelVo.setCategory(model.getCategory());
            modelVo.setCreateTime(model.getCreateTime());
            modelVo.setVersion(model.getVersion());
            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
            if (metaInfo != null) {
                modelVo.setDescription(metaInfo.getDescription());
                modelVo.setFormType(metaInfo.getFormType());
                modelVo.setFormId(metaInfo.getFormId());
            }
            modelVoList.add(modelVo);
        });
        return modelVoList;
    }
    @Override
    public TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery) {
        ModelQuery modelQuery = repositoryService.createModelQuery()
            .modelKey(modelBo.getModelKey())
            .orderByModelVersion()
            .desc();
        // æ‰§è¡ŒæŸ¥è¯¢ï¼ˆä¸æ˜¾ç¤ºæœ€æ–°ç‰ˆï¼Œ-1)
        long pageTotal = modelQuery.count() - 1;
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        // offset+1,去掉最新版
        int offset = 1 + pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<Model> modelList = modelQuery.listPage(offset, pageQuery.getPageSize());
        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
        modelList.forEach(model -> {
            WfModelVo modelVo = new WfModelVo();
            modelVo.setModelId(model.getId());
            modelVo.setModelName(model.getName());
            modelVo.setModelKey(model.getKey());
            modelVo.setCategory(model.getCategory());
            modelVo.setCreateTime(model.getCreateTime());
            modelVo.setVersion(model.getVersion());
            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
            if (metaInfo != null) {
                modelVo.setDescription(metaInfo.getDescription());
                modelVo.setFormType(metaInfo.getFormType());
                modelVo.setFormId(metaInfo.getFormId());
            }
            modelVoList.add(modelVo);
        });
        Page<WfModelVo> page = new Page<>();
        page.setRecords(modelVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }
    @Override
    public WfModelVo getModel(String modelId) {
        // èŽ·å–æµç¨‹æ¨¡åž‹
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在!");
        }
        // èŽ·å–æµç¨‹å›¾
        String bpmnXml = queryBpmnXmlById(modelId);
        WfModelVo modelVo = new WfModelVo();
        modelVo.setModelId(model.getId());
        modelVo.setModelName(model.getName());
        modelVo.setModelKey(model.getKey());
        modelVo.setCategory(model.getCategory());
        modelVo.setCreateTime(model.getCreateTime());
        modelVo.setVersion(model.getVersion());
        modelVo.setBpmnXml(bpmnXml);
        WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
        if (metaInfo != null) {
            modelVo.setDescription(metaInfo.getDescription());
            modelVo.setFormType(metaInfo.getFormType());
            modelVo.setFormId(metaInfo.getFormId());
            if (FormType.PROCESS.getType().equals(metaInfo.getFormType())) {
                WfFormVo wfFormVo = formService.queryById(metaInfo.getFormId());
                modelVo.setContent(wfFormVo.getContent());
            }
        }
        return modelVo;
    }
    @Override
    public String queryBpmnXmlById(String modelId) {
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        return StrUtil.utf8Str(bpmnBytes);
    }
    @Override
    public void insertModel(WfModelBo modelBo) {
        Model model = repositoryService.newModel();
        model.setName(modelBo.getModelName());
        model.setKey(modelBo.getModelKey());
        model.setCategory(modelBo.getCategory());
        String metaInfo = buildMetaInfo(new WfMetaInfoDto(), modelBo.getDescription());
        model.setMetaInfo(metaInfo);
        // ä¿å­˜æµç¨‹æ¨¡åž‹
        repositoryService.saveModel(model);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModel(WfModelBo modelBo) {
        // æ ¹æ®æ¨¡åž‹Key查询模型信息
        Model model = repositoryService.getModel(modelBo.getModelId());
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在!");
        }
        model.setCategory(modelBo.getCategory());
        WfMetaInfoDto metaInfoDto = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
        String metaInfo = buildMetaInfo(metaInfoDto, modelBo.getDescription());
        model.setMetaInfo(metaInfo);
        // ä¿å­˜æµç¨‹æ¨¡åž‹
        repositoryService.saveModel(model);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveModel(WfModelBo modelBo) {
        // æŸ¥è¯¢æ¨¡åž‹ä¿¡æ¯
        Model model = repositoryService.getModel(modelBo.getModelId());
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在!");
        }
        BpmnModel bpmnModel = ModelUtils.getBpmnModel(modelBo.getBpmnXml());
        if (ObjectUtil.isEmpty(bpmnModel)) {
            throw new RuntimeException("获取模型设计失败!");
        }
        String processName = bpmnModel.getMainProcess().getName();
        // èŽ·å–å¼€å§‹èŠ‚ç‚¹
        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
        if (ObjectUtil.isNull(startEvent)) {
            throw new RuntimeException("开始节点不存在,请检查流程设计是否有误!");
        }
        // èŽ·å–å¼€å§‹èŠ‚ç‚¹é…ç½®çš„è¡¨å•Key
        if (StrUtil.isBlank(startEvent.getFormKey())) {
            throw new RuntimeException("请配置流程表单");
        }
        Model newModel;
        if (Boolean.TRUE.equals(modelBo.getNewVersion())) {
            newModel = repositoryService.newModel();
            newModel.setName(processName);
            newModel.setKey(model.getKey());
            newModel.setCategory(model.getCategory());
            newModel.setMetaInfo(model.getMetaInfo());
            newModel.setVersion(model.getVersion() + 1);
        } else {
            newModel = model;
            // è®¾ç½®æµç¨‹åç§°
            newModel.setName(processName);
        }
        // ä¿å­˜æµç¨‹æ¨¡åž‹
        repositoryService.saveModel(newModel);
        // ä¿å­˜ BPMN XML
        byte[] bpmnXmlBytes = StringUtils.getBytes(modelBo.getBpmnXml(), StandardCharsets.UTF_8);
        repositoryService.addModelEditorSource(newModel.getId(), bpmnXmlBytes);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void latestModel(String modelId) {
        // èŽ·å–æµç¨‹æ¨¡åž‹
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在!");
        }
        Integer latestVersion = repositoryService.createModelQuery()
            .modelKey(model.getKey())
            .latestVersion()
            .singleResult()
            .getVersion();
        if (model.getVersion().equals(latestVersion)) {
            throw new RuntimeException("当前版本已是最新版!");
        }
        // èŽ·å– BPMN XML
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        Model newModel = repositoryService.newModel();
        newModel.setName(model.getName());
        newModel.setKey(model.getKey());
        newModel.setCategory(model.getCategory());
        newModel.setMetaInfo(model.getMetaInfo());
        newModel.setVersion(latestVersion + 1);
        // ä¿å­˜æµç¨‹æ¨¡åž‹
        repositoryService.saveModel(newModel);
        // ä¿å­˜ BPMN XML
        repositoryService.addModelEditorSource(newModel.getId(), bpmnBytes);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByIds(Collection<String> ids) {
        ids.forEach(id -> {
            Model model = repositoryService.getModel(id);
            if (ObjectUtil.isNull(model)) {
                throw new RuntimeException("流程模型不存在!");
            }
            repositoryService.deleteModel(id);
        });
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deployModel(String modelId) {
        // èŽ·å–æµç¨‹æ¨¡åž‹
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在!");
        }
        // èŽ·å–æµç¨‹å›¾
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        if (ArrayUtil.isEmpty(bpmnBytes)) {
            throw new RuntimeException("请先设计流程图!");
        }
        String bpmnXml = StringUtils.toEncodedString(bpmnBytes, StandardCharsets.UTF_8);
        BpmnModel bpmnModel = ModelUtils.getBpmnModel(bpmnXml);
        String processName = model.getName() + ProcessConstants.SUFFIX;
        // éƒ¨ç½²æµç¨‹
        Deployment deployment = repositoryService.createDeployment()
            .name(model.getName())
            .key(model.getKey())
            .category(model.getCategory())
            .addBytes(processName, bpmnBytes)
            .deploy();
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
            .deploymentId(deployment.getId())
            .singleResult();
        // ä¿®æ”¹æµç¨‹å®šä¹‰çš„分类,便于搜索流程
        repositoryService.setProcessDefinitionCategory(procDef.getId(), model.getCategory());
        // ä¿å­˜éƒ¨ç½²è¡¨å•
        return deployFormService.saveInternalDeployForm(deployment.getId(), bpmnModel);
    }
    /**
     * æž„建模型扩展信息
     * @return
     */
    private String buildMetaInfo(WfMetaInfoDto metaInfo, String description) {
        // åªæœ‰éžç©ºï¼Œæ‰è¿›è¡Œè®¾ç½®ï¼Œé¿å…æ›´æ–°æ—¶çš„覆盖
        if (StringUtils.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        }
        if (StringUtils.isNotEmpty(metaInfo.getCreateUser())) {
            // èŽ·å–å½“å‰çš„ç”¨æˆ·
            LoginUser loginUser = LoginHelper.getLoginUser();
            if (ObjectUtil.isNotNull(loginUser)){
                metaInfo.setCreateUser(loginUser.getUsername());
            }
        }
        return JsonUtils.toJsonString(metaInfo);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1037 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.DateUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.flowable.common.constant.ProcessConstants;
import org.ruoyi.flowable.common.constant.TaskConstants;
import org.ruoyi.flowable.common.enums.ProcessStatus;
import org.ruoyi.flowable.core.FormConf;
import org.ruoyi.flowable.core.domain.ProcessQuery;
import org.ruoyi.flowable.core.domain.model.PageQuery;
import org.ruoyi.flowable.core.page.TableDataInfo;
import org.ruoyi.flowable.factory.FlowServiceFactory;
import org.ruoyi.flowable.flow.FlowableUtils;
import org.ruoyi.flowable.utils.*;
import org.ruoyi.flowable.workflow.domain.WfDeployForm;
import org.ruoyi.flowable.workflow.domain.vo.*;
import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
import org.ruoyi.flowable.workflow.service.IWfProcessService;
import org.ruoyi.flowable.workflow.service.IWfTaskService;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricActivityInstanceQuery;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
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.api.history.HistoricTaskInstanceQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.ruoyi.system.domain.SysDept;
import org.ruoyi.system.domain.SysRole;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.vo.SysDeptVo;
import org.ruoyi.system.domain.vo.SysRoleVo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.service.ISysDeptService;
import org.ruoyi.system.service.ISysRoleService;
import org.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author KonBAI
 * @createTime 2022/3/24 18:57
 */
@RequiredArgsConstructor
@Service
public class WfProcessServiceImpl extends FlowServiceFactory implements IWfProcessService {
    private final IWfTaskService wfTaskService;
    private final ISysUserService userService;
    private final WfDeployFormMapper deployFormMapper;
    private final ISysDeptService deptService;
    private final ISysRoleService roleService;
    @Autowired
    private ISysUserService remoteUserService;
    @Autowired
    private ISysDeptService remoteDeptService;
    /**
     * æµç¨‹å®šä¹‰åˆ—表
     *
     * @param pageQuery åˆ†é¡µå‚æ•°
     * @return æµç¨‹å®šä¹‰åˆ†é¡µåˆ—表数据
     */
    @Override
    public TableDataInfo<WfDefinitionVo> selectPageStartProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfDefinitionVo> page = new Page<>();
        // æµç¨‹å®šä¹‰åˆ—表数据查询
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
            .latestVersion()
            .active()
            .orderByProcessDefinitionKey()
            .asc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
        long pageTotal = processDefinitionQuery.count();
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<ProcessDefinition> definitionList = processDefinitionQuery.listPage(offset, pageQuery.getPageSize());
        List<WfDefinitionVo> definitionVoList = new ArrayList<>();
        for (ProcessDefinition processDefinition : definitionList) {
            String deploymentId = processDefinition.getDeploymentId();
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            WfDefinitionVo vo = new WfDefinitionVo();
            vo.setDefinitionId(processDefinition.getId());
            vo.setProcessKey(processDefinition.getKey());
            vo.setProcessName(processDefinition.getName());
            vo.setVersion(processDefinition.getVersion());
            vo.setDeploymentId(processDefinition.getDeploymentId());
            vo.setSuspended(processDefinition.isSuspended());
            // æµç¨‹å®šä¹‰æ—¶é—´
            vo.setCategory(deployment.getCategory());
            vo.setDeploymentTime(deployment.getDeploymentTime());
            definitionVoList.add(vo);
        }
        page.setRecords(definitionVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfDefinitionVo> selectStartProcessList(ProcessQuery processQuery) {
        // æµç¨‹å®šä¹‰åˆ—表数据查询
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
                .latestVersion()
                .active()
                .orderByProcessDefinitionKey()
                .asc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
        List<ProcessDefinition> definitionList = processDefinitionQuery.list();
        List<WfDefinitionVo> definitionVoList = new ArrayList<>();
        for (ProcessDefinition processDefinition : definitionList) {
            String deploymentId = processDefinition.getDeploymentId();
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            WfDefinitionVo vo = new WfDefinitionVo();
            vo.setDefinitionId(processDefinition.getId());
            vo.setProcessKey(processDefinition.getKey());
            vo.setProcessName(processDefinition.getName());
            vo.setVersion(processDefinition.getVersion());
            vo.setDeploymentId(processDefinition.getDeploymentId());
            vo.setSuspended(processDefinition.isSuspended());
            // æµç¨‹å®šä¹‰æ—¶é—´
            vo.setCategory(deployment.getCategory());
            vo.setDeploymentTime(deployment.getDeploymentTime());
            definitionVoList.add(vo);
        }
        return definitionVoList;
    }
    @Override
    public TableDataInfo<WfTaskVo> selectPageOwnProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
            .startedBy(TaskUtils.getUserId())
            .orderByProcessInstanceStartTime()
            .desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(historicProcessInstanceQuery, processQuery);
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery
            .listPage(offset, pageQuery.getPageSize());
        page.setTotal(historicProcessInstanceQuery.count());
        List<WfTaskVo> taskVoList = new ArrayList<>();
        for (HistoricProcessInstance hisIns : historicProcessInstances) {
            WfTaskVo taskVo = new WfTaskVo();
            // èŽ·å–æµç¨‹çŠ¶æ€
            HistoricVariableInstance processStatusVariable = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(hisIns.getId())
                .variableName(ProcessConstants.PROCESS_STATUS_KEY)
                .singleResult();
            String processStatus = null;
            if (ObjectUtil.isNotNull(processStatusVariable)) {
                processStatus = Convert.toStr(processStatusVariable.getValue());
            }
            // å…¼å®¹æ—§æµç¨‹
            if (processStatus == null) {
                processStatus = ObjectUtil.isNull(hisIns.getEndTime()) ? ProcessStatus.RUNNING.getStatus() : ProcessStatus.COMPLETED.getStatus();
            }
            taskVo.setProcessStatus(processStatus);
            taskVo.setCreateTime(hisIns.getStartTime());
            taskVo.setFinishTime(hisIns.getEndTime());
            taskVo.setProcInsId(hisIns.getId());
            // è®¡ç®—耗时
            if (Objects.nonNull(hisIns.getEndTime())) {
                taskVo.setDuration(DateUtils.getDatePoor(hisIns.getEndTime(), hisIns.getStartTime()));
            } else {
                taskVo.setDuration(DateUtils.getDatePoor(DateUtils.getNowDate(), hisIns.getStartTime()));
            }
            // æµç¨‹éƒ¨ç½²å®žä¾‹ä¿¡æ¯
            Deployment deployment = repositoryService.createDeploymentQuery()
                .deploymentId(hisIns.getDeploymentId()).singleResult();
            taskVo.setDeployId(hisIns.getDeploymentId());
            taskVo.setProcDefId(hisIns.getProcessDefinitionId());
            taskVo.setProcDefName(hisIns.getProcessDefinitionName());
            taskVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
            taskVo.setCategory(deployment.getCategory());
            // å½“前所处流程
            List<Task> taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).includeIdentityLinks().list();
            if (CollUtil.isNotEmpty(taskList)) {
                taskVo.setTaskName(taskList.stream().map(Task::getName).filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")));
            }
            taskVoList.add(taskVo);
        }
        page.setRecords(taskVoList);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfTaskVo> selectOwnProcessList(ProcessQuery processQuery) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .startedBy(TaskUtils.getUserId())
                .orderByProcessInstanceStartTime()
                .desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(historicProcessInstanceQuery, processQuery);
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.list();
        List<WfTaskVo> taskVoList = new ArrayList<>();
        for (HistoricProcessInstance hisIns : historicProcessInstances) {
            WfTaskVo taskVo = new WfTaskVo();
            taskVo.setCreateTime(hisIns.getStartTime());
            taskVo.setFinishTime(hisIns.getEndTime());
            taskVo.setProcInsId(hisIns.getId());
            // è®¡ç®—耗时
            if (Objects.nonNull(hisIns.getEndTime())) {
                taskVo.setDuration(DateUtils.getDatePoor(hisIns.getEndTime(), hisIns.getStartTime()));
            } else {
                taskVo.setDuration(DateUtils.getDatePoor(DateUtils.getNowDate(), hisIns.getStartTime()));
            }
            // æµç¨‹éƒ¨ç½²å®žä¾‹ä¿¡æ¯
            Deployment deployment = repositoryService.createDeploymentQuery()
                    .deploymentId(hisIns.getDeploymentId()).singleResult();
            taskVo.setDeployId(hisIns.getDeploymentId());
            taskVo.setProcDefId(hisIns.getProcessDefinitionId());
            taskVo.setProcDefName(hisIns.getProcessDefinitionName());
            taskVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
            taskVo.setCategory(deployment.getCategory());
            // å½“前所处流程
            List<Task> taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).includeIdentityLinks().list();
            if (CollUtil.isNotEmpty(taskList)) {
                taskVo.setTaskName(taskList.stream().map(Task::getName).filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")));
            }
            taskVoList.add(taskVo);
        }
        return taskVoList;
    }
    @Override
    public TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
        TaskQuery taskQuery = taskService.createTaskQuery()
            .active()
            .includeProcessVariables()
            .taskCandidateOrAssigned(TaskUtils.getUserId())
            .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
            .orderByTaskCreateTime().desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
        page.setTotal(taskQuery.count());
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize());
        List<WfTaskVo> flowList = new ArrayList<>();
        for (Task task : taskList) {
            WfTaskVo flowTask = new WfTaskVo();
            // å½“前流程信息
            flowTask.setTaskId(task.getId());
            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
            flowTask.setCreateTime(task.getCreateTime());
            flowTask.setProcDefId(task.getProcessDefinitionId());
            flowTask.setTaskName(task.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(task.getProcessDefinitionId())
                .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(task.getProcessInstanceId());
            flowTask.setCategory(pd.getCategory());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);
            // æµç¨‹å˜é‡
            flowTask.setProcVars(task.getProcessVariables());
            flowList.add(flowTask);
        }
        page.setRecords(flowList);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfTaskVo> selectTodoProcessList(ProcessQuery processQuery) {
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active()
                .includeProcessVariables()
                .taskCandidateOrAssigned(TaskUtils.getUserId())
                .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
                .orderByTaskCreateTime().desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
        List<Task> taskList = taskQuery.list();
        List<WfTaskVo> taskVoList = new ArrayList<>();
        for (Task task : taskList) {
            WfTaskVo taskVo = new WfTaskVo();
            // å½“前流程信息
            taskVo.setTaskId(task.getId());
            taskVo.setTaskDefKey(task.getTaskDefinitionKey());
            taskVo.setCreateTime(task.getCreateTime());
            taskVo.setProcDefId(task.getProcessDefinitionId());
            taskVo.setTaskName(task.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(task.getProcessDefinitionId())
                    .singleResult();
            taskVo.setDeployId(pd.getDeploymentId());
            taskVo.setProcDefName(pd.getName());
            taskVo.setProcDefVersion(pd.getVersion());
            taskVo.setProcInsId(task.getProcessInstanceId());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            taskVo.setStartUserId(userId);
            taskVo.setStartUserName(nickName);
            taskVoList.add(taskVo);
        }
        return taskVoList;
    }
    @Override
    public TableDataInfo<WfTaskVo> selectPageClaimProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
        TaskQuery taskQuery = taskService.createTaskQuery()
            .active()
            .includeProcessVariables()
            .taskCandidateUser(TaskUtils.getUserId())
            .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
            .orderByTaskCreateTime().desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
        page.setTotal(taskQuery.count());
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize());
        List<WfTaskVo> flowList = new ArrayList<>();
        for (Task task : taskList) {
            WfTaskVo flowTask = new WfTaskVo();
            // å½“前流程信息
            flowTask.setTaskId(task.getId());
            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
            flowTask.setCreateTime(task.getCreateTime());
            flowTask.setProcDefId(task.getProcessDefinitionId());
            flowTask.setTaskName(task.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(task.getProcessDefinitionId())
                .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(task.getProcessInstanceId());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);
            flowList.add(flowTask);
        }
        page.setRecords(flowList);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfTaskVo> selectClaimProcessList(ProcessQuery processQuery) {
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active()
                .includeProcessVariables()
                .taskCandidateUser(TaskUtils.getUserId())
                .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
                .orderByTaskCreateTime().desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
        List<Task> taskList = taskQuery.list();
        List<WfTaskVo> flowList = new ArrayList<>();
        for (Task task : taskList) {
            WfTaskVo flowTask = new WfTaskVo();
            // å½“前流程信息
            flowTask.setTaskId(task.getId());
            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
            flowTask.setCreateTime(task.getCreateTime());
            flowTask.setProcDefId(task.getProcessDefinitionId());
            flowTask.setTaskName(task.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(task.getProcessDefinitionId())
                    .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(task.getProcessInstanceId());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);
            flowList.add(flowTask);
        }
        return flowList;
    }
    @Override
    public TableDataInfo<WfTaskVo> selectPageFinishedProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
        HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
            .includeProcessVariables()
            .finished()
            .taskAssignee(TaskUtils.getUserId())
            .orderByHistoricTaskInstanceEndTime()
            .desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskInstanceQuery, processQuery);
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<HistoricTaskInstance> historicTaskInstanceList = taskInstanceQuery.listPage(offset, pageQuery.getPageSize());
        List<WfTaskVo> hisTaskList = new ArrayList<>();
        for (HistoricTaskInstance histTask : historicTaskInstanceList) {
            WfTaskVo flowTask = new WfTaskVo();
            // å½“前流程信息
            flowTask.setTaskId(histTask.getId());
            // å®¡æ‰¹äººå‘˜ä¿¡æ¯
            flowTask.setCreateTime(histTask.getCreateTime());
            flowTask.setFinishTime(histTask.getEndTime());
            flowTask.setDuration(DateUtil.formatBetween(histTask.getDurationInMillis(), BetweenFormatter.Level.SECOND));
            flowTask.setProcDefId(histTask.getProcessDefinitionId());
            flowTask.setTaskDefKey(histTask.getTaskDefinitionKey());
            flowTask.setTaskName(histTask.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(histTask.getProcessDefinitionId())
                .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(histTask.getProcessInstanceId());
            flowTask.setHisProcInsId(histTask.getProcessInstanceId());
            flowTask.setCategory(pd.getCategory());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(histTask.getProcessInstanceId())
                .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);
            // æµç¨‹å˜é‡
            flowTask.setProcVars(histTask.getProcessVariables());
            hisTaskList.add(flowTask);
        }
        page.setTotal(taskInstanceQuery.count());
        page.setRecords(hisTaskList);
//        Map<String, Object> result = new HashMap<>();
//        result.put("result",page);
//        result.put("finished",true);
        return TableDataInfo.build(page);
    }
    @Override
    public List<WfTaskVo> selectFinishedProcessList(ProcessQuery processQuery) {
        HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .includeProcessVariables()
                .finished()
                .taskAssignee(TaskUtils.getUserId())
                .orderByHistoricTaskInstanceEndTime()
                .desc();
        // æž„建搜索条件
        ProcessUtils.buildProcessSearch(taskInstanceQuery, processQuery);
        List<HistoricTaskInstance> historicTaskInstanceList = taskInstanceQuery.list();
        List<WfTaskVo> hisTaskList = new ArrayList<>();
        for (HistoricTaskInstance histTask : historicTaskInstanceList) {
            WfTaskVo flowTask = new WfTaskVo();
            // å½“前流程信息
            flowTask.setTaskId(histTask.getId());
            // å®¡æ‰¹äººå‘˜ä¿¡æ¯
            flowTask.setCreateTime(histTask.getCreateTime());
            flowTask.setFinishTime(histTask.getEndTime());
            flowTask.setDuration(DateUtil.formatBetween(histTask.getDurationInMillis(), BetweenFormatter.Level.SECOND));
            flowTask.setProcDefId(histTask.getProcessDefinitionId());
            flowTask.setTaskDefKey(histTask.getTaskDefinitionKey());
            flowTask.setTaskName(histTask.getName());
            // æµç¨‹å®šä¹‰ä¿¡æ¯
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(histTask.getProcessDefinitionId())
                    .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(histTask.getProcessInstanceId());
            flowTask.setHisProcInsId(histTask.getProcessInstanceId());
            // æµç¨‹å‘起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(histTask.getProcessInstanceId())
                    .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = null;
            SysUserVo user = userService.selectUserById(userId);
            if(!ObjectUtil.isNull(user)) {
                nickName=user.getNickName();
            }
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);
            // æµç¨‹å˜é‡
            flowTask.setProcVars(histTask.getProcessVariables());
            hisTaskList.add(flowTask);
        }
        return hisTaskList;
    }
    @Override
    public FormConf selectFormContent(String definitionId, String deployId, String procInsId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(definitionId);
        if (ObjectUtil.isNull(bpmnModel)) {
            throw new RuntimeException("获取流程设计失败!");
        }
        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
        WfDeployForm deployForm = deployFormMapper.selectOne(new LambdaQueryWrapper<WfDeployForm>()
            .eq(WfDeployForm::getDeployId, deployId)
            .eq(WfDeployForm::getFormKey, startEvent.getFormKey())
            .eq(WfDeployForm::getNodeKey, startEvent.getId()));
        Map<String, Object> formModel = JsonUtils.parseObject(deployForm.getContent(), Map.class);
        if (null == formModel || formModel.isEmpty()) {
            throw new RuntimeException("获取流程表单失败!");
        }
        FormConf formConf = new FormConf();
        formConf.setFormBtns(false);
        formConf.setFormModel(formModel);
        if (ObjectUtil.isNotEmpty(procInsId)) {
            // èŽ·å–æµç¨‹å®žä¾‹
            HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(procInsId)
                    .includeProcessVariables()
                    .singleResult();
            formConf.setFormData(historicProcIns.getProcessVariables());
        }
        return formConf;
    }
    /**
     * æ ¹æ®æµç¨‹å®šä¹‰ID启动流程实例
     *
     * @param procDefId æµç¨‹å®šä¹‰Id
     * @param variables æµç¨‹å˜é‡
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startProcessByDefId(String procDefId, Map<String, Object> variables) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(procDefId).singleResult();
            startProcess(processDefinition, variables);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("流程启动错误");
        }
    }
    /**
     * é€šè¿‡DefinitionKey启动流程
     * @param procDefKey æµç¨‹å®šä¹‰Key
     * @param variables æ‰©å±•参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startProcessByDefKey(String procDefKey, Map<String, Object> variables) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey).latestVersion().singleResult();
            startProcess(processDefinition, variables);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("流程启动错误");
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcessByIds(String[] instanceIds) {
        List<String> ids = Arrays.asList(instanceIds);
        // æ ¡éªŒæµç¨‹æ˜¯å¦ç»“束
        long activeInsCount = runtimeService.createProcessInstanceQuery()
            .processInstanceIds(new HashSet<>(ids)).active().count();
        if (activeInsCount > 0) {
            throw new ServiceException("不允许删除进行中的流程实例");
        }
        // åˆ é™¤åŽ†å²æµç¨‹å®žä¾‹
        historyService.bulkDeleteHistoricProcessInstances(ids);
    }
    /**
     * è¯»å–xml文件
     * @param processDefId æµç¨‹å®šä¹‰ID
     */
    @Override
    public String queryBpmnXmlById(String processDefId) {
        InputStream inputStream = repositoryService.getProcessModel(processDefId);
        try {
            return IoUtil.readUtf8(inputStream);
        } catch (IORuntimeException exception) {
            throw new RuntimeException("加载xml文件异常");
        }
    }
    /**
     * æµç¨‹è¯¦æƒ…信息
     *
     * @param procInsId æµç¨‹å®žä¾‹ID
     * @param taskId ä»»åŠ¡ID
     * @return
     */
    @Override
    public WfDetailVo queryProcessDetail(String procInsId, String taskId) {
        WfDetailVo detailVo = new WfDetailVo();
        // èŽ·å–æµç¨‹å®žä¾‹
        HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
            .processInstanceId(procInsId)
            .includeProcessVariables()
            .singleResult();
        if (StringUtils.isNotBlank(taskId)) {
            HistoricTaskInstance taskIns = historyService.createHistoricTaskInstanceQuery()
                .taskId(taskId)
                .includeIdentityLinks()
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .singleResult();
            if (taskIns == null) {
                throw new ServiceException("没有可办理的任务!");
            }
            detailVo.setTaskFormData(currTaskFormData(historicProcIns.getDeploymentId(), taskIns));
        }
        // èŽ·å–Bpmn模型信息
        InputStream inputStream = repositoryService.getProcessModel(historicProcIns.getProcessDefinitionId());
        String bpmnXmlStr = StrUtil.utf8Str(IoUtil.readBytes(inputStream, false));
        BpmnModel bpmnModel = ModelUtils.getBpmnModel(bpmnXmlStr);
        detailVo.setBpmnXml(bpmnXmlStr);
        detailVo.setHistoryProcNodeList(historyProcNodeList(historicProcIns));
        detailVo.setProcessFormList(processFormList(bpmnModel, historicProcIns));
        detailVo.setFlowViewer(getFlowViewer(bpmnModel, procInsId));
        return detailVo;
    }
    /**
     * å¯åŠ¨æµç¨‹å®žä¾‹
     */
    private void startProcess(ProcessDefinition procDef, Map<String, Object> variables) {
        if (ObjectUtil.isNotNull(procDef) && procDef.isSuspended()) {
            throw new ServiceException("流程已被挂起,请先激活流程");
        }
        // è®¾ç½®æµç¨‹å‘起人Id到流程中
        String userIdStr = TaskUtils.getUserId();
        identityService.setAuthenticatedUserId(userIdStr);
        variables.put(BpmnXMLConstants.ATTRIBUTE_EVENT_START_INITIATOR, userIdStr);
        // è®¾ç½®æµç¨‹çŠ¶æ€ä¸ºè¿›è¡Œä¸­
        variables.put(ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.RUNNING.getStatus());
        // å‘起流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDef.getId(), variables);
        // ç¬¬ä¸€ä¸ªç”¨æˆ·ä»»åŠ¡ä¸ºå‘èµ·äººï¼Œåˆ™è‡ªåŠ¨å®Œæˆä»»åŠ¡
        wfTaskService.startFirstTask(processInstance, variables);
    }
    /**
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param taskId ä»»åŠ¡ID
     * @return æµç¨‹å˜é‡
     */
    private Map<String, Object> getProcessVariables(String taskId) {
        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
            .includeProcessVariables()
            .finished()
            .taskId(taskId)
            .singleResult();
        if (Objects.nonNull(historicTaskInstance)) {
            return historicTaskInstance.getProcessVariables();
        }
        return taskService.getVariables(taskId);
    }
    /**
     * èŽ·å–å½“å‰ä»»åŠ¡æµç¨‹è¡¨å•ä¿¡æ¯
     */
    private FormConf currTaskFormData(String deployId, HistoricTaskInstance taskIns) {
        WfDeployFormVo deployFormVo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
            .eq(WfDeployForm::getDeployId, deployId)
            .eq(WfDeployForm::getFormKey, taskIns.getFormKey())
            .eq(WfDeployForm::getNodeKey, taskIns.getTaskDefinitionKey()));
        if (ObjectUtil.isNotEmpty(deployFormVo)) {
            FormConf currTaskFormData = JsonUtils.parseObject(deployFormVo.getContent(), FormConf.class);
            if (null != currTaskFormData) {
                currTaskFormData.setFormBtns(false);
                ProcessFormUtils.fillFormData(currTaskFormData, taskIns.getTaskLocalVariables());
                return currTaskFormData;
            }
        }
        return null;
    }
    /**
     * èŽ·å–åŽ†å²æµç¨‹è¡¨å•ä¿¡æ¯
     */
    private List<FormConf> processFormList(BpmnModel bpmnModel, HistoricProcessInstance historicProcIns) {
        List<FormConf> procFormList = new ArrayList<>();
        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(historicProcIns.getId()).finished()
            .activityTypes(CollUtil.newHashSet(BpmnXMLConstants.ELEMENT_EVENT_START, BpmnXMLConstants.ELEMENT_TASK_USER))
            .orderByHistoricActivityInstanceStartTime().asc()
            .list();
        List<String> processFormKeys = new ArrayList<>();
        for (HistoricActivityInstance activityInstance : activityInstanceList) {
            // èŽ·å–å½“å‰èŠ‚ç‚¹æµç¨‹å…ƒç´ ä¿¡æ¯
            FlowElement flowElement = ModelUtils.getFlowElementById(bpmnModel, activityInstance.getActivityId());
            // èŽ·å–å½“å‰èŠ‚ç‚¹è¡¨å•Key
            String formKey = ModelUtils.getFormKey(flowElement);
            if (formKey == null) {
                continue;
            }
            boolean localScope = Convert.toBool(ModelUtils.getElementAttributeValue(flowElement, ProcessConstants.PROCESS_FORM_LOCAL_SCOPE), false);
            Map<String, Object> variables;
            if (localScope) {
                // æŸ¥è¯¢ä»»åŠ¡èŠ‚ç‚¹å‚æ•°ï¼Œå¹¶è½¬æ¢æˆMap
                variables = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(historicProcIns.getId())
                    .taskId(activityInstance.getTaskId())
                    .list()
                    .stream()
                    .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
            } else {
                if (processFormKeys.contains(formKey)) {
                    continue;
                }
                variables = historicProcIns.getProcessVariables();
                processFormKeys.add(formKey);
            }
            // éžèŠ‚ç‚¹è¡¨å•æ­¤å¤„æŸ¥è¯¢ç»“æžœå¯èƒ½æœ‰å¤šæ¡ï¼ŒåªèŽ·å–ç¬¬ä¸€æ¡ä¿¡æ¯
            List<WfDeployFormVo> formInfoList = deployFormMapper.selectVoList(new LambdaQueryWrapper<WfDeployForm>()
                .eq(WfDeployForm::getDeployId, historicProcIns.getDeploymentId())
                .eq(WfDeployForm::getFormKey, formKey)
                .eq(localScope, WfDeployForm::getNodeKey, flowElement.getId()));
            //@update by Brath:避免空集合导致的NULL空指针
            WfDeployFormVo formInfo = formInfoList.stream().findFirst().orElse(null);
            if (ObjectUtil.isNotNull(formInfo)) {
                // æ—§æ•°æ® formInfo.getFormName() ä¸º null
                String formName = Optional.ofNullable(formInfo.getFormName()).orElse(StringUtils.EMPTY);
                String title = localScope ? formName.concat("(" + flowElement.getName() + ")") : formName;
                FormConf formConf = new FormConf();
                Map<String, Object> formModel = JsonUtils.parseObject(formInfo.getContent(), Map.class);
                if (null != formModel && !formModel.isEmpty()) {
                    formConf.setTitle(title);
                    formConf.setDisabled(true);
                    formConf.setFormBtns(false);
                    formConf.setFormModel(formModel);
                    formConf.setFormData(variables);
                    procFormList.add(formConf);
                }
            }
        }
        return procFormList;
    }
    @Deprecated
    private void buildStartFormData(HistoricProcessInstance historicProcIns, Process process, String deployId, List<FormConf> procFormList) {
        procFormList = procFormList == null ? new ArrayList<>() : procFormList;
        HistoricActivityInstance startInstance = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(historicProcIns.getId())
            .activityId(historicProcIns.getStartActivityId())
            .singleResult();
        StartEvent startEvent = (StartEvent) process.getFlowElement(startInstance.getActivityId());
        WfDeployFormVo startFormInfo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
            .eq(WfDeployForm::getDeployId, deployId)
            .eq(WfDeployForm::getFormKey, startEvent.getFormKey())
            .eq(WfDeployForm::getNodeKey, startEvent.getId()));
        if (ObjectUtil.isNotNull(startFormInfo)) {
            FormConf formConf = JsonUtils.parseObject(startFormInfo.getContent(), FormConf.class);
            if (null != formConf) {
                formConf.setTitle(startEvent.getName());
                formConf.setDisabled(true);
                formConf.setFormBtns(false);
                ProcessFormUtils.fillFormData(formConf, historicProcIns.getProcessVariables());
                procFormList.add(formConf);
            }
        }
    }
    @Deprecated
    private void buildUserTaskFormData(String procInsId, String deployId, Process process, List<FormConf> procFormList) {
        procFormList = procFormList == null ? new ArrayList<>() : procFormList;
        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(procInsId).finished()
            .activityType(BpmnXMLConstants.ELEMENT_TASK_USER)
            .orderByHistoricActivityInstanceStartTime().asc()
            .list();
        for (HistoricActivityInstance instanceItem : activityInstanceList) {
            UserTask userTask = (UserTask) process.getFlowElement(instanceItem.getActivityId(), true);
            String formKey = userTask.getFormKey();
            if (formKey == null) {
                continue;
            }
            // æŸ¥è¯¢ä»»åŠ¡èŠ‚ç‚¹å‚æ•°ï¼Œå¹¶è½¬æ¢æˆMap
            Map<String, Object> variables = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(procInsId)
                .taskId(instanceItem.getTaskId())
                .list()
                .stream()
                .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
            WfDeployFormVo deployFormVo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
                .eq(WfDeployForm::getDeployId, deployId)
                .eq(WfDeployForm::getFormKey, formKey)
                .eq(WfDeployForm::getNodeKey, userTask.getId()));
            if (ObjectUtil.isNotNull(deployFormVo)) {
                FormConf formConf = JsonUtils.parseObject(deployFormVo.getContent(), FormConf.class);
                if (null != formConf) {
                    formConf.setTitle(userTask.getName());
                    formConf.setDisabled(true);
                    formConf.setFormBtns(false);
                    ProcessFormUtils.fillFormData(formConf, variables);
                    procFormList.add(formConf);
                }
            }
        }
    }
    /**
     * èŽ·å–åŽ†å²ä»»åŠ¡ä¿¡æ¯åˆ—è¡¨
     */
    private List<WfProcNodeVo> historyProcNodeList(HistoricProcessInstance historicProcIns) {
        String procInsId = historicProcIns.getId();
        List<HistoricActivityInstance> historicActivityInstanceList =  historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(procInsId)
            .activityTypes(CollUtil.newHashSet(BpmnXMLConstants.ELEMENT_EVENT_START, BpmnXMLConstants.ELEMENT_EVENT_END, BpmnXMLConstants.ELEMENT_TASK_USER))
            .orderByHistoricActivityInstanceStartTime().desc()
            .orderByHistoricActivityInstanceEndTime().desc()
            .list();
        List<Comment> commentList = taskService.getProcessInstanceComments(procInsId);
        List<WfProcNodeVo> elementVoList = new ArrayList<>();
        for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
            WfProcNodeVo elementVo = new WfProcNodeVo();
            elementVo.setProcDefId(activityInstance.getProcessDefinitionId());
            elementVo.setActivityId(activityInstance.getActivityId());
            elementVo.setActivityName(activityInstance.getActivityName());
            elementVo.setActivityType(activityInstance.getActivityType());
            elementVo.setCreateTime(activityInstance.getStartTime());
            elementVo.setEndTime(activityInstance.getEndTime());
            if (ObjectUtil.isNotNull(activityInstance.getDurationInMillis())) {
                elementVo.setDuration(DateUtil.formatBetween(activityInstance.getDurationInMillis(), BetweenFormatter.Level.SECOND));
            }
            if (BpmnXMLConstants.ELEMENT_EVENT_START.equals(activityInstance.getActivityType())) {
                if (ObjectUtil.isNotNull(historicProcIns)) {
                    Long userId = Long.parseLong(historicProcIns.getStartUserId());
                    String nickName = null;
                    SysUserVo user = userService.selectUserById(userId);
                    if(!ObjectUtil.isNull(user)) {
                        nickName=user.getNickName();
                    }
                    if (nickName != null) {
                        elementVo.setAssigneeId(userId);
                        elementVo.setAssigneeName(nickName);
                    }
                }
            } else if (BpmnXMLConstants.ELEMENT_TASK_USER.equals(activityInstance.getActivityType())) {
                if (StringUtils.isNotBlank(activityInstance.getAssignee())) {
                    Long userId = Long.parseLong(activityInstance.getAssignee());
                    String nickName = null;
                    SysUserVo user = userService.selectUserById(userId);
                    if(!ObjectUtil.isNull(user)) {
                        nickName=user.getNickName();
                    }
                    elementVo.setAssigneeId(userId);
                    elementVo.setAssigneeName(nickName);
                }
                // å±•示审批人员
                List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(activityInstance.getTaskId());
                StringBuilder stringBuilder = new StringBuilder();
                for (HistoricIdentityLink identityLink : linksForTask) {
                    if ("candidate".equals(identityLink.getType())) {
                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
                            Long userId = Long.parseLong(identityLink.getUserId());
                            String nickName = null;
                            SysUserVo user = userService.selectUserById(userId);
                            if(!ObjectUtil.isNull(user)) {
                                nickName=user.getNickName();
                            }
                            stringBuilder.append(nickName).append(",");
                        }
                        if (StringUtils.isNotBlank(identityLink.getGroupId())) {
                            if (identityLink.getGroupId().startsWith(TaskConstants.ROLE_GROUP_PREFIX)) {
                                Long roleId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.ROLE_GROUP_PREFIX));
                                //finish do æŸ¥è¯¢è§’色名称
                                SysRoleVo role=roleService.selectRoleById(roleId);
                                if (!ObjectUtil.isNull(role)) {
                                    stringBuilder.append(role.getRoleName()).append(",");
                                }
                            } else if (identityLink.getGroupId().startsWith(TaskConstants.DEPT_GROUP_PREFIX)) {
                                Long deptId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.DEPT_GROUP_PREFIX));
                                //finish do æŸ¥è¯¢éƒ¨é—¨åç§°
                                SysDeptVo deptR=deptService.selectDeptById(deptId);
                                if (!ObjectUtil.isNull(deptR) ) {
                                    stringBuilder.append(deptR.getDeptName()).append(",");
                                }
                            }
                        }
                    }
                }
                if (StringUtils.isNotBlank(stringBuilder)) {
                    elementVo.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1));
                }
                // èŽ·å–æ„è§è¯„è®ºå†…å®¹
                if (CollUtil.isNotEmpty(commentList)) {
                    List<Comment> comments = new ArrayList<>();
                    for (Comment comment : commentList) {
                        if (comment.getTaskId().equals(activityInstance.getTaskId())) {
                            comments.add(comment);
                        }
                    }
                    elementVo.setCommentList(comments);
                }
            }
            elementVoList.add(elementVo);
        }
        return elementVoList;
    }
    /**
     * èŽ·å–æµç¨‹æ‰§è¡Œè¿‡ç¨‹
     *
     * @param procInsId
     * @return
     */
    private WfViewerVo getFlowViewer(BpmnModel bpmnModel, String procInsId) {
        // æž„建查询条件
        HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(procInsId);
        List<HistoricActivityInstance> allActivityInstanceList = query.list();
        if (CollUtil.isEmpty(allActivityInstanceList)) {
            return new WfViewerVo();
        }
        // æŸ¥è¯¢æ‰€æœ‰å·²å®Œæˆçš„元素
        List<HistoricActivityInstance> finishedElementList = allActivityInstanceList.stream()
            .filter(item -> ObjectUtil.isNotNull(item.getEndTime())).collect(Collectors.toList());
        // æ‰€æœ‰å·²å®Œæˆçš„连线
        Set<String> finishedSequenceFlowSet = new HashSet<>();
        // æ‰€æœ‰å·²å®Œæˆçš„任务节点
        Set<String> finishedTaskSet = new HashSet<>();
        finishedElementList.forEach(item -> {
            if (BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW.equals(item.getActivityType())) {
                finishedSequenceFlowSet.add(item.getActivityId());
            } else {
                finishedTaskSet.add(item.getActivityId());
            }
        });
        // æŸ¥è¯¢æ‰€æœ‰æœªç»“束的节点
        Set<String> unfinishedTaskSet = allActivityInstanceList.stream()
            .filter(item -> ObjectUtil.isNull(item.getEndTime()))
            .map(HistoricActivityInstance::getActivityId)
            .collect(Collectors.toSet());
        // DFS æŸ¥è¯¢æœªé€šè¿‡çš„元素集合
        Set<String> rejectedSet = FlowableUtils.dfsFindRejects(bpmnModel, unfinishedTaskSet, finishedSequenceFlowSet, finishedTaskSet);
        return new WfViewerVo(finishedTaskSet, finishedSequenceFlowSet, unfinishedTaskSet, rejectedSet);
    }
}
ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,664 @@
package org.ruoyi.flowable.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.flowable.common.constant.ProcessConstants;
import org.ruoyi.flowable.common.constant.TaskConstants;
import org.ruoyi.flowable.common.enums.FlowComment;
import org.ruoyi.flowable.common.enums.ProcessStatus;
import org.ruoyi.flowable.factory.FlowServiceFactory;
import org.ruoyi.flowable.flow.CustomProcessDiagramGenerator;
import org.ruoyi.flowable.flow.FlowableUtils;
import org.ruoyi.flowable.utils.ModelUtils;
import org.ruoyi.flowable.utils.TaskUtils;
import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
import org.ruoyi.flowable.workflow.service.IWfCopyService;
import org.ruoyi.flowable.workflow.service.IWfTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.service.ISysUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author KonBAI
 * @createTime 2022/3/10 00:12
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class WfTaskServiceImpl extends FlowServiceFactory implements IWfTaskService {
    private final ISysUserService sysUserService;
    private final IWfCopyService copyService;
    /**
     * å®Œæˆä»»åŠ¡
     *
     * @param taskBo è¯·æ±‚实体参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void complete(WfTaskBo taskBo) {
        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
        if (Objects.isNull(task)) {
            throw new ServiceException("任务不存在");
        }
        // èŽ·å– bpmn æ¨¡åž‹
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.DELEGATE.getType(), taskBo.getComment());
            taskService.resolveTask(taskBo.getTaskId());
        } else {
            taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.NORMAL.getType(), taskBo.getComment());
            taskService.setAssignee(taskBo.getTaskId(), TaskUtils.getUserId());
            if (ObjectUtil.isNotEmpty(taskBo.getVariables())) {
                // èŽ·å–æ¨¡åž‹ä¿¡æ¯
                String localScopeValue = ModelUtils.getUserTaskAttributeValue(bpmnModel, task.getTaskDefinitionKey(), ProcessConstants.PROCESS_FORM_LOCAL_SCOPE);
                boolean localScope = Convert.toBool(localScopeValue, false);
                taskService.complete(taskBo.getTaskId(), taskBo.getVariables(), localScope);
            } else {
                taskService.complete(taskBo.getTaskId());
            }
        }
        // è®¾ç½®ä»»åŠ¡èŠ‚ç‚¹åç§°
        taskBo.setTaskName(task.getName());
        // å¤„理下一级审批人
        if (StringUtils.isNotBlank(taskBo.getNextUserIds())) {
            this.assignNextUsers(bpmnModel, taskBo.getProcInsId(), taskBo.getNextUserIds());
        }
        // å¤„理抄送用户
        if (!copyService.makeCopy(taskBo)) {
            throw new RuntimeException("抄送任务失败");
        }
    }
    /**
     * æ‹’绝任务
     *
     * @param taskBo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void taskReject(WfTaskBo taskBo) {
        // å½“前任务 task
        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new RuntimeException("获取任务信息异常!");
        }
        if (task.isSuspended()) {
            throw new RuntimeException("任务处于挂起状态");
        }
        // èŽ·å–æµç¨‹å®žä¾‹
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
            .processInstanceId(taskBo.getProcInsId())
            .singleResult();
        if (processInstance == null) {
            throw new RuntimeException("流程实例不存在,请确认!");
        }
        // èŽ·å–æµç¨‹å®šä¹‰ä¿¡æ¯
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .processDefinitionId(task.getProcessDefinitionId())
            .singleResult();
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        // æ·»åŠ å®¡æ‰¹æ„è§
        taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.REJECT.getType(), taskBo.getComment());
        // è®¾ç½®æµç¨‹çŠ¶æ€ä¸ºå·²ç»ˆç»“
        runtimeService.setVariable(processInstance.getId(), ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.TERMINATED.getStatus());
        // èŽ·å–æ‰€æœ‰èŠ‚ç‚¹ä¿¡æ¯
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        EndEvent endEvent = ModelUtils.getEndEvent(bpmnModel);
        // ç»ˆæ­¢æµç¨‹
        List<Execution> executions = runtimeService.createExecutionQuery().parentId(task.getProcessInstanceId()).list();
        List<String> executionIds = executions.stream().map(Execution::getId).collect(Collectors.toList());
        runtimeService.createChangeActivityStateBuilder()
            .processInstanceId(task.getProcessInstanceId())
            .moveExecutionsToSingleActivityId(executionIds, endEvent.getId())
            .changeState();
        // å¤„理抄送用户
        if (!copyService.makeCopy(taskBo)) {
            throw new RuntimeException("抄送任务失败");
        }
    }
    /**
     * é€€å›žä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void taskReturn(WfTaskBo bo) {
        // å½“前任务 task
        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new RuntimeException("获取任务信息异常!");
        }
        if (task.isSuspended()) {
            throw new RuntimeException("任务处于挂起状态");
        }
        // èŽ·å–æµç¨‹å®šä¹‰ä¿¡æ¯
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        // èŽ·å–æµç¨‹æ¨¡åž‹ä¿¡æ¯
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        // èŽ·å–å½“å‰ä»»åŠ¡èŠ‚ç‚¹å…ƒç´ 
        FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        // èŽ·å–è·³è½¬çš„èŠ‚ç‚¹å…ƒç´ 
        FlowElement target = ModelUtils.getFlowElementById(bpmnModel, bo.getTargetKey());
        // ä»Žå½“前节点向前扫描,判断当前节点与目标节点是否属于串行,若目标节点是在并行网关上或非同一路线上,不可跳转
        boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
        if (!isSequential) {
            throw new RuntimeException("当前节点相对于目标节点,不属于串行关系,无法回退");
        }
        // èŽ·å–æ‰€æœ‰æ­£å¸¸è¿›è¡Œçš„ä»»åŠ¡èŠ‚ç‚¹ Key,这些任务不能直接使用,需要找出其中需要撤回的任务
        List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
        List<String> runTaskKeyList = new ArrayList<>();
        runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
        // éœ€é€€å›žä»»åŠ¡åˆ—è¡¨
        List<String> currentIds = new ArrayList<>();
        // é€šè¿‡çˆ¶çº§ç½‘关的出口连线,结合 runTaskList æ¯”对,获取需要撤回的任务
        List<UserTask> currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, null);
        currentUserTaskList.forEach(item -> currentIds.add(item.getId()));
        // å¾ªçŽ¯èŽ·å–é‚£äº›éœ€è¦è¢«æ’¤å›žçš„èŠ‚ç‚¹çš„ID,用来设置驳回原因
        List<String> currentTaskIds = new ArrayList<>();
        currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
            if (currentId.equals(runTask.getTaskDefinitionKey())) {
                currentTaskIds.add(runTask.getId());
            }
        }));
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        // è®¾ç½®å›žé€€æ„è§
        for (String currentTaskId : currentTaskIds) {
            taskService.addComment(currentTaskId, task.getProcessInstanceId(), FlowComment.REBACK.getType(), bo.getComment());
        }
        try {
            // 1 å¯¹ 1 æˆ– å¤š å¯¹ 1 æƒ…况,currentIds å½“前要跳转的节点列表(1或多),targetKey è·³è½¬åˆ°çš„节点(1)
            runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(task.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(currentIds, bo.getTargetKey()).changeState();
        } catch (FlowableObjectNotFoundException e) {
            throw new RuntimeException("未找到流程实例,流程可能已发生变化");
        } catch (FlowableException e) {
            throw new RuntimeException("无法取消或开始活动");
        }
        // è®¾ç½®ä»»åŠ¡èŠ‚ç‚¹åç§°
        bo.setTaskName(task.getName());
        // å¤„理抄送用户
        if (!copyService.makeCopy(bo)) {
            throw new RuntimeException("抄送任务失败");
        }
    }
    /**
     * èŽ·å–æ‰€æœ‰å¯å›žé€€çš„èŠ‚ç‚¹
     *
     * @param bo
     * @return
     */
    @Override
    public List<FlowElement> findReturnTaskList(WfTaskBo bo) {
        // å½“前任务 task
        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
        // èŽ·å–æµç¨‹å®šä¹‰ä¿¡æ¯
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        // èŽ·å–æµç¨‹æ¨¡åž‹ä¿¡æ¯
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        // æŸ¥è¯¢åŽ†å²èŠ‚ç‚¹å®žä¾‹
        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(task.getProcessInstanceId())
            .activityType(BpmnXMLConstants.ELEMENT_TASK_USER)
            .finished()
            .orderByHistoricActivityInstanceEndTime().asc()
            .list();
        List<String> activityIdList = activityInstanceList.stream()
            .map(HistoricActivityInstance::getActivityId)
            .filter(activityId -> !StringUtils.equals(activityId, task.getTaskDefinitionKey()))
            .distinct()
            .collect(Collectors.toList());
        // èŽ·å–å½“å‰ä»»åŠ¡èŠ‚ç‚¹å…ƒç´ 
        FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        List<FlowElement> elementList = new ArrayList<>();
        for (String activityId : activityIdList) {
            FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId);
            boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
            if (isSequential) {
                elementList.add(target);
            }
        }
        return elementList;
    }
    /**
     * åˆ é™¤ä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    @Override
    public void deleteTask(WfTaskBo bo) {
        // todo å¾…确认删除任务是物理删除任务 è¿˜æ˜¯é€»è¾‘删除,让这个任务直接通过?
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        taskService.deleteTask(bo.getTaskId(), bo.getComment());
    }
    /**
     * è®¤é¢†/签收任务
     *
     * @param taskBo è¯·æ±‚实体参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void claim(WfTaskBo taskBo) {
        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
        if (Objects.isNull(task)) {
            throw new ServiceException("任务不存在");
        }
        taskService.claim(taskBo.getTaskId(), TaskUtils.getUserId());
    }
    /**
     * å–消认领/签收任务
     *
     * @param bo è¯·æ±‚实体参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unClaim(WfTaskBo bo) {
        taskService.unclaim(bo.getTaskId());
    }
    /**
     * å§”派任务
     *
     * @param bo è¯·æ±‚实体参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delegateTask(WfTaskBo bo) {
        // å½“前任务 task
        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException("获取任务失败!");
        }
        // èŽ·å–å½“å‰çš„ç”¨æˆ·
        LoginUser loginUser = LoginHelper.getLoginUser();
        String currentNickname = null;
        if (ObjectUtil.isNotNull(loginUser)){
            currentNickname=loginUser.getNickName();
        }
        StringBuilder commentBuilder = new StringBuilder(currentNickname)
            .append("->");
        String nickName = null;
        SysUserVo user = sysUserService.selectUserById(Long.parseLong(bo.getUserId()));
        if(!ObjectUtil.isNull(user)) {
            nickName=user.getNickName();
        }
        if (StringUtils.isNotBlank(nickName)) {
            commentBuilder.append(nickName);
        } else {
            commentBuilder.append(bo.getUserId());
        }
        if (StringUtils.isNotBlank(bo.getComment())) {
            commentBuilder.append(": ").append(bo.getComment());
        }
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        // æ·»åŠ å®¡æ‰¹æ„è§
        taskService.addComment(bo.getTaskId(), task.getProcessInstanceId(), FlowComment.DELEGATE.getType(), commentBuilder.toString());
        // è®¾ç½®åŠžç†äººä¸ºå½“å‰ç™»å½•äºº
        taskService.setOwner(bo.getTaskId(), TaskUtils.getUserId());
        // æ‰§è¡Œå§”æ´¾
        taskService.delegateTask(bo.getTaskId(), bo.getUserId());
        // è®¾ç½®ä»»åŠ¡èŠ‚ç‚¹åç§°
        bo.setTaskName(task.getName());
        // å¤„理抄送用户
        if (!copyService.makeCopy(bo)) {
            throw new RuntimeException("抄送任务失败");
        }
    }
    /**
     * è½¬åŠžä»»åŠ¡
     *
     * @param bo è¯·æ±‚实体参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transferTask(WfTaskBo bo) {
        // å½“前任务 task
        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
        if (ObjectUtil.isEmpty(task)) {
            throw new ServiceException("获取任务失败!");
        }
        // èŽ·å–å½“å‰çš„ç”¨æˆ·
        LoginUser loginUser = LoginHelper.getLoginUser();
        String currentNickname = null;
        if (ObjectUtil.isNotNull(loginUser)){
            currentNickname=loginUser.getNickName();
        }
        StringBuilder commentBuilder = new StringBuilder(currentNickname)
                .append("->");
        String nickName = null;
        SysUserVo user = sysUserService.selectUserById(Long.parseLong(bo.getUserId()));
        if(!ObjectUtil.isNull(user)) {
            nickName=user.getNickName();
        }
        if (StringUtils.isNotBlank(nickName)) {
            commentBuilder.append(nickName);
        } else {
            commentBuilder.append(bo.getUserId());
        }
        if (StringUtils.isNotBlank(bo.getComment())) {
            commentBuilder.append(": ").append(bo.getComment());
        }
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        // æ·»åŠ å®¡æ‰¹æ„è§
        taskService.addComment(bo.getTaskId(), task.getProcessInstanceId(), FlowComment.TRANSFER.getType(), commentBuilder.toString());
        // è®¾ç½®æ‹¥æœ‰è€…为当前登录人
        taskService.setOwner(bo.getTaskId(), TaskUtils.getUserId());
        // è½¬åŠžä»»åŠ¡
        taskService.setAssignee(bo.getTaskId(), bo.getUserId());
        // è®¾ç½®ä»»åŠ¡èŠ‚ç‚¹åç§°
        bo.setTaskName(task.getName());
        // å¤„理抄送用户
        if (!copyService.makeCopy(bo)) {
            throw new RuntimeException("抄送任务失败");
        }
    }
    /**
     * å–消申请
     *
     * @param bo
     * @return
     */
    @Override
    public void stopProcess(WfTaskBo bo) {
        List<Task> taskList = taskService.createTaskQuery().processInstanceId(bo.getProcInsId()).list();
        if (CollectionUtils.isEmpty(taskList)) {
            throw new RuntimeException("流程未启动或已执行完成,取消申请失败");
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
            .processInstanceId(bo.getProcInsId()).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        if (Objects.nonNull(bpmnModel)) {
            Process process = bpmnModel.getMainProcess();
            List<EndEvent> endNodes = process.findFlowElementsOfType(EndEvent.class, false);
            if (CollectionUtils.isNotEmpty(endNodes)) {
                Authentication.setAuthenticatedUserId(TaskUtils.getUserId());
                runtimeService.setVariable(processInstance.getId(), ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.CANCELED.getStatus());
                for (Task task : taskList) {
                    taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.STOP.getType(), "取消流程");
                }
                // èŽ·å–å½“å‰æµç¨‹æœ€åŽä¸€ä¸ªèŠ‚ç‚¹
                String endId = endNodes.get(0).getId();
                List<Execution> executions = runtimeService.createExecutionQuery()
                    .parentId(processInstance.getProcessInstanceId()).list();
                List<String> executionIds = new ArrayList<>();
                executions.forEach(execution -> executionIds.add(execution.getId()));
                // å˜æ›´æµç¨‹ä¸ºå·²ç»“束状态
                runtimeService.createChangeActivityStateBuilder()
                    .moveExecutionsToSingleActivityId(executionIds, endId).changeState();
            }
        }
    }
    /**
     * æ’¤å›žæµç¨‹
     *
     * @param taskBo è¯·æ±‚实体参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revokeProcess(WfTaskBo taskBo) {
        String procInsId = taskBo.getProcInsId();
        String taskId = taskBo.getTaskId();
        // æ ¡éªŒæµç¨‹æ˜¯å¦ç»“束
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
            .processInstanceId(procInsId)
            .active()
            .singleResult();
        if(ObjectUtil.isNull(processInstance)) {
            throw new RuntimeException("流程已结束或已挂起,无法执行撤回操作");
        }
        // èŽ·å–å¾…æ’¤å›žä»»åŠ¡å®žä¾‹
        HistoricTaskInstance currTaskIns = historyService.createHistoricTaskInstanceQuery()
            .taskId(taskId)
            .taskAssignee(TaskUtils.getUserId())
            .singleResult();
        if (ObjectUtil.isNull(currTaskIns)) {
            throw new RuntimeException("当前任务不存在,无法执行撤回操作");
        }
        // èŽ·å– bpmn æ¨¡åž‹
        BpmnModel bpmnModel = repositoryService.getBpmnModel(currTaskIns.getProcessDefinitionId());
        UserTask currUserTask = ModelUtils.getUserTaskByKey(bpmnModel, currTaskIns.getTaskDefinitionKey());
        // æŸ¥æ‰¾ä¸‹ä¸€çº§ç”¨æˆ·ä»»åŠ¡åˆ—è¡¨
        List<UserTask> nextUserTaskList = ModelUtils.findNextUserTasks(currUserTask);
        List<String> nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).collect(Collectors.toList());
        // èŽ·å–å½“å‰èŠ‚ç‚¹ä¹‹åŽå·²å®Œæˆçš„æµç¨‹åŽ†å²èŠ‚ç‚¹
        List<HistoricTaskInstance> finishedTaskInsList = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(procInsId)
            .taskCreatedAfter(currTaskIns.getEndTime())
            .finished()
            .list();
        for (HistoricTaskInstance finishedTaskInstance : finishedTaskInsList) {
            // æ£€æŸ¥å·²å®Œæˆæµç¨‹åŽ†å²èŠ‚ç‚¹æ˜¯å¦å­˜åœ¨ä¸‹ä¸€çº§ä¸­
            if (CollUtil.contains(nextUserTaskKeys, finishedTaskInstance.getTaskDefinitionKey())) {
                throw new RuntimeException("下一流程已处理,无法执行撤回操作");
            }
        }
        // èŽ·å–æ‰€æœ‰æ¿€æ´»çš„ä»»åŠ¡èŠ‚ç‚¹ï¼Œæ‰¾åˆ°éœ€è¦æ’¤å›žçš„ä»»åŠ¡
        List<Task> activateTaskList = taskService.createTaskQuery().processInstanceId(procInsId).list();
        List<String> revokeExecutionIds = new ArrayList<>();
        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
        for (Task task : activateTaskList) {
            // æ£€æŸ¥æ¿€æ´»çš„任务节点是否存在下一级中,如果存在,则加入到需要撤回的节点
            if (CollUtil.contains(nextUserTaskKeys, task.getTaskDefinitionKey())) {
                // æ·»åŠ æ’¤å›žå®¡æ‰¹ä¿¡æ¯
                taskService.setAssignee(task.getId(), TaskUtils.getUserId());
                // èŽ·å–å½“å‰çš„ç”¨æˆ·
                LoginUser loginUser = LoginHelper.getLoginUser();
                String currentNickname = null;
                if (ObjectUtil.isNotNull(loginUser)){
                    currentNickname=loginUser.getNickName();
                }
                taskService.addComment(task.getId(), task.getProcessInstanceId(), FlowComment.REVOKE.getType(), currentNickname + "撤回流程审批");
                revokeExecutionIds.add(task.getExecutionId());
            }
        }
        try {
            runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(procInsId)
                .moveExecutionsToSingleActivityId(revokeExecutionIds, currTaskIns.getTaskDefinitionKey()).changeState();
        } catch (FlowableObjectNotFoundException e) {
            throw new RuntimeException("未找到流程实例,流程可能已发生变化");
        } catch (FlowableException e) {
            throw new RuntimeException("执行撤回操作失败");
        }
    }
    /**
     * èŽ·å–æµç¨‹è¿‡ç¨‹å›¾
     *
     * @param processId
     * @return
     */
    @Override
    public InputStream diagram(String processId) {
        String processDefinitionId;
        // èŽ·å–å½“å‰çš„æµç¨‹å®žä¾‹
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
        // å¦‚果流程已经结束,则得到结束节点
        if (Objects.isNull(processInstance)) {
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {// å¦‚果流程没有结束,则取当前活动节点
            // æ ¹æ®æµç¨‹å®žä¾‹ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }
        // èŽ·å¾—æ´»åŠ¨çš„èŠ‚ç‚¹
        List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();
        List<String> highLightedFlows = new ArrayList<>();
        List<String> highLightedNodes = new ArrayList<>();
        //高亮线
        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
            if ("sequenceFlow".equals(tempActivity.getActivityType())) {
                //高亮线
                highLightedFlows.add(tempActivity.getActivityId());
            } else {
                //高亮节点
                highLightedNodes.add(tempActivity.getActivityId());
            }
        }
        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
        //获取自定义图片生成器
        ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator();
        return diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodes, highLightedFlows, configuration.getActivityFontName(),
            configuration.getLabelFontName(), configuration.getAnnotationFontName(), configuration.getClassLoader(), 1.0, true);
    }
    /**
     * èŽ·å–æµç¨‹å˜é‡
     *
     * @param taskId ä»»åŠ¡ID
     * @return æµç¨‹å˜é‡
     */
    @Override
    public Map<String, Object> getProcessVariables(String taskId) {
        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
            .includeProcessVariables()
            .finished()
            .taskId(taskId)
            .singleResult();
        if (Objects.nonNull(historicTaskInstance)) {
            return historicTaskInstance.getProcessVariables();
        }
        return taskService.getVariables(taskId);
    }
    /**
     * å¯åŠ¨ç¬¬ä¸€ä¸ªä»»åŠ¡
     * @param processInstance æµç¨‹å®žä¾‹
     * @param variables æµç¨‹å‚æ•°
     */
    @Override
    public void startFirstTask(ProcessInstance processInstance, Map<String, Object> variables) {
        // è‹¥ç¬¬ä¸€ä¸ªç”¨æˆ·ä»»åŠ¡ä¸ºå‘èµ·äººï¼Œåˆ™è‡ªåŠ¨å®Œæˆä»»åŠ¡
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).list();
        if (CollUtil.isNotEmpty(tasks)) {
            String userIdStr = (String) variables.get(TaskConstants.PROCESS_INITIATOR);
            identityService.setAuthenticatedUserId(TaskUtils.getUserId());
            // èŽ·å–å½“å‰çš„ç”¨æˆ·
            LoginUser loginUser = LoginHelper.getLoginUser();
            String currentNickname = null;
            if (ObjectUtil.isNotNull(loginUser)){
                currentNickname=loginUser.getNickName();
            }
            for (Task task : tasks) {
                if (StrUtil.equals(task.getAssignee(), userIdStr)) {
                    taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), currentNickname+ "发起流程申请");
                    taskService.complete(task.getId(), variables);
                }
            }
        }
    }
    /**
     * æŒ‡æ´¾ä¸‹ä¸€ä»»åŠ¡å®¡æ‰¹äºº
     * @param bpmnModel bpmn模型
     * @param processInsId æµç¨‹å®žä¾‹id
     * @param userIds ç”¨æˆ·ids
     */
    private void assignNextUsers(BpmnModel bpmnModel, String processInsId, String userIds) {
        // èŽ·å–æ‰€æœ‰èŠ‚ç‚¹ä¿¡æ¯
        List<Task> list = taskService.createTaskQuery()
            .processInstanceId(processInsId)
            .list();
        if (list.size() == 0) {
            return;
        }
        Queue<String> assignIds = CollUtil.newLinkedList(userIds.split(","));
        if (list.size() == assignIds.size()) {
            for (Task task : list) {
                taskService.setAssignee(task.getId(), assignIds.poll());
            }
            return;
        }
        // ä¼˜å…ˆå¤„理非多实例任务
        Iterator<Task> iterator = list.iterator();
        while (iterator.hasNext()) {
            Task task = iterator.next();
            if (!ModelUtils.isMultiInstance(bpmnModel, task.getTaskDefinitionKey())) {
                if (!assignIds.isEmpty()) {
                    taskService.setAssignee(task.getId(), assignIds.poll());
                }
                iterator.remove();
            }
        }
        // è‹¥å­˜åœ¨å¤šå®žä¾‹ä»»åŠ¡ï¼Œåˆ™è¿›è¡ŒåŠ¨æ€åŠ å‡ç­¾
        if (CollUtil.isNotEmpty(list)) {
            if (assignIds.isEmpty()) {
                // åŠ¨æ€å‡ç­¾
                for (Task task : list) {
                    runtimeService.deleteMultiInstanceExecution(task.getExecutionId(), true);
                }
            } else {
                // åŠ¨æ€åŠ ç­¾
                for (String assignId : assignIds) {
                    Map<String, Object> assignVariables = Collections.singletonMap(BpmnXMLConstants.ATTRIBUTE_TASK_USER_ASSIGNEE, assignId);
                    runtimeService.addMultiInstanceExecution(list.get(0).getTaskDefinitionKey(), list.get(0).getProcessInstanceId(), assignVariables);
                }
            }
        }
    }
}
ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
                                 â–ˆâ–ˆ         â–ˆâ–ˆâ–ˆâ–ˆ  â–ˆâ–ˆ                               â–ˆâ–ˆ       â–ˆâ–ˆ
                          â–ˆâ–ˆ   â–ˆâ–ˆâ–‘â–‘         â–‘██░  â–‘██                              â–‘██      â–‘██
 â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–ˆâ–ˆ   â–ˆâ–ˆ  â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ  â–‘░██ â–ˆâ–ˆ  â–ˆâ–ˆ       â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–‘██  â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ  â–ˆâ–ˆâ–ˆ     â–ˆâ–ˆ  â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ  â–‘██      â–‘██  â–ˆâ–ˆâ–ˆâ–ˆâ–ˆ
░░██░░█░██  â–‘██ â–ˆâ–ˆâ–‘░░░██  â–‘░███  â–‘██ â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–‘░░██░  â–‘██ â–ˆâ–ˆâ–‘░░░██░░██  â–ˆ â–‘██ â–‘░░░░░██ â–‘██████  â–‘██ â–ˆâ–ˆâ–‘░░██
 â–‘██ â–‘ â–‘██  â–‘██░██   â–‘██   â–‘██   â–‘██░░░░░   â–‘██   â–‘██░██   â–‘██ â–‘██ â–ˆâ–ˆâ–ˆâ–‘██  â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆ â–‘██░░░██ â–‘██░███████
 â–‘██   â–‘██  â–‘██░██   â–‘██   â–ˆâ–ˆ    â–‘██        â–‘██   â–‘██░██   â–‘██ â–‘████░████ â–ˆâ–ˆâ–‘░░░██ â–‘██  â–‘██ â–‘██░██░░░░
░███   â–‘░██████░░██████   â–ˆâ–ˆ     â–‘██        â–‘██   â–ˆâ–ˆâ–ˆâ–‘░██████  â–ˆâ–ˆâ–ˆâ–‘ â–‘░░██░░████████░██████  â–ˆâ–ˆâ–ˆâ–‘░██████
░░░     â–‘â–‘â–‘â–‘â–‘â–‘  â–‘â–‘â–‘â–‘â–‘â–‘   â–‘â–‘      â–‘â–‘         â–‘â–‘   â–‘â–‘â–‘  â–‘â–‘â–‘â–‘â–‘â–‘  â–‘â–‘â–‘    â–‘â–‘â–‘  â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘   â–‘â–‘â–‘  â–‘â–‘â–‘â–‘â–‘â–‘
ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
# Tomcat
server:
  port: 9308
# Spring
spring:
  main:
    lazy-initialization: true #默认false å…³é—­
  application:
    # åº”用名称
    name: ruoyi-flowable
  profiles:
    # çŽ¯å¢ƒé…ç½®
    active: dev
  cloud:
    nacos:
      discovery:
        # æœåŠ¡æ³¨å†Œåœ°å€
        server-addr: 127.0.0.1:8848
      config:
        # é…ç½®ä¸­å¿ƒåœ°å€
        server-addr: 127.0.0.1:8848
        # é…ç½®æ–‡ä»¶æ ¼å¼
        file-extension: yml
        # å…±äº«é…ç½®
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- æ—¥å¿—存放路径 -->
    <property name="log.path" value="logs/ruoyi-flowable" />
   <!-- æ—¥å¿—输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
    <!-- æŽ§åˆ¶å°è¾“出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!-- ç³»ç»Ÿæ—¥å¿—输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <!-- å¾ªçŽ¯æ”¿ç­–ï¼šåŸºäºŽæ—¶é—´åˆ›å»ºæ—¥å¿—æ–‡ä»¶ -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- æ—¥å¿—文件名格式 -->
            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- æ—¥å¿—最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- è¿‡æ»¤çš„级别 -->
            <level>INFO</level>
            <!-- åŒ¹é…æ—¶çš„æ“ä½œï¼šæŽ¥æ”¶ï¼ˆè®°å½•) -->
            <onMatch>ACCEPT</onMatch>
            <!-- ä¸åŒ¹é…æ—¶çš„æ“ä½œï¼šæ‹’绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <!-- å¾ªçŽ¯æ”¿ç­–ï¼šåŸºäºŽæ—¶é—´åˆ›å»ºæ—¥å¿—æ–‡ä»¶ -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- æ—¥å¿—文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- æ—¥å¿—最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- è¿‡æ»¤çš„级别 -->
            <level>ERROR</level>
            <!-- åŒ¹é…æ—¶çš„æ“ä½œï¼šæŽ¥æ”¶ï¼ˆè®°å½•) -->
            <onMatch>ACCEPT</onMatch>
            <!-- ä¸åŒ¹é…æ—¶çš„æ“ä½œï¼šæ‹’绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- Console Appender -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- MyBatis-Plus Logger Configuration -->
    <logger name="com.baomidou.mybatisplus" level="DEBUG"/>
    <!-- ç³»ç»Ÿæ¨¡å—日志级别控制  -->
    <logger name="com.ruoyi" level="info" />
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn" />
    <root level="info">
        <appender-ref ref="console" />
    </root>
    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>
ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
<?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.ruoyi.flowable.workflow.mapper.WfCategoryMapper">
    <resultMap type="org.ruoyi.flowable.workflow.domain.WfCategory" id="FlowCategoryResult">
        <result property="categoryId" column="category_id"/>
        <result property="categoryName" column="category_name"/>
        <result property="code" column="code"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="remark" column="remark"/>
        <result property="delFlag" column="del_flag"/>
    </resultMap>
</mapper>
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java
@@ -322,4 +322,15 @@
        return R.ok(trees);
    }
    /**
     * èŽ·å–éƒ¨é—¨ä¸‹çš„æ‰€æœ‰ç”¨æˆ·ä¿¡æ¯
     */
    @SaCheckPermission("system:user:list")
    @GetMapping("/list/dept/{deptId}")
    public R<List<SysUserVo>> listByDeptId(@PathVariable Long deptId) {
        List<SysUserVo> sysUserVos = userService.selectUserList(new SysUserBo());
        return R.ok(sysUserVos.stream()
                .filter(user -> deptId.equals(user.getDeptId()))
                .collect(Collectors.toList()));
    }
}
ruoyi-modules/sc-page-designer/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>sc-page-designer</artifactId>
    <description>
        school-ai页面设计器模块
    </description>
    <dependencies>
        <!-- é€šç”¨å·¥å…·-->
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-common-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-system-api</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package org.ruoyi.pageDesigner.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 14:41:24
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/page-designer")
public class PageDesignerController extends BaseController {
    private final PageDesignerService pageDesignerService;
    /**
     * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
     */
    @GetMapping("/list")
    public TableDataInfo<PageDesignerVo> list(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery) {
        return pageDesignerService.selectPagelistAll(pageDesignerDTO, pageQuery);
    }
    /**
     * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
     */
    @GetMapping("/{id}")
    public R<PageDesigner> getInfo(@PathVariable Long id) {
        return R.ok(pageDesignerService.getDetail(id));
    }
    /**
     * æ–°å¢žé¡µé¢è®¾è®¡
     */
    @PostMapping
    public R<Void> add(@Valid @RequestBody PageDesignerDTO dto) {
        pageDesignerService.add(dto);
        return R.ok("新增成功");
    }
    /**
     * ä¿®æ”¹é¡µé¢è®¾è®¡
     */
    @PutMapping
    public R<Void> update(@Valid @RequestBody PageDesignerDTO dto) {
        pageDesignerService.updatePage(dto);
        return R.ok("修改成功");
    }
    /**
     * åˆ é™¤é¡µé¢è®¾è®¡
     */
    @DeleteMapping
    public R<Void> remove(@RequestBody List<Long> ids) {
        pageDesignerService.deleteByIds(ids);
        return R.ok("删除成功");
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/controller/PageDesignerTemplateController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package org.ruoyi.pageDesigner.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import org.ruoyi.pageDesigner.service.PageDesignerTemplateService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 14:41:24
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/page-designer-template")
public class PageDesignerTemplateController extends BaseController {
    private final PageDesignerTemplateService service;
    /**
     * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
     */
    @GetMapping("/list")
    public TableDataInfo<PageDesignerTemplateVo> list(PageDesignerTemplateDTO dto, PageQuery pageQuery) {
        return service.selectPagelistAll(dto, pageQuery);
    }
    /**
     * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
     */
    @GetMapping("/{id}")
    public R<PageDesignerTemplate> getInfo(@PathVariable Long id) {
        return R.ok(service.getDetail(id));
    }
    /**
     * æ–°å¢žé¡µé¢è®¾è®¡
     */
    @PostMapping
    public R<Void> add(@Valid @RequestBody PageDesignerTemplateDTO dto) {
        service.add(dto);
        return R.ok("新增成功");
    }
    /**
     * ä¿®æ”¹é¡µé¢è®¾è®¡
     */
    @PutMapping
    public R<Void> update(@Valid @RequestBody PageDesignerTemplateDTO dto) {
        service.updatePage(dto);
        return R.ok("修改成功");
    }
    /**
     * åˆ é™¤é¡µé¢è®¾è®¡
     */
    @DeleteMapping
    public R<Void> remove(@RequestBody List<Long> ids) {
        service.deleteByIds(ids);
        return R.ok("删除成功");
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesigner.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package org.ruoyi.pageDesigner.domain;
import com.baomidou.mybatisplus.annotation.TableField;
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.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:24:05
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("PAGE_DESIGNER")
public class PageDesigner extends BaseEntity {
    @TableId(value = "ID")
    private Long id;
    @TableField("NAME")
    private String name;
    @TableField("MENU_ID")
    private String menuId; // å…³è”的菜单ID
    @TableField("MENU_PARENT_ID")
    private String menuParentId; // å…³è”的菜单父ID
    @TableField("STATUS")
    private String status;
    @TableField(value = "DEL_FLAG")
    @TableLogic
    private String delFlag;
    @TableField("REMARK")
    private String remark;
    @TableField("FORM_JSON")
    private String formJson;
    @TableField("SHOW_COLUMN")
    private String showColumn; // å¯ä»¥å°† selectedFields å­˜å‚¨ä¸º JSON å­—符串
    @TableField("ACTIONS_FUNC")
    private String actionsFunc;  // å¯ä»¥å°† enableActions å­˜å‚¨ä¸º JSON å­—符串
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = PageDesigner.class, reverseConvertGenerate = false)
public class PageDesignerDTO extends BaseEntity {
    private Long id; // ä¿®æ”¹æ—¶ä¼ å…¥
    private String name;       // é¡µé¢åç§°
    private String menuId;     // å…³è”菜单 ID
    private String status;     // å¯ç”¨çŠ¶æ€
    private String remark;     // å¤‡æ³¨
    private String formJson;   // è¡¨å•设计 JSON å­—符串
    private String showColumn;   // å­—段显示配置
    private String actionsFunc;  // å¯ç”¨çš„æ“ä½œé¡¹ï¼ˆå¢žåˆ æ”¹æŸ¥ï¼‰
    private String menuParentId;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplate.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.ruoyi.pageDesigner.domain;
import com.baomidou.mybatisplus.annotation.TableField;
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.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:24:05
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("PAGE_DESIGNER_TEMPLATE")
public class PageDesignerTemplate extends BaseEntity {
    @TableId(value = "ID")
    private Long id;
    @TableField(value = "PAGE_ID")
    private Long pageId;
    @TableField("FORM_DATA")
    private String formData;
    @TableField(value = "DEL_FLAG")
    @TableLogic
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.ruoyi.core.domain.BaseEntity;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = PageDesignerTemplate.class, reverseConvertGenerate = false)
public class PageDesignerTemplateDTO extends BaseEntity {
    private Long id;
    private Long pageId;
    private String formData;
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerTemplateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serializable;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@AutoMapper(target = PageDesignerTemplate.class)
public class PageDesignerTemplateVo implements Serializable {
    private Long id;
    private Long pageId;
    private String formData;
    private String delFlag;
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/domain/PageDesignerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package org.ruoyi.pageDesigner.domain;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.system.domain.vo.SysMenuVo;
import java.io.Serializable;
import java.util.Date;
/**
 * @author kanglujie
 * @date 2025-06-23 17:23:35
 */
@Data
@AutoMapper(target = PageDesigner.class)
public class PageDesignerVo implements Serializable {
    private Long id; // ä¿®æ”¹æ—¶ä¼ å…¥
    private String name;       // é¡µé¢åç§°
    private String status;     // å¯ç”¨çŠ¶æ€
    private String remark;     // å¤‡æ³¨
    private String menuParentId; // èœå•父ID
    private Date createTime; // åˆ›å»ºæ—¶é—´
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package org.ruoyi.pageDesigner.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
/**
 * @author kanglujie
 * @date 2025-06-23 17:34:48
 */
@Mapper
public interface PageDesignerMapper extends BaseMapper<PageDesigner> {
    Page<PageDesignerVo> selectPagelistAll(@Param("page") Page<PageDesigner> page, @Param(Constants.WRAPPER) Wrapper<PageDesigner> queryWrapper);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/mapper/PageDesignerTemplateMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package org.ruoyi.pageDesigner.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.ruoyi.pageDesigner.domain.PageDesignerTemplate;
import org.ruoyi.pageDesigner.domain.PageDesignerTemplateVo;
/**
 * @author kanglujie
 * @date 2025-06-23 17:34:48
 */
@Mapper
public interface PageDesignerTemplateMapper extends BaseMapper<PageDesignerTemplate> {
    Page<PageDesignerTemplateVo> selectPagelistAll(@Param("page") Page<PageDesignerTemplate> page, @Param(Constants.WRAPPER) Wrapper<PageDesignerTemplate> queryWrapper);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package org.ruoyi.pageDesigner.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:35:56
 */
public interface PageDesignerService extends IService<PageDesigner> {
    void add(PageDesignerDTO dto);
    void updatePage(PageDesignerDTO dto);
    List<PageDesigner> listAll(String keyword);
    PageDesigner getDetail(Long id);
    void deleteByIds(List<Long> ids);
    TableDataInfo<PageDesignerVo> selectPagelistAll(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/PageDesignerTemplateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package org.ruoyi.pageDesigner.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:35:56
 */
public interface PageDesignerTemplateService extends IService<PageDesignerTemplate> {
    void add(PageDesignerTemplateDTO dto);
    void updatePage(PageDesignerTemplateDTO dto);
    List<PageDesignerTemplate> listAll(String keyword);
    PageDesignerTemplate getDetail(Long id);
    void deleteByIds(List<Long> ids);
    TableDataInfo<PageDesignerTemplateVo> selectPagelistAll(PageDesignerTemplateDTO dto, PageQuery pageQuery);
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package org.ruoyi.pageDesigner.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.amazonaws.util.json.Jackson;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.UserConstants;
import org.ruoyi.common.core.utils.StreamUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.helper.DataBaseHelper;
import org.ruoyi.pageDesigner.domain.PageDesigner;
import org.ruoyi.pageDesigner.domain.PageDesignerDTO;
import org.ruoyi.pageDesigner.domain.PageDesignerVo;
import org.ruoyi.pageDesigner.mapper.PageDesignerMapper;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.ruoyi.system.domain.SysMenu;
import org.ruoyi.system.mapper.SysMenuMapper;
import org.ruoyi.system.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * @author kanglujie
 * @date 2025-06-23 17:36:29
 */
@Service
@RequiredArgsConstructor
public class PageDesignerServiceImpl extends ServiceImpl<PageDesignerMapper, PageDesigner>
        implements PageDesignerService {
    private final SysMenuMapper menuMapper;
    @Override
    public void add(PageDesignerDTO dto) {
        SysMenu menu = new SysMenu();
        menu.setMenuName(dto.getName());
        menu.setParentId(Long.valueOf(dto.getMenuParentId()));
        menu.setOrderNum(999);
        menu.setComponent("tool/template/index");
        menu.setIsFrame("1");
        menu.setIsCache("0");
        menu.setMenuType("C"); // C:目录, M:菜单, F:按钮
        menu.setVisible("0");
        menu.setStatus(dto.getStatus());
        menuMapper.insert(menu);
        PageDesigner entity = convertToEntity(dto);
        entity.setMenuId(menu.getMenuId().toString());
        this.save(entity);
        menu.setPath(String.valueOf(entity.getId()));
        menuMapper.updateById(menu);
    }
    @Override
    public void updatePage(PageDesignerDTO dto) {
        PageDesigner entity = convertToEntity(dto);
        this.updateById(entity);
        PageDesigner byId = this.getById(entity.getId());
        SysMenu menu = new SysMenu();
        menu.setMenuId(Long.valueOf(byId.getMenuId()));
        menu.setStatus(entity.getStatus());
        menu.setParentId(Long.valueOf(entity.getMenuParentId()));
        menuMapper.updateById(menu);
    }
    @Override
    public TableDataInfo<PageDesignerVo> selectPagelistAll(PageDesignerDTO pageDesignerDTO, PageQuery pageQuery) {
        Page<PageDesignerVo> page = baseMapper.selectPagelistAll(pageQuery.build(), this.buildQueryWrapper(pageDesignerDTO));
        return TableDataInfo.build(page);
    }
    private Wrapper<PageDesigner> buildQueryWrapper(PageDesignerDTO pageDesignerDTO) {
        Map<String, Object> params = pageDesignerDTO.getParams();
        QueryWrapper<PageDesigner> wrapper = Wrappers.query();
        wrapper.eq("del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(pageDesignerDTO.getId()), "id", pageDesignerDTO.getId())
                .like(StringUtils.isNotBlank(pageDesignerDTO.getName()), "name", pageDesignerDTO.getName())
                .eq(StringUtils.isNotBlank(pageDesignerDTO.getStatus()), "status", pageDesignerDTO.getStatus())
                .between(params.get("beginTime") != null && params.get("endTime") != null,
                        "create_time", params.get("beginTime"), params.get("endTime"))
        ;
        return wrapper;
    }
    @Override
    public List<PageDesigner> listAll(String keyword) {
        LambdaQueryWrapper<PageDesigner> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(keyword != null, PageDesigner::getName, keyword);
        return this.list(wrapper);
    }
    @Override
    public PageDesigner getDetail(Long id) {
        return this.getById(id);
    }
    @Override
    public void deleteByIds(List<Long> ids) {
        List<PageDesigner> designers = this.listByIds(ids);
        for (PageDesigner designer : designers) {
            Long id = designer.getId();
            this.removeById(id);
            Long menuId = Long.valueOf(designer.getMenuId());
            if (menuId != null) {
                menuMapper.deleteById(menuId);
            }
        }
    }
    private PageDesigner convertToEntity(PageDesignerDTO dto) {
        PageDesigner entity = new PageDesigner();
        entity.setId(dto.getId());
        entity.setName(dto.getName());
        entity.setMenuId(dto.getMenuId());
        entity.setStatus(dto.getStatus());
        entity.setRemark(dto.getRemark());
        entity.setFormJson(dto.getFormJson());
        entity.setShowColumn(dto.getShowColumn());
        entity.setActionsFunc(dto.getActionsFunc());
        entity.setMenuParentId(dto.getMenuParentId());
        return entity;
    }
}
ruoyi-modules/sc-page-designer/src/main/java/org/ruoyi/pageDesigner/service/impl/PageDesignerTemplateServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package org.ruoyi.pageDesigner.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.UserConstants;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.pageDesigner.domain.*;
import org.ruoyi.pageDesigner.mapper.PageDesignerMapper;
import org.ruoyi.pageDesigner.mapper.PageDesignerTemplateMapper;
import org.ruoyi.pageDesigner.service.PageDesignerService;
import org.ruoyi.pageDesigner.service.PageDesignerTemplateService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author kanglujie
 * @date 2025-06-23 17:36:29
 */
@Service
@RequiredArgsConstructor
public class PageDesignerTemplateServiceImpl extends ServiceImpl<PageDesignerTemplateMapper, PageDesignerTemplate>
        implements PageDesignerTemplateService {
    private final ObjectMapper objectMapper;
    @Override
    public void add(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = convertToEntity(dto);
        this.save(entity);
    }
    @Override
    public void updatePage(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = convertToEntity(dto);
        this.updateById(entity);
    }
    @Override
    public TableDataInfo<PageDesignerTemplateVo> selectPagelistAll(PageDesignerTemplateDTO dto, PageQuery pageQuery) {
        Page<PageDesignerTemplateVo> page = baseMapper.selectPagelistAll(pageQuery.build(), this.buildQueryWrapper(dto));
        return TableDataInfo.build(page);
    }
    private Wrapper<PageDesignerTemplate> buildQueryWrapper(PageDesignerTemplateDTO dto) {
        //Map<String, Object> params = pageDesignerDTO.getParams();
        QueryWrapper<PageDesignerTemplate> wrapper = Wrappers.query();
        wrapper.eq("del_flag", UserConstants.USER_NORMAL)
                .eq(ObjectUtil.isNotNull(dto.getPageId()), "page_id", dto.getPageId())
        //        .like(StringUtils.isNotBlank(pageDesignerDTO.getName()), "name", pageDesignerDTO.getName())
        //        .eq(StringUtils.isNotBlank(pageDesignerDTO.getStatus()), "status", pageDesignerDTO.getStatus())
        //        .between(params.get("beginTime") != null && params.get("endTime") != null,
        //                "create_time", params.get("beginTime"), params.get("endTime"))
        ;
        return wrapper;
    }
    @Override
    public List<PageDesignerTemplate> listAll(String keyword) {
        LambdaQueryWrapper<PageDesignerTemplate> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(keyword != null, PageDesignerTemplate::getPageId, keyword);
        return this.list(wrapper);
    }
    @Override
    public PageDesignerTemplate getDetail(Long id) {
        return this.getById(id);
    }
    @Override
    public void deleteByIds(List<Long> ids) {
        this.removeByIds(ids);
    }
    private PageDesignerTemplate convertToEntity(PageDesignerTemplateDTO dto) {
        PageDesignerTemplate entity = new PageDesignerTemplate();
        entity.setId(dto.getId());
        entity.setFormData(dto.getFormData());
        entity.setDelFlag(dto.getDelFlag());
        entity.setPageId(dto.getPageId());
        return entity;
    }
}
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<?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.ruoyi.pageDesigner.mapper.PageDesignerMapper">
    <!-- å¤šç»“构嵌套自动映射需带上每个实体的主键id å¦åˆ™æ˜ å°„会失败 -->
    <resultMap type="org.ruoyi.pageDesigner.domain.PageDesignerVo" id="PageDesignerResult">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="status" column="status"/>
        <result property="createTime" column="create_time"/>
        <result property="menuParentId" column="menu_parent_id"/>
        <result property="remark" column="remark"/>
    </resultMap>
    <select id="selectPagelistAll" resultMap="PageDesignerResult">
        select
            id,
            name,
            status,
            create_time,
            menu_parent_id,
            remark
        from page_designer
        ${ew.getCustomSqlSegment}
    </select>
</mapper>
ruoyi-modules/sc-page-designer/src/main/resources/mapper/PageDesignerTemplateMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
<?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.ruoyi.pageDesigner.mapper.PageDesignerTemplateMapper">
    <!-- ç»“果映射 -->
    <resultMap type="org.ruoyi.pageDesigner.domain.PageDesignerTemplateVo" id="PageDesignerTemplateResult">
        <id property="id" column="id"/>
        <result property="pageId" column="page_id"/>
        <result property="formData" column="form_data"/>
        <result property="delFlag" column="del_flag"/>
    </resultMap>
    <select id="selectPagelistAll" resultMap="PageDesignerTemplateResult">
        SELECT
        id,
        page_id,
        form_data,
        del_flag
        FROM page_designer_template
        <where>
            ${ew.sqlSegment}
        </where>
    </select>
</mapper>
ruoyi-modules/sc-services/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>sc-services</artifactId>
    <description>
        school-ai业务模块
    </description>
    <dependencies>
    <!-- é€šç”¨å·¥å…·-->
    <dependency>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-common-core</artifactId>
    </dependency>
    </dependencies>
</project>
ruoyi-ui/apps/web-antd/package.json
@@ -46,14 +46,18 @@
    "@vben/utils": "workspace:*",
    "@vueuse/core": "catalog:",
    "ant-design-vue": "catalog:",
    "axios": "^1.10.0",
    "bpmn-js": "^18.6.2",
    "cropperjs": "^1.6.2",
    "crypto-js": "^4.2.0",
    "dayjs": "catalog:",
    "diagram-js": "^15.3.0",
    "echarts": "^5.5.1",
    "element-plus": "^2.10.2",
    "jsencrypt": "^3.3.2",
    "lodash-es": "^4.17.21",
    "pinia": "catalog:",
    "qs": "^6.13.1",
    "tinymce": "^7.3.0",
    "unplugin-vue-components": "^0.27.3",
    "vue": "catalog:",
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import { requestClient } from '#/api/request';
import type {
  PageDesigner
} from './model';
enum Api {
  pageAdd = '/page-designer',
  pageUpdate = '/page-designer',
  pageList = '/page-designer/list',
  pageInfo = '/page-designer',
  pageRemove = '/page-designer',
}
/**
 * æ–°å¢žé¡µé¢è®¾è®¡
 */
export function pageAdd(data: any) {
  return requestClient.post(Api.pageAdd, data);
}
/**
 * ä¿®æ”¹é¡µé¢è®¾è®¡
 */
export function pageUpdate(data: any) {
  return requestClient.put(Api.pageUpdate, data);
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
 */
export function pageList(params?: any) {
    return requestClient.get<{
      total: number;
      rows: PageDesigner[];
      code: number;
      msg: string;
    }>(Api.pageList, { params });
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
 */
export function pageInfo(id: string | number) {
  return requestClient.get<PageDesigner>(`${Api.pageInfo}/${id}`);
}
/**
 * åˆ é™¤é¡µé¢è®¾è®¡
 */
export function pageRemove(ids: string[] | number[]) {
  return requestClient.delete(Api.pageRemove, { data: ids });
}
ruoyi-ui/apps/web-antd/src/api/tool/page-designer/model.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
export interface PageDesigner {
  id?: string | number;          // ä¸»é”®ï¼Œç¼–辑时用
  name: string;                  // é¡µé¢åç§°
  menuId: string | number | null; // ä¸Šçº§ç›®å½•(菜单ID)
  parentId: string | number | null; // çˆ¶çº§ID
  status: string;                // çŠ¶æ€
  remark?: string | null;        // å¤‡æ³¨
  formJson?: string | null;      // è¡¨å•设计JSON
  showColumn?: string | null;    // æ˜¾ç¤ºå­—段(JSON字符串)
  actionsFunc?: string | null;   // å¯ç”¨åŠŸèƒ½(JSON字符串)
  createTime?: string;           // åˆ›å»ºæ—¶é—´
  updateTime?: string;           // æ›´æ–°æ—¶é—´
  menu?: {                       // å…³è”的菜单信息
    menuId: number;
    menuName: string;
  } | null;
  parent?: {                     // å…³è”的父级信息
    id: string | number;
    name: string;
  } | null;
}
ruoyi-ui/apps/web-antd/src/api/tool/template/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import { requestClient } from '#/api/request';
import type { Template } from './model';
enum Api {
  templateAdd = '/page-designer-template',
  templateUpdate = '/page-designer-template',
  templateList = '/page-designer-template/list',
  templateInfo = '/page-designer-template',
  templateRemove = '/page-designer-template',
}
/**
 * æ–°å¢žé¡µé¢è®¾è®¡
 */
export function templateAdd(data: any) {
  return requestClient.post(Api.templateAdd, data);
}
/**
 * ä¿®æ”¹é¡µé¢è®¾è®¡
 */
export function templateUpdate(data: any) {
  return requestClient.put(Api.templateUpdate, data);
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡åˆ—è¡¨
 */
export function templateList(params?: any) {
  return requestClient.get(Api.templateList, { params });
}
/**
 * èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…
 */
export function templateInfo(id: string | number) {
  return requestClient.get(`${Api.templateInfo}/${id}`);
}
/**
 * åˆ é™¤é¡µé¢è®¾è®¡
 */
export function templateRemove(ids: string[] | number[]) {
  return requestClient.delete(Api.templateRemove, { data: ids });
}
ruoyi-ui/apps/web-antd/src/api/tool/template/model.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
export interface Template {
  id?: string | number;          // ä¸»é”®
  formData: string;                  // æ¨¡ç‰ˆåç§°
  pageDesignId: string | number; // å…³è”的页面设计ID
  createTime?: string;           // åˆ›å»ºæ—¶é—´
  updateTime?: string;           // æ›´æ–°æ—¶é—´
  pageDesign?: {                 // å…³è”的页面设计信息
    id: string | number;
    name: string;
    formJson?: string;
    showColumn?: string;
    actionsFunc?: string;
  } | null;
}
ruoyi-ui/apps/web-antd/src/bootstrap.ts
@@ -19,7 +19,10 @@
import { router } from './router';
import formCreate from '@form-create/element-ui';
import FcDesigner from '@form-create/designer';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
async function bootstrap(namespace: string) {
  // åˆå§‹åŒ–组件适配器
  await initComponentAdapter();
@@ -62,6 +65,7 @@
  app.use(ElementPlus);
  app.use(formCreate);
  app.use(FcDesigner);
  app.use(Antd);
  // åŠ¨æ€æ›´æ–°æ ‡é¢˜
  watchEffect(() => {
    if (preferences.app.dynamicTitle) {
ruoyi-ui/apps/web-antd/src/services/flowableService.js
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import type { VxeGridProps } from 'src/adapter/vxe-table';
import type { FormSchemaGetter } from "src/adapter/form";
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'roleName',
    label: '名称',
  },
  {
    component: 'Select',
    fieldName: 'roleSort',
    label: '创建人',
  },
  {
    component: 'RangePicker',
    fieldName: 'createTime',
    label: '创建时间',
  },
];
export const columns: VxeGridProps['columns'] = [
  { type: 'checkbox', width: 60 },
  {
    title: '名称',
    field: 'roleName',
  },
  {
    title: '创建人',
    field: 'roleSort',
  },
  {
    title: '创建时间',
    field: 'createTime',
  },
  {
    field: 'action',
    fixed: 'right',
    slots: { default: 'action' },
    title: '操作',
    width: 180,
  },
];
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/index.vue
@@ -1,10 +1,92 @@
<template>
<div>工作考核指标</div>
  <Page :auto-content-height="true">
    <BasicTable table-title="工作考核指标列表">
      <template #toolbar-tools>
        <Space>
          <a-button
            v-access:code="['system:role:export']"
            @click="handleDownloadExcel"
            style="margin-right: 10px"
          >
            {{ $t('pages.common.export') }}
          </a-button>
          <a-button
            type="primary"
            v-access:code="['system:role:add']"
            @click="handleAdd"
          >
            {{ $t('pages.common.add') }}
          </a-button>
        </Space>
      </template>
    </BasicTable>
  </Page>
</template>
<script>
export default {
  name: "index"
<script setup lang="ts">
import type { VxeGridProps } from "#/adapter/vxe-table";
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import type { VbenFormProps } from '@vben/common-ui';
import type { roleList} from "#/api/system/role";
import { columns, querySchema } from './data';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
  // æ—¥æœŸé€‰æ‹©æ ¼å¼åŒ–
  fieldMappingTime: [
    [
      'createTime',
      ['params[beginTime]', 'params[endTime]'],
      ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
    ],
  ],
};
const gridOptions: VxeGridProps = {
  checkboxConfig: {
    // é«˜äº®
    highlight: true,
    // ç¿»é¡µæ—¶ä¿ç•™é€‰ä¸­çŠ¶æ€
    reserve: true,
    // ç‚¹å‡»è¡Œé€‰ä¸­
    // trigger: 'row',
    checkMethod: ({ row }) => row.roleId !== 1,
  },
  columns,
  height: 'auto',
  keepSource: true,
  pagerConfig: {},
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        return await roleList({
          pageNum: page.currentPage,
          pageSize: page.pageSize,
          ...formValues,
        });
      },
    },
  },
  rowConfig: {
    keyField: 'roleId',
  },
  id: 'system-role-index',
};
const [BasicTable,tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
});
const handleAdd = () => {
  console.log('新增')
}
const handleDownloadExcel = () => {
  console.log('导出')
}
</script>
ruoyi-ui/apps/web-antd/src/views/assessment/serviceRating/serivceDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
<script>
import {modelInfo} from "#/api/system/model/index.js";
import {pick} from "lodash-es";
import {useVbenModal} from "@vben/common-ui";
import {ref, computed} from "vue";
import { $t } from '@vben/locales';
const defaultValues = {
  id: undefined,
  name: undefined,
}
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('编辑') : $t('新增');
});
const { validate, validateInfos, resetFields } = Form.useForm(
  formData,
  formRules,
);
const [BasicModal, modalApi] = useVbenModal({
  class: 'w-[550px]',
  fullscreenButton: false,
  closeOnClickModal: false,
  onClosed: handleCancel,
  onConfirm: handleConfirm,
  onOpenChange: async (isOpen) => {
    if (!isOpen) {
      return null;
    }
    modalApi.modalLoading(true);
    const { id } = modalApi.getData() as { id?: number | string };
    isUpdate.value = !!id;
    if (isUpdate.value && id) {
      const record = await modelInfo(id);
      // åªèµ‹å€¼å­˜åœ¨çš„字段
      const filterRecord = pick(record, Object.keys(defaultValues));
      formData.value = filterRecord;
    }
    modalApi.modalLoading(false);
  },
});
</script>
<template>
  <BasicModal :title="title">
    <Form :label-col="{ span: 4 }">
      <FormItem label="名称" v-bind="validateInfos.name">
        <Input
          v-model:value="formData.name"
          :placeholder="$t('ui.formRules.required')"
        />
      </FormItem>
    </Form>
  </BasicModal>
</template>
<style scoped>
</style>
ruoyi-ui/apps/web-antd/src/views/system/process/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,472 @@
<template>
  <div class="process-definition-container">
    <a-card title="流程定义列表" :bordered="false">
      <div class="table-actions">
        <a-button type="primary" @click="showCreateModal">新增流程图</a-button>
      </div>
      <a-table
        :columns="columns"
        :data-source="definitions"
        :row-key="record => record.id"
        :pagination="pagination"
        :loading="loading"
        @change="handleTableChange"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'action'">
            <a-space>
              <a-button type="link" @click="showDiagram(record)">查看</a-button>
              <a-button type="link" @click="editDiagram(record)">修改</a-button>
              <a-button type="link" danger @click="deleteDefinition(record)">删除</a-button>
            </a-space>
          </template>
        </template>
      </a-table>
    </a-card>
    <!-- æµç¨‹å›¾æŸ¥çœ‹æ¨¡æ€æ¡† -->
    <a-modal
      v-model:visible="diagramVisible"
      title="流程图查看"
      width="80%"
      :footer="null"
      @cancel="handleDiagramCancel"
    >
      <div style="text-align: center">
        <img
          v-if="currentDiagramUrl"
          :src="currentDiagramUrl"
          alt="流程图"
          style="max-width: 100%"
        />
        <a-skeleton v-else active />
      </div>
    </a-modal>
    <!-- æµç¨‹å›¾ç¼–辑模态框 -->
    <a-modal
      v-model:visible="editorVisible"
      :title="editorTitle"
      width="90%"
      :maskClosable="false"
      :okText="'保存'"
      :cancelText="'取消'"
      :confirmLoading="editorSaving"
      @ok="handleEditorOk"
      @cancel="handleEditorCancel"
      :destroyOnClose="true"
      :afterClose="handleEditorAfterClose"
      :style="{ top: '20px' }"
      :bodyStyle="{
      padding: '0',
      height: 'calc(100vh - 100px)',
      overflow: 'hidden',
      display: 'flex',
      flexDirection: 'column'
    }"
    >
      <div class="editor-container">
        <div v-if="isCreateMode" class="create-form">
          <a-form layout="vertical">
            <a-form-item label="流程名称" required>
              <a-input v-model:value="newProcess.name" placeholder="请输入流程名称" />
            </a-form-item>
            <a-form-item label="流程Key" required>
              <a-input v-model:value="newProcess.key" placeholder="请输入流程Key" />
            </a-form-item>
            <a-form-item label="流程描述">
              <a-textarea v-model:value="newProcess.description" placeholder="请输入流程描述" />
            </a-form-item>
          </a-form>
        </div>
        <div class="bpmn-editor" ref="bpmnEditor"></div>
      </div>
    </a-modal>
  </div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { message, Modal } from 'ant-design-vue'
import BpmnModeler from 'bpmn-js/lib/Modeler'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
// è¡¨æ ¼åˆ—定义
const columns = [
  {
    title: 'ID',
    dataIndex: 'id',
    key: 'id',
    ellipsis: true
  },
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name'
  },
  {
    title: 'Key',
    dataIndex: 'key',
    key: 'key'
  },
  {
    title: '操作',
    key: 'action',
  }
]
// æ•°æ®çŠ¶æ€
const definitions = ref([])
const loading = ref(false)
const pagination = ref({
  current: 1,
  pageSize: 10,
  total: 0,
  showSizeChanger: true,
  pageSizeOptions: ['10', '20', '50']
})
// æµç¨‹å›¾æŸ¥çœ‹ç›¸å…³çŠ¶æ€
const diagramVisible = ref(false)
const currentDiagramUrl = ref('')
const currentProcessDefinition = ref(null)
// æµç¨‹å›¾ç¼–辑相关状态
const editorVisible = ref(false)
const editorSaving = ref(false)
const editorTitle = ref('流程图编辑')
const bpmnModeler = ref(null)
const bpmnEditor = ref(null)
const isCreateMode = ref(false)
const newProcess = ref({
  name: '',
  key: '',
  description: ''
})
// èŽ·å–æµç¨‹å®šä¹‰åˆ—è¡¨
const fetchProcessDefinitions = async (params = {}) => {
  loading.value = true
  try {
    // æ¨¡æ‹Ÿæ•°æ®
    const mockData = [
      { id: '1', name: '请假流程', key: 'leaveProcess'},
      { id: '2', name: '报销流程', key: 'expenseProcess'},
      { id: '3', name: '采购流程', key: 'purchaseProcess' }
    ]
    // æ¨¡æ‹Ÿåˆ†é¡µ
    const start = (params.page - 1) * params.size
    const end = start + params.size
    definitions.value = mockData.slice(start, end)
    pagination.value.total = mockData.length
  } catch (error) {
    message.error('加载流程定义失败:'+ error.message)
  } finally {
    loading.value = false
  }
}
// è¡¨æ ¼åˆ†é¡µ/排序变化处理
const handleTableChange = (pag, filters, sorter) => {
  const params = {
    page: pag.current,
    size: pag.pageSize
  }
  if (sorter.field) {
    params.sort = sorter.field
    params.order = sorter.order === 'ascend' ? 'asc' : 'desc'
  }
  fetchProcessDefinitions(params)
}
// æ˜¾ç¤ºæµç¨‹å›¾
const showDiagram = (record) => {
  currentProcessDefinition.value = record
  currentDiagramUrl.value = `https://via.placeholder.com/800x600?text=流程图+${record.id}`
  diagramVisible.value = true
}
// é”€æ¯BPMN编辑器
const destroyBpmnEditor = () => {
  if (bpmnModeler.value) {
    bpmnModeler.value.destroy()
    bpmnModeler.value = null
  }
}
// åˆå§‹åŒ–BPMN编辑器
const initBpmnEditor = async (xml) => {
  await nextTick()
  // å…ˆé”€æ¯æ—§çš„编辑器
  destroyBpmnEditor()
  try {
    // åˆ›å»ºæ–°çš„编辑器实例
    bpmnModeler.value = new BpmnModeler({
      container: bpmnEditor.value,
    })
    // åŠ è½½XML或默认流程图
    const diagram = xml || `
      <?xml version="1.0" encoding="UTF-8"?>
      <bpmn:definitions
        xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
        xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
        xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
        xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
        id="Definitions_1"
        targetNamespace="http://bpmn.io/schema/bpmn">
        <bpmn:process id="Process_1" isExecutable="false">
          <bpmn:startEvent id="StartEvent_1" />
        </bpmn:process>
        <bpmndi:BPMNDiagram id="BPMNDiagram_1">
          <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
            <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
              <dc:Bounds x="173" y="102" width="36" height="36" />
            </bpmndi:BPMNShape>
          </bpmndi:BPMNPlane>
        </bpmndi:BPMNDiagram>
      </bpmn:definitions>
    `
    await bpmnModeler.value.importXML(diagram)
  } catch (err) {
    console.error('Error rendering diagram', err)
    message.error('初始化流程图编辑器失败: ' + err.message)
  }
}
// æ˜¾ç¤ºåˆ›å»ºæ–°æµç¨‹æ¨¡æ€æ¡†
const showCreateModal = () => {
  isCreateMode.value = true
  editorTitle.value = '创建新流程图'
  editorVisible.value = true
  // é‡ç½®è¡¨å•
  newProcess.value = {
    name: '',
    key: '',
    description: ''
  }
  // åˆå§‹åŒ–编辑器
  initBpmnEditor()
}
// ç¼–辑流程图
const editDiagram = async (record) => {
  isCreateMode.value = false
  currentProcessDefinition.value = record
  editorTitle.value = `编辑流程 - ${record.name}`
  editorVisible.value = true
  // æ¨¡æ‹ŸåŠ è½½æµç¨‹å®šä¹‰XML
  const mockXml = `
    <?xml version="1.0" encoding="UTF-8"?>
    <bpmn:definitions
      xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
      xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
      xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
      xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
      id="Definitions_1"
      targetNamespace="http://bpmn.io/schema/bpmn">
      <bpmn:process id="${record.id}" name="${record.name}" isExecutable="true">
        <bpmn:startEvent id="StartEvent_1" name="开始" />
        <bpmn:userTask id="UserTask_1" name="提交申请" />
        <bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="UserTask_1" />
        <bpmn:endEvent id="EndEvent_1" name="结束" />
        <bpmn:sequenceFlow id="Flow_2" sourceRef="UserTask_1" targetRef="EndEvent_1" />
      </bpmn:process>
      <bpmndi:BPMNDiagram id="BPMNDiagram_1">
        <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${record.id}">
          <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
            <dc:Bounds x="173" y="102" width="36" height="36" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="UserTask_1_di" bpmnElement="UserTask_1">
            <dc:Bounds x="280" y="80" width="100" height="80" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
            <dc:Bounds x="450" y="102" width="36" height="36" />
          </bpmndi:BPMNShape>
          <bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
            <di:waypoint x="209" y="120" />
            <di:waypoint x="280" y="120" />
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
            <di:waypoint x="380" y="120" />
            <di:waypoint x="450" y="120" />
          </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
      </bpmndi:BPMNDiagram>
    </bpmn:definitions>
  `
  // åˆå§‹åŒ–编辑器并加载XML
  initBpmnEditor(mockXml)
}
// ä¿å­˜æµç¨‹å›¾ä¿®æ”¹
const handleEditorOk = async () => {
  editorSaving.value = true
  try {
    if (isCreateMode.value) {
      // éªŒè¯è¡¨å•
      if (!newProcess.value.name || !newProcess.value.key) {
        message.error('请填写流程名称和Key')
        return
      }
      // èŽ·å–XML
      const { xml } = await bpmnModeler.value.saveXML({ format: true })
      console.log('新流程XML:', xml)
      // æ¨¡æ‹Ÿåˆ›å»ºæ–°æµç¨‹
      await new Promise(resolve => setTimeout(resolve, 1000))
      // æ·»åŠ åˆ°åˆ—è¡¨
      const newId = Math.max(...definitions.value.map(d => parseInt(d.id))) + 1
      definitions.value.unshift({
        id: newId.toString(),
        name: newProcess.value.name,
        key: newProcess.value.key,
        version: 1
      })
      message.success('新流程创建成功')
    } else {
      // èŽ·å–ä¿®æ”¹åŽçš„XML
      const { xml } = await bpmnModeler.value.saveXML({ format: true })
      console.log('修改后的XML:', xml)
      // æ¨¡æ‹Ÿä¿å­˜
      await new Promise(resolve => setTimeout(resolve, 1000))
      message.success('流程图保存成功')
    }
    editorVisible.value = false
  } catch (error) {
    console.error('Error saving BPMN diagram', error)
    message.error('操作失败: ' + error.message)
  } finally {
    editorSaving.value = false
  }
}
// åˆ é™¤æµç¨‹å®šä¹‰
const deleteDefinition = (record) => {
  Modal.confirm({
    title: '确认删除流程?',
    content: `确定要删除流程 "${record.name}" å—?此操作不可恢复。`,
    okText: '确认',
    okType: 'danger',
    cancelText: '取消',
    onOk() {
      // æ¨¡æ‹Ÿåˆ é™¤
      definitions.value = definitions.value.filter(item => item.id !== record.id)
      message.success('流程删除成功')
    }
  })
}
// æ¨¡æ€æ¡†å…³é—­åŽæ¸…理
const handleEditorAfterClose = () => {
  destroyBpmnEditor()
}
const handleEditorCancel = () => {
  editorVisible.value = false
}
const handleDiagramCancel = () => {
  diagramVisible.value = false
}
// åˆå§‹åŒ–加载数据
onMounted(() => {
  const params = {
    page: 1,
    size: 10
  }
  fetchProcessDefinitions(params)
})
</script>
<style scoped>
.process-definition-container {
  padding: 20px;
  background: #fff;
}
.table-actions {
  margin-bottom: 16px;
  display: flex;
  justify-content: flex-end;
}
.editor-container {
  height: 600px;
  display: flex;
  flex-direction: column;
}
.bpmn-editor {
  flex: 1;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  margin-top: 16px;
  min-height: 500px; /* ç¡®ä¿ç¼–辑器有足够高度 */
}
.create-form {
  padding: 16px;
  background: #fafafa;
  border-radius: 2px;
  border: 1px solid #d9d9d9;
  margin-bottom: 16px;
}
/* éšè—BPMN水印 */
.bpmn-editor :deep(.bjs-powered-by) {
  display: none !important;
}
.editor-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
.bpmn-editor {
  flex: 1;
  min-height: 0; /* é‡è¦ï¼šå…è®¸flex容器收缩 */
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  margin-top: 16px;
}
.create-form {
  padding: 16px;
  background: #fafafa;
  border-radius: 2px;
  border: 1px solid #d9d9d9;
  margin-bottom: 16px;
}
/* éšè—BPMN水印 */
.bpmn-editor :deep(.bjs-powered-by) {
  display: none !important;
}
/* ç¡®ä¿BPMN工具栏可见 */
.bpmn-editor :deep(.djs-palette) {
  top: 20px;
  left: 20px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/data.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { h } from 'vue';
import { FolderIcon, VbenIcon } from '@vben/icons';
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '页面名称',
  },
  {
    component: 'Select',
    componentProps: {
      options: [
        { label: '正常', value: '0' },
        { label: '停用', value: '1' },
      ],
    },
    fieldName: 'status',
    label: '页面状态',
  },
];
export const columns: VxeGridProps['columns'] = [
  {
    title: '页面名称',
    field: 'name',
  },
  {
    title: '上级目录',
    field: 'menuParentName',
  },
  {
    title: '状态',
    field: 'status',
    slots: {
      default: ({ row }) => {
        return row.status === '0' ? '正常' : '停用';
      },
    },
  },
  {
    title: '创建时间',
    field: 'createTime',
  },
  {
    field: 'action',
    fixed: 'right',
    slots: { default: 'action' },
    title: '操作',
  },
];
export const drawerSchema = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '页面名称',
    rules: 'required',
  },
  {
    component: 'TreeSelect',
    fieldName: 'menuParentId',
    label: '上级目录',
    componentProps: {
      allowClear: true,
      showSearch: true,
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'status',
    label: '状态',
    componentProps: {
      options: [
        { label: '正常', value: '0' },
        { label: '停用', value: '1' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'CheckboxGroup',
    fieldName: 'actionsFunc',
    label: '启用功能',
    defaultValue: ['add', 'edit', 'delete', 'query'],
    componentProps: {
      options: [
        { label: '新增', value: 'add' },
        { label: '编辑', value: 'edit' },
        { label: '删除', value: 'delete' },
        { label: '查询', value: 'query' },
      ],
    },
    rules: 'required',
  },
  {
    component: 'Input',
    fieldName: 'remark',
    label: '备注',
    componentProps: {
      type: 'textarea',
      rows: 2,
    },
  },
];
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
<template>
  <Page v-if="isAdmin" :auto-content-height="true">
    <BasicTable table-title="页面设计器" >
      <template #toolbar-tools>
        <Space>
          <a-button type="primary" @click="handleAdd">新增</a-button>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <ghost-button @click="handleEdit(row)">编辑</ghost-button>
          <Popconfirm :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?" @confirm="handleDelete(row)">
            <ghost-button danger @click.stop="">删除</ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
    <PageDrawer ref="pageModalRef" @reload="tableApi.query()" :menu-array="menuArray" />
  </Page>
  <Fallback v-else description="您没有页面生成器的访问权限" status="403" />
</template>
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, ref, onMounted } from 'vue';
import { useAccess } from '@vben/access';
import { Fallback, Page, useVbenDrawer } from '@vben/common-ui';
import { eachTree, getVxePopupContainer } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { columns, querySchema } from './data';
import PageDrawer from './page-drawer.vue';
import FcDesigner from '@form-create/designer';
import { pageList, pageRemove } from '#/api/tool/page-designer';
import { menuList } from '../../../api/system/menu';
import { listToTree} from '@vben/utils';
// ç§»é™¤mock数据
// const pageList = async (params: any) => { ... };
// const pageRemove = async (ids: number[]) => {};
const menuArray = ref([]);
const processedMenuTree = ref([]);
onMounted(async () => {
  try {
    // èŽ·å–åŽŸå§‹èœå•æ•°æ®
    const rawMenuData = await menuList();
    menuArray.value = rawMenuData;
    // å¤„理菜单数据
    processMenuData();
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
});
// å¤„理菜单数据的函数
const processMenuData = () => {
  if (!menuArray.value || menuArray.value.length === 0) return;
  // 1. è¿‡æ»¤æŽ‰æŒ‰é’®ç±»åž‹(F)和菜单类型(C)
  const filteredList = menuArray.value.filter(item =>
    item.menuType !== 'F' && item.menuType !== 'C'
  );
  // 2. è½¬æ¢ä¸ºæ ‘形结构
  const treeData = listToTree(filteredList, {
    id: 'menuId',
    pid: 'parentId'
  });
  // 3. æ·»åŠ æ ¹èŠ‚ç‚¹
  processedMenuTree.value = [
    {
      menuId: 0,
      parentId: 0,
      menuName: '根目录',
      children: treeData
    }
  ];
};
const formOptions: VbenFormProps = {
  commonConfig: {
    labelWidth: 80,
    componentProps: {
      allowClear: true,
    },
  },
  schema: querySchema(),
  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const getFullMenuPath = (id: number) => {
  if (!processedMenuTree.value || processedMenuTree.value.length === 0) return '';
  // é€’归查找菜单路径
  const findPath = (tree, currentId, path = []): string[] | null => {
    for (const item of tree) {
      if (item.menuId === currentId) {
        return [...path, item.menuName];
      }
      if (item.children && item.children.length > 0) {
        const found = findPath(item.children, currentId, [...path, item.menuName]);
        if (found) return found;
      }
    }
    return null;
  };
  const path = findPath(processedMenuTree.value, id);
  return path ? path.join(' / ') : '根目录';
};
const gridOptions: VxeGridProps = {
  columns,
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: true,
  },
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        const resp = await pageList({
          pageNum: page.currentPage,
          pageSize: page.pageSize,
          ...formValues,
        });
        // å¤„理返回数据,添加menuParentName
        const processedRows = resp.rows.map(row => {
          return {
            ...row,
            menuParentName: getFullMenuPath(row.menuParentId) || '根目录'
          };
        });
        return {
          rows: processedRows,  // ä½¿ç”¨å¤„理后的数据
          total: resp.total,
        };
      },
    },
  },
  rowConfig: {
    keyField: 'id',
  },
  id: 'tool-page-designer-index',
  columnConfig: { resizable: true },
};
const [BasicTable, tableApi] = useVbenVxeGrid({
  formOptions,
  gridOptions,
});
const designer = ref();
const pageModalRef = ref();
function getFormJson() {
  // èŽ·å–è®¾è®¡ç»“æžœ
  const json = designer.value.getRule();
  // ä½ å¯ä»¥å°† json å­˜åˆ°åŽç«¯
}
function setFormJson(json) {
  // åŠ è½½å·²æœ‰è®¾è®¡
  designer.value.setRule(json);
}
function handleAdd() {
  pageModalRef.value.open({ update: false });
}
function handleEdit(record) {
  pageModalRef.value.open({ id: record.id, update: true });
}
async function handleDelete(row: any) {
  await pageRemove([row.id]);
  await tableApi.query();
}
function handleSubAdd(row) {
  pageModalRef.value.open({ id: row.id, update: false });
}
const { hasAccessByRoles } = useAccess();
const isAdmin = computed(() => {
  return hasAccessByRoles(['admin', 'superadmin']);
});
</script>
<style scoped>
.designer-page {
  background: #f5f6fa;
  padding: 16px;
  min-height: 100vh;
}
.designer-query-form {
  background: #fff;
  padding: 16px 16px 0 16px;
  border-radius: 6px;
  margin-bottom: 12px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
.designer-toolbar {
  background: #fff;
  padding: 12px 16px;
  border-radius: 6px;
  margin-bottom: 12px;
  display: flex;
  gap: 8px;
}
.designer-table {
  background: #fff;
  border-radius: 6px;
  padding: 0 0 16px 0;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/page-designer/page-drawer.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,337 @@
<script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted } from 'vue';
import { Modal, message, FormItem } from 'ant-design-vue';
import { $t } from '@vben/locales';
import { useVbenForm } from '#/adapter/form';
import { drawerSchema } from './data';
import FcDesigner from '@form-create/designer';
import { listToTree, addFullName, getPopupContainer } from '@vben/utils';
import { pageAdd, pageUpdate, pageInfo } from '#/api/tool/page-designer';
interface ModalProps {
  id?: number | string;
  update: boolean;
}
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 90,
  },
  schema: drawerSchema(),
  showDefaultActions: false,
  wrapperClass: 'grid-cols-5',
});
const designer = ref();
const selectedFields = ref([]); // å¤šé€‰æ¡†é€‰ä¸­çš„字段key
const fieldOptions = ref([]);   // è®¾è®¡åŒºæ‰€æœ‰å­—段
const modalVisible = ref(false);
const modalLoading = ref(false);
const currentEditId = ref<string | number>(''); // å½“前编辑的ID
const fullMenuTree = ref([]);
const props=defineProps({
  menuArray: {
    type: Array,
    required: true,
    default: () => []
  }
});
// æ‰“开弹窗
const open = async (params: ModalProps = { update: false }) => {
  try {
    modalVisible.value = true;
    modalLoading.value = true;
    isUpdate.value = params.update;
    currentEditId.value = params.id || ''; // ä¿å­˜å½“前编辑的ID
    await setupPageSelect();
    if (params.id) {
      await formApi.setFieldValue('menuParentId', params.id);
      if (params.update) {
        // èŽ·å–è¯¦æƒ…æ•°æ®
        const record = await pageInfo(params.id);
        // è®¾ç½®åŸºç¡€è¡¨å•数据
        const menuParentId = String(record.menuParentId || record.parentId || '');
        await formApi.setValues({
          name: record.name,
          menuParentId: menuParentId,  // ç”¨ menuParentId å­—段
          status: record.status,
          remark: record.remark,
          actionsFunc: record.actionsFunc ? JSON.parse(record.actionsFunc) : ['add', 'edit', 'delete', 'query']
        });
        // åŠ è½½è¡¨å•è®¾è®¡æ•°æ®
        if (record.formJson) {
          try {
            const formRule = JSON.parse(record.formJson);
            console.log('设计器规则:', formRule);
            designer.value.setRule(formRule);
            // æ›´æ–°å­—段选项
            await nextTick();
            updateFieldOptions();
            // æ¢å¤é€‰ä¸­çš„字段
            if (record.showColumn) {
              selectedFields.value = JSON.parse(record.showColumn);
            }
          } catch (e) {
            console.error('加载表单设计数据失败:', e);
            message.error('加载表单设计数据失败');
          }
        }
      }
    } else {
      // æ–°å¢žæ—¶é‡ç½®æ•°æ®
      designer.value?.setRule([]);
      selectedFields.value = [];
      await formApi.resetForm();
    }
  } catch (error) {
    console.error('打开弹窗失败:', error);
    message.error('加载数据失败');
  } finally {
    modalLoading.value = false;
  }
};
const close = () => {
  modalVisible.value = false;
};
defineExpose({ open, close });
async function setupPageSelect() {
  // èŽ·å–èœå•æ•°æ®
  if (!props.menuArray || props.menuArray.length === 0) {
    await nextTick(); // ç­‰å¾…可能的异步加载
    if (!props.menuArray || props.menuArray.length === 0) {
      console.warn('menuArray is empty');
      return;
    }
  }
  // è¿‡æ»¤æŽ‰æŒ‰é’®ç±»åž‹
  const filteredList = props.menuArray.filter(item => item.menuType !== 'F' && item.menuType !== 'C');
  // æ”¯æŒi18n
  filteredList.forEach(item => { item.menuName = $t(item.menuName); });
  // è½¬ä¸ºæ ‘结构
  const menuTree = listToTree(filteredList, { id: 'menuId', pid: 'parentId' });
  // é€’归映射 menuId -> menuParentId
  function mapMenuIdToParentId(list) {
    return list.map(item => {
      const newItem = { ...item, menuParentId: item.menuId };
      if (item.children) {
        newItem.children = mapMenuIdToParentId(item.children);
      }
      return newItem;
    });
  }
  fullMenuTree.value = [
    {
      menuId: 0,
      menuParentId: 0,
      menuName: $t('menu.root'),
      children: mapMenuIdToParentId(menuTree),
    },
  ];
  // ç”Ÿæˆå…¨è·¯å¾„名
  addFullName(fullMenuTree.value, 'menuName', ' / ');
  formApi.updateSchema([
    {
      componentProps: {
        fieldNames: {
          label: 'menuName',
          value: 'menuId', // ç”¨ menuId
          children: 'children'
        },
        getPopupContainer,
        listHeight: 300,
        showSearch: true,
        treeData: fullMenuTree.value,
        treeDefaultExpandAll: false,
        treeDefaultExpandedKeys: [0],
        treeLine: { showLeafIcon: false },
        treeNodeFilterProp: 'menuName',
        treeNodeLabelProp: 'fullName',
      },
      fieldName: 'menuParentId', // ç”¨ menuParentId
    },
  ]);
}
// åŒæ­¥æ‰€æœ‰å­—段到选中状态
const syncAllFields = () => {
  // èŽ·å–è¡¨å•ç»„ä»¶çš„è§„åˆ™æè¿°
  const formDesc = designer.value?.getFormDescription?.();
  if (!formDesc || !Array.isArray(formDesc)) {
    message.warning('暂无设计数据');
    return;
  }
  // æå–字段信息
  const allFields = formDesc
    .filter(item => item && item.field && item.title)
    .map(item => ({
      title: item.title,
      field: item.field
    }));
  if (allFields.length === 0) {
    message.warning('未找到可用字段');
    return;
  }
  // æ›´æ–°å­—段选项
  fieldOptions.value = allFields.map(item => ({
    label: item.title,
    value: item.field
  }));
  // é€‰ä¸­æ‰€æœ‰å­—段
  selectedFields.value = allFields.map(item => item.field);
  message.success(`已同步 ${allFields.length} ä¸ªå­—段`);
};
// å¤„理设计器变化
const handleDesignerChange = () => {
  nextTick(() => {
    updateFieldOptions();
  });
};
// å½“设计器内容变化时更新字段选项
const updateFieldOptions = () => {
  // èŽ·å–è¡¨å•ç»„ä»¶çš„è§„åˆ™æè¿°
  const formDesc = designer.value?.getFormDescription?.();
  if (!formDesc || !Array.isArray(formDesc)) return;
  const fields = formDesc
    .filter(item => item && item.field && item.title)
    .map(item => ({
      title: item.title,
      field: item.field
    }));
  fieldOptions.value = fields.map(item => ({
    label: item.title,
    value: item.field
  }));
};
// ç›‘听设计器内容变化
watch(() => modalVisible.value, (val) => {
  if (val) {
    nextTick(() => updateFieldOptions());
  }
});
async function handleOk() {
  try {
    modalLoading.value = true;
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    const data = await formApi.getValues();
    // å¦‚果是编辑模式,添加id字段
    if (isUpdate.value) {
      data.id = currentEditId.value;
    }
    // èŽ·å–è¡¨å•è®¾è®¡ JSON
    data.formJson = designer.value.getJson();
    // æ·»åŠ é€‰ä¸­çš„å­—æ®µ
    data.showColumn = JSON.stringify(selectedFields.value);
    // è½¬æ¢å¯ç”¨åŠŸèƒ½ä¸ºJSON字符串
    data.actionsFunc = JSON.stringify(data.actionsFunc);
    // åŒæ­¥ä¸€æ¬¡å­—段多选
    updateFieldOptions();
    await (isUpdate.value ? pageUpdate(data) : pageAdd(data));
    emit('reload');
    close();
    message.success('保存成功');
  } catch (error) {
    console.error(error);
  } finally {
    modalLoading.value = false;
  }
}
function handleCancel() {
  close();
}
</script>
<template>
  <a-modal
    v-model:open="modalVisible"
    :title="title"
    :width="'80vw'"
    :confirm-loading="modalLoading"
    @ok="handleOk"
    @cancel="handleCancel"
    :bodyStyle="{ padding: '24px', minHeight: '60vh' }"
    destroyOnClose
    wrapClassName="page-designer-modal"
  >
    <template #closeIcon>
      <span></span>
    </template>
    <BasicForm />
    <div style="margin-top: 16px;">
      <FcDesigner
        ref="designer"
        @update="handleDesignerChange"
        @change="handleDesignerChange"
        @add-rule="handleDesignerChange"
        @remove-rule="handleDesignerChange"
      />
      <div style="margin-top: 8px; display: flex; justify-content: flex-end;">
        <a-button type="primary" ghost @click="syncAllFields">
          åŒæ­¥è®¾è®¡å­—段到表格
        </a-button>
      </div>
    </div>
    <FormItem label="表格字段" style="margin-top: 24px;">
      <a-checkbox-group
        v-model:value="selectedFields"
        :options="fieldOptions"
        style="width:100%;display:flex;flex-wrap:wrap;gap:8px"
      />
    </FormItem>
    <template #empty>
      <div style="padding: 32px 0; color: #999; text-align: center;">
        æš‚无数据
      </div>
    </template>
  </a-modal>
</template>
<style scoped>
.page-designer-modal .ant-modal {
  max-width: 1200px;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/template/data.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '模版名称',
  },
  {
    component: 'Select',
    componentProps: {
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
    fieldName: 'status',
    label: '模版状态',
  },
];
export const columns = [
  {
    title: '姓名',
    dataIndex: 'name',
    auth: 'test', // æ ¹æ®æƒé™æŽ§åˆ¶æ˜¯å¦æ˜¾ç¤º: æ— æƒé™ï¼Œä¸æ˜¾ç¤º
  }
];
export const drawerSchema = () => [
  {
    component: 'Input',
    fieldName: 'name',
    label: '模版名称',
    rules: 'required',
  },
  {
    component: 'Select',
    fieldName: 'pageDesignId',
    label: '页面设计',
    componentProps: {
      allowClear: true,
      showSearch: true,
      filterOption: (input: string, option: any) => {
        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
      },
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'templateType',
    label: '模版类型',
    componentProps: {
      options: [
        { label: '列表页', value: 'list' },
        { label: '表单页', value: 'form' },
        { label: '详情页', value: 'detail' },
        { label: '仪表板', value: 'dashboard' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'Select',
    fieldName: 'status',
    label: '状态',
    componentProps: {
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
    rules: 'selectRequired',
  },
  {
    component: 'Input',
    fieldName: 'remark',
    label: '备注',
    componentProps: {
      type: 'textarea',
      rows: 2,
    },
  },
];
ruoyi-ui/apps/web-antd/src/views/tool/template/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,432 @@
<template>
  <Page v-if="isAdmin && pageId" :auto-content-height="true">
    <BasicTable
      :key="tableKey"
      :table-title="pageDesignDetail?.name || '模板列表'"
      :grid-options="gridOptions"
    >
      <template #toolbar-tools>
        <Space>
          <a-button v-if="showAction('add')" type="primary" @click="handleAdd">新增</a-button>
        </Space>
      </template>
      <template #action="{ row }">
        <Space>
          <ghost-button v-if="showAction('query')" @click="handleQuery(row)">详情</ghost-button>
          <ghost-button v-if="showAction('edit')" @click="handleEdit(row)">编辑</ghost-button>
          <Popconfirm v-if="showAction('delete')" :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?" @confirm="handleDelete(row)">
            <ghost-button danger @click.stop="">删除</ghost-button>
          </Popconfirm>
        </Space>
      </template>
    </BasicTable>
    <TemplateDrawer ref="templateModalRef" @reload="tableApi.query()" />
    <a-modal v-model:open="detailModalVisible" title="详情" :footer="null" width="600px">
      <a-descriptions bordered column="1">
        <a-descriptions-item v-for="item in formDataFields" :key="item.key" :label="formFieldTitleMap[item.key] || item.key">
          <span>{{ item.value }}</span>
        </a-descriptions-item>
        <a-descriptions-item label="创建时间">
          <span>{{ createTime }}</span>
        </a-descriptions-item>
      </a-descriptions>
    </a-modal>
  </Page>
  <Fallback v-else description="未指定 pageId,无法访问此页面" status="403" />
</template>
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, ref, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccess } from '@vben/access';
import { Fallback, Page } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Popconfirm, Space, Spin as ASpin } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { columns as baseColumns, querySchema } from './data';
import TemplateDrawer from './template-drawer.vue';
import { templateList, templateRemove, templateInfo } from '#/api/tool/template';
import { pageInfo } from '#/api/tool/page-designer';
const route = useRoute();
const router = useRouter();
const pageId = ref<string | number>('');
const pageDesignDetail = ref<any>(null); // é¡µé¢è®¾è®¡è¯¦æƒ…
const loading = ref(true); // åŠ è½½ä¸­
const detailModalVisible = ref(false);
const detailRecord = ref({});
const formDataFields = ref([]);
const createTime = ref('');
// åŠ¨æ€columns
const dynamicColumns = ref([
  { field: 'id', title: 'ID', width: 100 },
  { field: 'formData', title: '表单数据', minWidth: 160 },
  { field: 'action', title: '操作', width: 160, slots: { default: 'action' } }
]);
// ç”¨äºŽè¡¨æ ¼é‡æ–°æ¸²æŸ“çš„ key
const tableKey = ref(0);
// æ›´æ–°åŠ¨æ€åˆ—çš„å‡½æ•°
function updateDynamicColumns() {
  if (!pageDesignDetail.value || !pageDesignDetail.value.showColumn || !pageDesignDetail.value.formJson) {
    console.log('使用默认列');
    return;
  }
  try {
    const showFields = JSON.parse(pageDesignDetail.value.showColumn);
    const formFields = JSON.parse(pageDesignDetail.value.formJson);
    const cols = showFields.map(field => {
      const fieldDef = formFields.find(f => f.field === field);
      return {
        field: field,
        title: fieldDef ? fieldDef.title : field,
        minWidth: 120,
        align: 'center',
      };
    });
    cols.push({
      field: 'action',
      title: '操作',
      width: 160,
      slots: { default: 'action' },
    });
    dynamicColumns.value = cols;
    gridOptions.value = { ...gridOptions.value, columns: cols };
    tableKey.value++;
  } catch (error) {
    const fallbackCols = [
      { field: 'id', title: 'ID', width: 100 },
      { field: 'formData', title: '表单数据', minWidth: 160 },
      { field: 'action', title: '操作', width: 160, slots: { default: 'action' } }
    ];
    dynamicColumns.value = fallbackCols;
    gridOptions.value = { ...gridOptions.value, columns: fallbackCols };
    tableKey.value++;
  }
}
// åŠ¨æ€æŒ‰é’®
function showAction(action: string) {
  if (!pageDesignDetail.value || !pageDesignDetail.value.actionsFunc) {
    return true;
  }
  try {
    const actions = JSON.parse(pageDesignDetail.value.actionsFunc);
    if (!Array.isArray(actions)) {
      console.warn('actionsFunc ä¸æ˜¯æ•°ç»„格式:', pageDesignDetail.value.actionsFunc);
      return true;
    }
    return actions.includes(action);
  } catch (error) {
    console.error('解析 actionsFunc å¤±è´¥:', error);
    return true;
  }
}
// èŽ·å– pageId,只用 params
function getPageId() {
  const segments = window.location.pathname.split('/');
  return segments[segments.length - 1] || '';
}
onMounted(() => {
  const initialPageId = getPageId();
  console.log('获取到的 pageId:', initialPageId);
  if (initialPageId) {
    pageId.value = initialPageId;
    handlePageIdChange();
  } else {
    loading.value = false;
  }
});
// ç›‘听路由变化,自动更新 pageId
watch(
  () => [route.meta.pageId, route.params.pageId, route.query.pageId],
  (newValues, oldValues) => {
    // åªæœ‰å½“值真正变化时才处理
    if (JSON.stringify(newValues) !== JSON.stringify(oldValues)) {
      const newPageId = getPageId();
      console.log('路由变化后 pageId:', newPageId);
      // åªæœ‰å½“ pageId çœŸæ­£å˜åŒ–时才更新
      if (newPageId !== pageId.value) {
        pageId.value = newPageId;
        handlePageIdChange();
        tableApi.query();
      }
    }
  },
  { deep: true }
);
// pageId变化时自动获取页面设计详情
async function handlePageIdChange() {
  loading.value = true;
  console.log(`[handlePageIdChange] å¼€å§‹å¤„理 pageId: ${pageId.value}`);
  try {
    if (pageId.value) {
      const detail = await pageInfo(pageId.value);
      console.log('[handlePageIdChange] èŽ·å–åˆ°çš„é¡µé¢è®¾è®¡è¯¦æƒ… (detail):', JSON.parse(JSON.stringify(detail)));
      // å¤„理数据,避免循环引用
      const safeDetail = {
        id: detail.id,
        name: detail.name,
        menuParentId: detail.menuId,
        status: detail.status,
        remark: detail.remark,
        formJson: detail.formJson,
        showColumn: detail.showColumn,
        actionsFunc: detail.actionsFunc,
        createTime: detail.createTime,
        updateTime: detail.updateTime,
        createBy: detail.createBy,
        updateBy: detail.updateBy,
        createDept: detail.createDept
      };
      pageDesignDetail.value = safeDetail;
      console.log('[handlePageIdChange] è®¾ç½®çš„ pageDesignDetail.value:', JSON.parse(JSON.stringify(pageDesignDetail.value)));
      updateDynamicColumns();
    } else {
      pageDesignDetail.value = null;
      updateDynamicColumns();
    }
  } catch (error) {
    console.error('[handlePageIdChange] èŽ·å–é¡µé¢è®¾è®¡è¯¦æƒ…å¤±è´¥:', error);
    pageDesignDetail.value = null;
    updateDynamicColumns();
  } finally {
    loading.value = false;
  }
}
// const formOptions: VbenFormProps = {
//   commonConfig: {
//     labelWidth: 80,
//     componentProps: {
//       allowClear: true,
//     },
//   },
//   schema: querySchema(),
//   wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// };
const gridOptions = ref<VxeGridProps>({
  columns: dynamicColumns.value,
  height: 'auto',
  keepSource: true,
  pagerConfig: {
    enabled: true,
  },
  proxyConfig: {
    ajax: {
      query: async ({ page }, formValues = {}) => {
        try {
          console.log('查询参数:', { page, formValues });
          const queryParams = {
            pageNum: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
          };
          if (pageId.value) {
            queryParams.pageId = pageId.value;
          }
          const resp = await templateList(queryParams);
          // å¤„理每条 row çš„ formData
          const rows = (resp.rows || []).map(row => {
            let formData = {};
            try {
              if (row.formData) {
                formData = JSON.parse(row.formData);
                console.log('解析后的 formData:', formData);
                // åˆ é™¤åŽŸå§‹çš„ formData å­—段,因为我们已经展开它的内容
                const { formData: _, ...restRow } = row;
                // è¿”回展开后的数据
                return {
                  ...restRow,
                  ...formData
                };
              }
            } catch (e) {
              console.error('解析 formData å¤±è´¥:', e);
            }
            return row;
          });
          console.log('处理后的 rows:', rows);
          return {
            rows,
            total: resp.total || 0,
          };
        } catch (error) {
          console.error('查询模板列表失败:', error);
          return {
            rows: [],
            total: 0,
          };
        }
      },
    },
  },
  rowConfig: {
    keyField: 'id',
  },
  id: 'tool-template-index',
  columnConfig: { resizable: true },
});
const [BasicTable, tableApi] = useVbenVxeGrid({
  // formOptions,
  gridOptions: computed(() => ({
    ...gridOptions.value,
    columns: dynamicColumns.value
  })),
});
const templateModalRef = ref();
const generateModalRef = ref();
function handleAdd() {
  // å¦‚果有 pageId,传递给新增
  const params: any = { update: false };
  if (pageId.value) {
    params.pageId = pageId.value;
  }
  // åŠ¨æ€ä¼ é€’formJson,先JSON.parse,保证是纯对象
  if (pageDesignDetail.value && pageDesignDetail.value.formJson) {
    try {
      const formJson = JSON.parse(pageDesignDetail.value.formJson);
      params.formJson = formJson;
      console.log('传递动态表单字段:', formJson);
    } catch (error) {
      console.error('解析 formJson å¤±è´¥:', error);
      params.formJson = undefined;
    }
  }
  templateModalRef.value.open(params);
}
function handleEdit(record) {
  // ç¼–辑时也传递formJson,先JSON.parse,保证是纯对象
  const params: any = {
    id: record.id,
    update: true,
    pageId: pageId.value,  // ä¼ é€’页面设计ID
    record: record  // ä¼ é€’完整的记录数据
  };
  if (pageDesignDetail.value && pageDesignDetail.value.formJson) {
    try {
      const formJson = JSON.parse(pageDesignDetail.value.formJson);
      params.formJson = formJson;
      console.log('编辑时传递数据:', { record, formJson, pageId: pageId.value });
    } catch (error) {
      console.error('解析 formJson å¤±è´¥:', error);
      params.formJson = undefined;
    }
  }
  templateModalRef.value.open(params);
}
async function handleDelete(row: any) {
  try {
    await templateRemove([row.id]);
    await tableApi.query();
  } catch (error) {
    console.error('删除模板失败:', error);
  }
}
async function handleQuery(row) {
  try {
    const detail = await templateInfo(row.id);
    detailRecord.value = detail || {};
    createTime.value = detail.createTime || '';
    // è§£æž formData
    let fields = [];
    if (detail.formData) {
      try {
        const data = JSON.parse(detail.formData);
        fields = Object.entries(data).map(([key, value]) => ({ key, value }));
      } catch (e) {
        fields = [];
      }
    }
    formDataFields.value = fields;
    detailModalVisible.value = true;
  } catch (e) {
    detailRecord.value = {};
    formDataFields.value = [];
    createTime.value = '';
    detailModalVisible.value = true;
  }
}
const { hasAccessByRoles } = useAccess();
const isAdmin = computed(() => {
  try {
    return hasAccessByRoles(['admin', 'superadmin']);
  } catch (error) {
    console.error('检查权限失败:', error);
    return false;
  }
});
const isReady = computed(() => {
  try {
    // ç®€åŒ–判断逻辑,减少不必要的计算
    return !!(pageDesignDetail.value && !loading.value);
  } catch (error) {
    console.error('检查页面准备状态失败:', error);
    return false;
  }
});
const formFieldTitleMap = computed(() => {
  try {
    if (pageDesignDetail.value && pageDesignDetail.value.formJson) {
      const arr = JSON.parse(pageDesignDetail.value.formJson);
      return arr.reduce((map, item) => {
        map[item.field] = item.title || item.field;
        return map;
      }, {});
    }
  } catch (e) {}
  return {};
});
</script>
<style scoped>
.template-page {
  background: #f5f6fa;
  padding: 16px;
  min-height: 100vh;
  height: 100vh;
  overflow: hidden;
}
/* ç¡®ä¿è¡¨æ ¼å®¹å™¨é«˜åº¦ç¨³å®š */
:deep(.vxe-table--main-wrapper) {
  height: 690px !important;
}
/* ç¡®ä¿åˆ†é¡µå™¨ä½ç½®å›ºå®š */
:deep(.vxe-pager) {
  position: sticky;
  bottom: 0;
  background: white;
  z-index: 10;
}
</style>
ruoyi-ui/apps/web-antd/src/views/tool/template/template-drawer.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,224 @@
<script setup lang="ts">
import { computed, ref, watch, nextTick } from 'vue';
import { Modal, message, FormItem } from 'ant-design-vue';
import { $t } from '@vben/locales';
import { templateAdd, templateUpdate, templateInfo } from '#/api/tool/template';
import { pageList } from '#/api/tool/page-designer';
import formCreate from '@form-create/element-ui';
interface ModalProps {
  id?: number | string;
  update: boolean;
  pageId?: string | number;
  formJson?: any;
}
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
  return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const modalVisible = ref(false);
const modalLoading = ref(false);
const currentEditId = ref<string | number>('');
const dynamicFormRef = ref();
const dynamicFormRule = ref([]);
const dynamicFormOption = ref({
  submitBtn: false,
  resetBtn: false
});
const showOkBtn = ref(true);
const currentPageId = ref('');
const formApi = ref();
// åˆ¤æ–­æ˜¯å¦æœ‰æäº¤æŒ‰é’®
type RuleItem = { type?: string };
const hasSubmitBtn = (rules: any[] = []) => {
  return rules.some((item: RuleItem) => item.type === 'submit');
};
// æ›´å½»åº•的递归移除所有 submit æŒ‰é’®ï¼Œæ”¯æŒ children、body、columns、list、options、tabs ç­‰å®¹å™¨
function removeSubmitBtn(rules = []) {
  return rules
    .filter(item => item.type !== 'submit')
    .map(item => {
      if (item.children) item.children = removeSubmitBtn(item.children);
      if (item.body) item.body = removeSubmitBtn(item.body);
      if (item.columns) item.columns = item.columns.map(col => ({
        ...col,
        list: removeSubmitBtn(col.list || [])
      }));
      if (item.list) item.list = removeSubmitBtn(item.list);
      if (item.options && Array.isArray(item.options)) item.options = removeSubmitBtn(item.options);
      if (item.tabs) item.tabs = item.tabs.map(tab => ({
        ...tab,
        list: removeSubmitBtn(tab.list || [])
      }));
      return item;
    });
}
// æ‰“开弹窗
const open = async (params: ModalProps = { update: false }) => {
  try {
    console.log('打开弹窗,参数:', params);
    modalVisible.value = true;
    modalLoading.value = true;
    isUpdate.value = params.update;
    currentEditId.value = params.id || '';
    currentPageId.value = params.pageId || '';
    // è®¾ç½®åŠ¨æ€è¡¨å•å­—æ®µï¼Œå§‹ç»ˆç§»é™¤æ‰€æœ‰submit按钮
    if (params.formJson) {
      console.log('设置动态表单规则:', params.formJson);
      dynamicFormRule.value = removeSubmitBtn(params.formJson);
      showOkBtn.value = true; // å§‹ç»ˆæ˜¾ç¤ºç¡®å®šæŒ‰é’®
    } else {
      dynamicFormRule.value = [];
      showOkBtn.value = true;
    }
    // ç¼–辑模式
    if (params.id && params.update) {
      console.log('编辑模式,获取详情数据');
      try {
        // èŽ·å–è¯¦æƒ…æ•°æ®
        const record = await templateInfo(params.id);
        console.log('获取到的记录数据:', record);
        // è®¾ç½®åŠ¨æ€è¡¨å•æ•°æ®
        if (record.formData) {
          try {
            const dynamicData = JSON.parse(record.formData);
            console.log('解析后的表单数据:', dynamicData);
            await nextTick();
            if (formApi.value) {
              console.log('准备设置表单数据到组件');
              // ä½¿ç”¨ form-create çš„ API è®¾ç½®å€¼
              Object.keys(dynamicData).forEach(key => {
                formApi.value.setValue(key, dynamicData[key]);
              });
              console.log('表单数据设置完成');
            } else {
              console.warn('表单API未就绪');
              message.error('表单未就绪,请重试');
            }
          } catch (error) {
            console.error('解析动态字段数据失败:', error);
            message.error('加载表单数据失败');
          }
        } else {
          console.log('记录中没有 formData æ•°æ®');
        }
      } catch (error) {
        console.error('处理表单数据失败:', error);
        message.error('加载表单数据失败,请重试');
      }
    } else {
      // æ–°å¢žæ—¶é‡ç½®æ•°æ®
      console.log('新增模式,重置表单');
      await nextTick();
      if (formApi.value) {
        formApi.value.resetFields && formApi.value.resetFields();
      }
    }
  } catch (error) {
    console.error('打开弹窗失败:', error);
    message.error('加载数据失败');
  } finally {
    modalLoading.value = false;
  }
};
const close = () => {
  modalVisible.value = false;
};
defineExpose({ open, close });
function onFormMounted(api) {
  formApi.value = api;
}
// handleOk åªè´Ÿè´£è§¦å‘表单 submit
async function handleOk() {
  await nextTick();
  console.log('handleOk formApi:', formApi.value);
  if (!dynamicFormRule.value || dynamicFormRule.value.length === 0) {
    message.error('表单规则未加载,无法提交');
    return;
  }
  if (!formApi.value) {
    message.error('表单未渲染完成,请稍后重试');
    return;
  }
  try {
    modalLoading.value = true;
    const valid = await formApi.value.validate();
    if (!valid) {
      modalLoading.value = false;
      return;
    }
    const formData = formApi.value.formData();
    await onFormSubmit(formData);
  } catch (error) {
    console.error('保存失败:', error);
    message.error('保存失败');
    modalLoading.value = false;
  }
}
// form-create çš„ submit äº‹ä»¶å¤„理
async function onFormSubmit(dynamicData: any) {
  try {
    modalLoading.value = true;
    // æž„建保存数据
    const finalData = {
      pageId: currentPageId.value,
      formData: Object.keys(dynamicData).length > 0 ? JSON.stringify(dynamicData) : undefined
    };
    if (isUpdate.value) {
      finalData.id = currentEditId.value;
    }
    console.log(finalData)
    await (isUpdate.value ? templateUpdate(finalData) : templateAdd(finalData));
    emit('reload');
    close();
    message.success('保存成功');
  } catch (error) {
    console.error('保存失败:', error);
    message.error('保存失败');
  } finally {
    modalLoading.value = false;
  }
}
function handleCancel() {
  close();
}
</script>
<template>
  <a-modal
    v-model:open="modalVisible"
    :title="title"
    :width="'80vw'"
    :confirm-loading="modalLoading"
    @ok="handleOk"
    @cancel="handleCancel"
    :bodyStyle="{ padding: '24px', minHeight: '60vh' }"
    destroyOnClose
    :ok-button-props="{ style: showOkBtn ? {} : { display: 'none' } }"
  >
    <!-- åŠ¨æ€è¡¨å•åŒºåŸŸ -->
    <form-create
      ref="dynamicFormRef"
      :rule="dynamicFormRule"
      :option="dynamicFormOption"
      @submit="onFormSubmit"
      @mounted="onFormMounted"
    />
  </a-modal>
</template>