办学质量监测教学评价系统
shenrongliang
14 小时以前 50da976ac81fbf9f3fcd6aeeeb48ec55d7e692e9
集成工作流
已修改7个文件
已添加93个文件
10627 ■■■■■ 文件已修改
ruoyi-admin/src/main/resources/application-dev.yml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 1 ●●●● 补丁 | 查看 | 原始文档 | 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 1 ●●●● 补丁 | 查看 | 原始文档 | 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-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
@@ -315,4 +315,3 @@
        stdio:
          servers-configuration: classpath:mcp-server.json
        request-timeout: 300s
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,7 @@
        <module>ruoyi-chat</module>
        <module>ruoyi-system</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-flowable</module>
    </modules>
    <properties>
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>