瀏覽代碼

:zap: 增加流程模块

smallchill 7 年之前
父節點
當前提交
8101049f2b
共有 53 個文件被更改,包括 2991 次插入28 次删除
  1. 4 4
      doc/sql/bladex-sword-mysql.sql
  2. 21 5
      pom.xml
  3. 63 0
      src/main/java/org/springblade/demo/leave/controller/LeaveController.java
  4. 67 0
      src/main/java/org/springblade/demo/leave/entity/ProcessLeave.java
  5. 29 0
      src/main/java/org/springblade/demo/leave/mapper/LeaveMapper.java
  6. 6 0
      src/main/java/org/springblade/demo/leave/mapper/LeaveMapper.xml
  7. 37 0
      src/main/java/org/springblade/demo/leave/service/ILeaveService.java
  8. 81 0
      src/main/java/org/springblade/demo/leave/service/impl/LeaveServiceImpl.java
  9. 6 0
      src/main/java/org/springblade/demo/package-info.java
  10. 136 0
      src/main/java/org/springblade/flow/business/controller/WorkController.java
  11. 72 0
      src/main/java/org/springblade/flow/business/service/FlowBusinessService.java
  12. 77 0
      src/main/java/org/springblade/flow/business/service/IFlowService.java
  13. 316 0
      src/main/java/org/springblade/flow/business/service/impl/FlowBusinessServiceImpl.java
  14. 97 0
      src/main/java/org/springblade/flow/business/service/impl/FlowServiceImpl.java
  15. 61 0
      src/main/java/org/springblade/flow/core/constant/ProcessConstant.java
  16. 175 0
      src/main/java/org/springblade/flow/core/entity/BladeFlow.java
  17. 43 0
      src/main/java/org/springblade/flow/core/entity/FlowEntity.java
  18. 66 0
      src/main/java/org/springblade/flow/core/utils/FlowUtil.java
  19. 71 0
      src/main/java/org/springblade/flow/core/utils/TaskUtil.java
  20. 53 0
      src/main/java/org/springblade/flow/engine/config/FlowableConfiguration.java
  21. 52 0
      src/main/java/org/springblade/flow/engine/constant/FlowConstant.java
  22. 66 0
      src/main/java/org/springblade/flow/engine/controller/FlowFollowController.java
  23. 119 0
      src/main/java/org/springblade/flow/engine/controller/FlowManagerController.java
  24. 86 0
      src/main/java/org/springblade/flow/engine/controller/FlowModelController.java
  25. 187 0
      src/main/java/org/springblade/flow/engine/controller/FlowProcessController.java
  26. 50 0
      src/main/java/org/springblade/flow/engine/entity/FlowExecution.java
  27. 57 0
      src/main/java/org/springblade/flow/engine/entity/FlowModel.java
  28. 59 0
      src/main/java/org/springblade/flow/engine/entity/FlowProcess.java
  29. 46 0
      src/main/java/org/springblade/flow/engine/mapper/FlowMapper.java
  30. 53 0
      src/main/java/org/springblade/flow/engine/mapper/FlowMapper.xml
  31. 128 0
      src/main/java/org/springblade/flow/engine/service/FlowEngineService.java
  32. 378 0
      src/main/java/org/springblade/flow/engine/service/impl/FlowEngineServiceImpl.java
  33. 72 0
      src/main/java/org/springblade/flow/engine/utils/FlowCache.java
  34. 2 1
      src/main/java/org/springblade/modules/auth/AuthController.java
  35. 2 1
      src/main/java/org/springblade/modules/desk/controller/DashBoardController.java
  36. 2 1
      src/main/java/org/springblade/modules/desk/controller/NoticeController.java
  37. 2 1
      src/main/java/org/springblade/modules/develop/controller/CodeController.java
  38. 2 1
      src/main/java/org/springblade/modules/system/controller/AuthClientController.java
  39. 2 1
      src/main/java/org/springblade/modules/system/controller/DeptController.java
  40. 2 1
      src/main/java/org/springblade/modules/system/controller/DictController.java
  41. 2 1
      src/main/java/org/springblade/modules/system/controller/LogApiController.java
  42. 2 1
      src/main/java/org/springblade/modules/system/controller/LogErrorController.java
  43. 2 1
      src/main/java/org/springblade/modules/system/controller/LogUsualController.java
  44. 2 1
      src/main/java/org/springblade/modules/system/controller/MenuController.java
  45. 2 1
      src/main/java/org/springblade/modules/system/controller/ParamController.java
  46. 2 1
      src/main/java/org/springblade/modules/system/controller/RoleController.java
  47. 2 1
      src/main/java/org/springblade/modules/system/controller/TenantController.java
  48. 2 1
      src/main/java/org/springblade/modules/system/controller/UserController.java
  49. 1 1
      src/main/resources/application-dev.yml
  50. 1 1
      src/main/resources/application-prod.yml
  51. 1 1
      src/main/resources/application-test.yml
  52. 123 0
      src/main/resources/processes/LeaveProcess.bpmn20.xml
  53. 1 1
      src/test/java/org/springblade/test/BladeTest.java

File diff suppressed because it is too large
+ 4 - 4
doc/sql/bladex-sword-mysql.sql


+ 21 - 5
pom.xml

@@ -18,6 +18,7 @@
         <swagger.bootstrapui.version>1.9.3</swagger.bootstrapui.version>
         <mybatis.plus.version>3.1.0</mybatis.plus.version>
         <protostuff.version>1.6.0</protostuff.version>
+        <flowable.version>6.4.1</flowable.version>
 
         <spring.boot.version>2.1.4.RELEASE</spring.boot.version>
         <spring.platform.version>Cairo-SR7</spring.platform.version>
@@ -48,6 +49,7 @@
     </dependencyManagement>
 
     <dependencies>
+        <!-- Blade -->
         <dependency>
             <groupId>org.springblade</groupId>
             <artifactId>blade-core-boot</artifactId>
@@ -86,6 +88,18 @@
             <version>${bladex.tool.version}</version>
             <scope>test</scope>
         </dependency>
+        <!-- 工作流 -->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-starter</artifactId>
+            <version>${flowable.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-json-converter</artifactId>
+            <version>${flowable.version}</version>
+        </dependency>
+        <!-- Mybatis-Plus -->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-generator</artifactId>
@@ -98,21 +112,23 @@
             <version>1.7</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-spring</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+        <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <scope>provided</scope>
         </dependency>
+        <!-- devtools -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
             <optional>true</optional>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <build>

+ 63 - 0
src/main/java/org/springblade/demo/leave/controller/LeaveController.java

@@ -0,0 +1,63 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.demo.leave.controller;
+
+import lombok.AllArgsConstructor;
+import org.springblade.common.cache.UserCache;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.tool.api.R;
+import org.springblade.demo.leave.entity.ProcessLeave;
+import org.springblade.demo.leave.service.ILeaveService;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * 控制器
+ *
+ * @author Chill
+ */
+@ApiIgnore
+@RestController
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME + "/process/leave")
+@AllArgsConstructor
+public class LeaveController {
+
+	private ILeaveService leaveService;
+
+	/**
+	 * 详情
+	 *
+	 * @param businessId 主键
+	 */
+	@GetMapping("detail")
+	public R<ProcessLeave> detail(Integer businessId) {
+		ProcessLeave detail = leaveService.getById(businessId);
+		detail.getFlow().setAssigneeName(UserCache.getUser(detail.getCreateUser()).getName());
+		return R.data(detail);
+	}
+
+	/**
+	 * 新增或修改
+	 *
+	 * @param leave 请假信息
+	 */
+	@PostMapping("start-process")
+	public R startProcess(@RequestBody ProcessLeave leave) {
+		return R.status(leaveService.startProcess(leave));
+	}
+
+}

+ 67 - 0
src/main/java/org/springblade/demo/leave/entity/ProcessLeave.java

@@ -0,0 +1,67 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.demo.leave.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.flow.core.entity.FlowEntity;
+
+import java.time.LocalDateTime;
+
+/**
+ * 请假流程实体类
+ *
+ * @author Chill
+ */
+@Data
+@TableName("blade_process_leave")
+@EqualsAndHashCode(callSuper = true)
+public class ProcessLeave extends FlowEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 流程定义id
+	 */
+	private String processDefinitionId;
+	/**
+	 * 流程实例id
+	 */
+	private String processInstanceId;
+	/**
+	 * 请假开始时间
+	 */
+	private LocalDateTime startTime;
+	/**
+	 * 请假结束时间
+	 */
+	private LocalDateTime endTime;
+	/**
+	 * 请假理由
+	 */
+	private String reason;
+	/**
+	 * 审批人
+	 */
+	private String taskUser;
+	/**
+	 * 流程申请时间
+	 */
+	private LocalDateTime applyTime;
+
+}

+ 29 - 0
src/main/java/org/springblade/demo/leave/mapper/LeaveMapper.java

@@ -0,0 +1,29 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.demo.leave.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.demo.leave.entity.ProcessLeave;
+
+/**
+ * Mapper 接口
+ *
+ * @author Chill
+ */
+public interface LeaveMapper extends BaseMapper<ProcessLeave> {
+
+}

+ 6 - 0
src/main/java/org/springblade/demo/leave/mapper/LeaveMapper.xml

@@ -0,0 +1,6 @@
+<?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.springblade.demo.leave.mapper.LeaveMapper">
+
+
+</mapper>

+ 37 - 0
src/main/java/org/springblade/demo/leave/service/ILeaveService.java

@@ -0,0 +1,37 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.demo.leave.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.demo.leave.entity.ProcessLeave;
+
+/**
+ * 服务类
+ *
+ * @author Chill
+ */
+public interface ILeaveService extends BaseService<ProcessLeave> {
+
+	/**
+	 * 开启流程
+	 *
+	 * @param leave 请假实体
+	 * @return boolean
+	 */
+	boolean startProcess(ProcessLeave leave);
+
+}

+ 81 - 0
src/main/java/org/springblade/demo/leave/service/impl/LeaveServiceImpl.java

@@ -0,0 +1,81 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.demo.leave.service.impl;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.demo.leave.entity.ProcessLeave;
+import org.springblade.demo.leave.mapper.LeaveMapper;
+import org.springblade.demo.leave.service.ILeaveService;
+import org.springblade.flow.business.service.IFlowService;
+import org.springblade.flow.core.constant.ProcessConstant;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.FlowUtil;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * 服务实现类
+ *
+ * @author Chill
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class LeaveServiceImpl extends BaseServiceImpl<LeaveMapper, ProcessLeave> implements ILeaveService {
+
+	private IFlowService flowService;
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public boolean startProcess(ProcessLeave leave) {
+		String businessTable = FlowUtil.getBusinessTable(ProcessConstant.LEAVE_KEY);
+		if (Func.isEmpty(leave.getId())) {
+			// 保存leave
+			leave.setApplyTime(LocalDateTime.now());
+			save(leave);
+			// 启动流程
+			Kv variables = Kv.create()
+				.set(ProcessConstant.TASK_VARIABLE_CREATE_USER, SecureUtil.getUserName())
+				.set("taskUser", TaskUtil.getTaskUser(leave.getTaskUser()))
+				.set("days", Duration.between(leave.getStartTime(), leave.getEndTime()).toDays());
+			BladeFlow flow = flowService.startProcessInstanceById(leave.getProcessDefinitionId(), FlowUtil.getBusinessKey(businessTable, String.valueOf(leave.getId())), variables);
+			if (Func.isNotEmpty(flow)) {
+				log.debug("流程已启动,流程ID:" + flow.getProcessInstanceId());
+				// 返回流程id写入leave
+				leave.setProcessInstanceId(flow.getProcessInstanceId());
+				updateById(leave);
+			} else {
+				throw new ServiceException("开启流程失败");
+			}
+		} else {
+
+			updateById(leave);
+		}
+		return true;
+	}
+
+}

+ 6 - 0
src/main/java/org/springblade/demo/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * Created by Blade.
+ *
+ * @author zhuangqian
+ */
+package org.springblade.demo;

+ 136 - 0
src/main/java/org/springblade/flow/business/controller/WorkController.java

@@ -0,0 +1,136 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.business.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.flowable.engine.TaskService;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.flow.business.service.FlowBusinessService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 流程事务通用接口
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/work")
+@Api(value = "流程事务通用接口", tags = "流程事务通用接口")
+public class WorkController {
+
+	private TaskService taskService;
+	private FlowEngineService flowEngineService;
+	private FlowBusinessService flowBusinessService;
+
+	/**
+	 * 发起事务列表页
+	 */
+	@GetMapping("start-list")
+	@ApiOperation(value = "发起事务列表页", notes = "传入流程类型", position = 1)
+	public R<IPage<FlowProcess>> startList(@ApiParam("流程类型") String category, Query query) {
+		IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category);
+		return R.data(pages);
+	}
+
+	/**
+	 * 待签事务列表页
+	 */
+	@GetMapping("claim-list")
+	@ApiOperation(value = "待签事务列表页", notes = "传入流程信息", position = 2)
+	public R<IPage<BladeFlow>> claimList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectClaimPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 待办事务列表页
+	 */
+	@GetMapping("todo-list")
+	@ApiOperation(value = "待办事务列表页", notes = "传入流程信息", position = 3)
+	public R<IPage<BladeFlow>> todoList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectTodoPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 已发事务列表页
+	 */
+	@GetMapping("send-list")
+	@ApiOperation(value = "已发事务列表页", notes = "传入流程信息", position = 4)
+	public R<IPage<BladeFlow>> sendList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectSendPage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 办结事务列表页
+	 */
+	@GetMapping("done-list")
+	@ApiOperation(value = "办结事务列表页", notes = "传入流程信息", position = 5)
+	public R<IPage<BladeFlow>> doneList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
+		IPage<BladeFlow> pages = flowBusinessService.selectDonePage(Condition.getPage(query), bladeFlow);
+		return R.data(pages);
+	}
+
+	/**
+	 * 签收事务
+	 *
+	 * @param taskId 任务id
+	 */
+	@PostMapping("claim-task")
+	@ApiOperation(value = "签收事务", notes = "传入流程信息", position = 6)
+	public R claimTask(@ApiParam("任务id") String taskId) {
+		taskService.claim(taskId, TaskUtil.getTaskUser());
+		return R.success("签收事务成功");
+	}
+
+	/**
+	 * 完成任务
+	 *
+	 * @param flow 请假信息
+	 */
+	@PostMapping("complete-task")
+	public R completeTask(@ApiParam("任务信息") @RequestBody BladeFlow flow) {
+		return R.status(flowBusinessService.completeTask(flow));
+	}
+
+	/**
+	 * 删除任务
+	 *
+	 * @param taskId 任务id
+	 * @param reason 删除原因
+	 */
+	@PostMapping("delete-task")
+	@ApiOperation(value = "删除任务", notes = "传入流程信息", position = 7)
+	public R deleteTask(@ApiParam("任务id") String taskId, @ApiParam("删除原因") String reason) {
+		taskService.deleteTask(taskId, reason);
+		return R.success("删除任务成功");
+	}
+
+}

+ 72 - 0
src/main/java/org/springblade/flow/business/service/FlowBusinessService.java

@@ -0,0 +1,72 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.business.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.flow.core.entity.BladeFlow;
+
+/**
+ * 流程业务类
+ *
+ * @author Chill
+ */
+public interface FlowBusinessService {
+
+	/**
+	 * 流程待签列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程待办列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程已发列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 流程办结列表
+	 *
+	 * @param page      分页工具
+	 * @param bladeFlow 流程类
+	 * @return
+	 */
+	IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow);
+
+	/**
+	 * 完成任务
+	 *
+	 * @param leave 请假信息
+	 * @return boolean
+	 */
+	boolean completeTask(BladeFlow leave);
+}

+ 77 - 0
src/main/java/org/springblade/flow/business/service/IFlowService.java

@@ -0,0 +1,77 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.business.service;
+
+import org.springblade.flow.core.entity.BladeFlow;
+
+import java.util.Map;
+
+/**
+ * 工作流调用接口.
+ *
+ * @author Chill
+ */
+public interface IFlowService {
+
+	/**
+	 * 开启流程
+	 *
+	 * @param processDefinitionId 流程id
+	 * @param businessKey         业务key
+	 * @param variables           参数
+	 * @return BladeFlow
+	 */
+	BladeFlow startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables);
+
+	/**
+	 * 开启流程
+	 *
+	 * @param processDefinitionKey 流程标识
+	 * @param businessKey          业务key
+	 * @param variables            参数
+	 * @return BladeFlow
+	 */
+	BladeFlow startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables);
+
+	/**
+	 * 完成任务
+	 *
+	 * @param taskId            任务id
+	 * @param processInstanceId 流程实例id
+	 * @param comment           评论
+	 * @param variables         参数
+	 * @return R
+	 */
+	boolean completeTask(String taskId, String processInstanceId, String comment, Map<String, Object> variables);
+
+	/**
+	 * 获取流程变量
+	 *
+	 * @param taskId       任务id
+	 * @param variableName 变量名
+	 * @return R
+	 */
+	Object taskVariable(String taskId, String variableName);
+
+	/**
+	 * 获取流程变量集合
+	 *
+	 * @param taskId 任务id
+	 * @return R
+	 */
+	Map<String, Object> taskVariables(String taskId);
+}

+ 316 - 0
src/main/java/org/springblade/flow/business/service/impl/FlowBusinessServiceImpl.java

@@ -0,0 +1,316 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.business.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.AllArgsConstructor;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.history.HistoricProcessInstanceQuery;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.flowable.task.api.history.HistoricTaskInstanceQuery;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.business.service.FlowBusinessService;
+import org.springblade.flow.core.constant.ProcessConstant;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.constant.FlowConstant;
+import org.springblade.flow.engine.utils.FlowCache;
+import org.springframework.stereotype.Service;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 流程业务实现类
+ *
+ * @author Chill
+ */
+@Service
+@AllArgsConstructor
+public class FlowBusinessServiceImpl implements FlowBusinessService {
+
+	private TaskService taskService;
+	private HistoryService historyService;
+
+	@Override
+	public IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		String taskGroup = TaskUtil.getCandidateGroup();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		// 等待签收的任务
+		TaskQuery claimUserQuery = taskService.createTaskQuery().taskCandidateUser(taskUser)
+			.includeProcessVariables().active().orderByTaskCreateTime().desc();
+		// 等待签收的任务
+		TaskQuery claimRoleQuery = taskService.createTaskQuery().taskCandidateGroup(taskGroup)
+			.includeProcessVariables().active().orderByTaskCreateTime().desc();
+
+		// 构建列表数据
+		buildFlowTaskList(bladeFlow, flowList, claimUserQuery, FlowConstant.STATUS_CLAIM);
+		buildFlowTaskList(bladeFlow, flowList, claimRoleQuery, FlowConstant.STATUS_CLAIM);
+
+		// 计算总数
+		long count = claimUserQuery.count() + claimRoleQuery.count();
+		// 设置页数
+		page.setSize(count);
+		// 设置总数
+		page.setTotal(count);
+		// 设置数据
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		// 已签收的任务
+		TaskQuery todoQuery = taskService.createTaskQuery().taskAssignee(taskUser).active()
+			.includeProcessVariables().orderByTaskCreateTime().desc();
+
+		// 构建列表数据
+		buildFlowTaskList(bladeFlow, flowList, todoQuery, FlowConstant.STATUS_TODO);
+
+		// 计算总数
+		long count = todoQuery.count();
+		// 设置页数
+		page.setSize(count);
+		// 设置总数
+		page.setTotal(count);
+		// 设置数据
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		HistoricProcessInstanceQuery historyQuery = historyService.createHistoricProcessInstanceQuery().startedBy(taskUser).orderByProcessInstanceStartTime().desc();
+
+		if (bladeFlow.getCategory() != null) {
+			historyQuery.processDefinitionCategory(bladeFlow.getCategory());
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			historyQuery.startedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			historyQuery.startedBefore(bladeFlow.getEndDate());
+		}
+
+		// 查询列表
+		List<HistoricProcessInstance> historyList = historyQuery.listPage(Func.toInt(page.getCurrent() - 1), Func.toInt(page.getSize() * page.getCurrent()));
+
+		historyList.forEach(historicProcessInstance -> {
+			BladeFlow flow = new BladeFlow();
+			// historicProcessInstance
+			flow.setCreateTime(historicProcessInstance.getStartTime());
+			flow.setEndTime(historicProcessInstance.getEndTime());
+			flow.setVariables(historicProcessInstance.getProcessVariables());
+			String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+			if (businessKey.length > 1) {
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+			}
+			flow.setHistoryActivityName(historicProcessInstance.getName());
+			flow.setProcessInstanceId(historicProcessInstance.getId());
+			flow.setHistoryProcessInstanceId(historicProcessInstance.getId());
+			// ProcessDefinition
+			ProcessDefinition processDefinition = FlowCache.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flow.setProcessInstanceId(historicProcessInstance.getId());
+			// HistoricTaskInstance
+			HistoricTaskInstance historyTask = historyService.createHistoricTaskInstanceQuery().processInstanceId(historicProcessInstance.getId()).orderByHistoricTaskInstanceEndTime().desc().list().get(0);
+			flow.setTaskId(historyTask.getId());
+			flow.setTaskName(historyTask.getName());
+			flow.setTaskDefinitionKey(historyTask.getTaskDefinitionKey());
+			// Status
+			if (historicProcessInstance.getEndActivityId() != null) {
+				flow.setProcessIsFinished(FlowConstant.STATUS_FINISHED);
+			} else {
+				flow.setProcessIsFinished(FlowConstant.STATUS_UNFINISHED);
+			}
+			flow.setStatus(FlowConstant.STATUS_FINISH);
+			flowList.add(flow);
+		});
+
+		// 计算总数
+		long count = historyQuery.count();
+		// 设置总数
+		page.setTotal(count);
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
+		String taskUser = TaskUtil.getTaskUser();
+		List<BladeFlow> flowList = new LinkedList<>();
+
+		HistoricTaskInstanceQuery doneQuery = historyService.createHistoricTaskInstanceQuery().taskAssignee(taskUser).finished()
+			.includeProcessVariables().orderByHistoricTaskInstanceEndTime().desc();
+
+		if (bladeFlow.getCategory() != null) {
+			doneQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			doneQuery.taskCompletedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			doneQuery.taskCompletedBefore(bladeFlow.getEndDate());
+		}
+
+		// 查询列表
+		List<HistoricTaskInstance> doneList = doneQuery.listPage(Func.toInt(page.getCurrent() - 1), Func.toInt(page.getSize() * page.getCurrent()));
+		doneList.forEach(historicTaskInstance -> {
+			BladeFlow flow = new BladeFlow();
+			flow.setTaskId(historicTaskInstance.getId());
+			flow.setTaskDefinitionKey(historicTaskInstance.getTaskDefinitionKey());
+			flow.setTaskName(historicTaskInstance.getName());
+			flow.setAssignee(historicTaskInstance.getAssignee());
+			flow.setCreateTime(historicTaskInstance.getCreateTime());
+			flow.setExecutionId(historicTaskInstance.getExecutionId());
+			flow.setHistoryTaskEndTime(historicTaskInstance.getEndTime());
+			flow.setVariables(historicTaskInstance.getProcessVariables());
+
+			ProcessDefinition processDefinition = FlowCache.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+
+			flow.setProcessInstanceId(historicTaskInstance.getProcessInstanceId());
+			flow.setHistoryProcessInstanceId(historicTaskInstance.getProcessInstanceId());
+			HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance((historicTaskInstance.getProcessInstanceId()));
+			if (Func.isNotEmpty(historicProcessInstance)) {
+				String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+				if (historicProcessInstance.getEndActivityId() != null) {
+					flow.setProcessIsFinished(FlowConstant.STATUS_FINISHED);
+				} else {
+					flow.setProcessIsFinished(FlowConstant.STATUS_UNFINISHED);
+				}
+			}
+			flow.setStatus(FlowConstant.STATUS_FINISH);
+			flowList.add(flow);
+		});
+		// 计算总数
+		long count = doneQuery.count();
+		// 设置总数
+		page.setTotal(count);
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public boolean completeTask(BladeFlow flow) {
+		String taskId = flow.getTaskId();
+		String processInstanceId = flow.getProcessInstanceId();
+		String comment = Func.toStr(flow.getComment(), ProcessConstant.PASS_COMMENT);
+		// 增加评论
+		if (StringUtil.isNoneBlank(processInstanceId, comment)) {
+			taskService.addComment(taskId, processInstanceId, comment);
+		}
+		// 创建变量
+		Map<String, Object> variables = flow.getVariables();
+		if (variables == null) {
+			variables = Kv.create();
+		}
+		variables.put(ProcessConstant.PASS_KEY, flow.isPass());
+		// 完成任务
+		taskService.complete(taskId, variables);
+		return true;
+	}
+
+	/**
+	 * 构建流程
+	 *
+	 * @param bladeFlow 流程通用类
+	 * @param flowList  流程列表
+	 * @param taskQuery 任务查询类
+	 * @param status    状态
+	 */
+	private void buildFlowTaskList(BladeFlow bladeFlow, List<BladeFlow> flowList, TaskQuery taskQuery, String status) {
+		if (bladeFlow.getCategory() != null) {
+			taskQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
+		}
+		if (bladeFlow.getBeginDate() != null) {
+			taskQuery.taskCreatedAfter(bladeFlow.getBeginDate());
+		}
+		if (bladeFlow.getEndDate() != null) {
+			taskQuery.taskCreatedBefore(bladeFlow.getEndDate());
+		}
+		taskQuery.list().forEach(task -> {
+			BladeFlow flow = new BladeFlow();
+			flow.setTaskId(task.getId());
+			flow.setTaskDefinitionKey(task.getTaskDefinitionKey());
+			flow.setTaskName(task.getName());
+			flow.setAssignee(task.getAssignee());
+			flow.setCreateTime(task.getCreateTime());
+			flow.setClaimTime(task.getClaimTime());
+			flow.setExecutionId(task.getExecutionId());
+			flow.setVariables(task.getProcessVariables());
+
+			HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
+			if (Func.isNotEmpty(historicProcessInstance)) {
+				String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
+				flow.setBusinessTable(businessKey[0]);
+				flow.setBusinessId(businessKey[1]);
+			}
+
+			ProcessDefinition processDefinition = FlowCache.getProcessDefinition(task.getProcessDefinitionId());
+			flow.setCategory(processDefinition.getCategory());
+			flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flow.setProcessDefinitionId(processDefinition.getId());
+			flow.setProcessDefinitionName(processDefinition.getName());
+			flow.setProcessDefinitionKey(processDefinition.getKey());
+			flow.setProcessDefinitionVersion(processDefinition.getVersion());
+			flow.setProcessInstanceId(task.getProcessInstanceId());
+			flow.setStatus(status);
+			flowList.add(flow);
+		});
+	}
+
+	/**
+	 * 获取历史流程
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @return HistoricProcessInstance
+	 */
+	private HistoricProcessInstance getHistoricProcessInstance(String processInstanceId) {
+		return historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+	}
+
+}

+ 97 - 0
src/main/java/org/springblade/flow/business/service/impl/FlowServiceImpl.java

@@ -0,0 +1,97 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.business.service.impl;
+
+import lombok.AllArgsConstructor;
+import org.flowable.engine.IdentityService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.business.service.IFlowService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * 流程实现类
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+public class FlowServiceImpl implements IFlowService {
+
+	private RuntimeService runtimeService;
+	private IdentityService identityService;
+	private TaskService taskService;
+
+	@Override
+	public BladeFlow startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables) {
+		// 设置流程启动用户
+		identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
+		// 开启流程
+		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
+		// 组装流程通用类
+		BladeFlow flow = new BladeFlow();
+		flow.setProcessInstanceId(processInstance.getId());
+		return flow;
+	}
+
+	@Override
+	public BladeFlow startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables) {
+		// 设置流程启动用户
+		identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
+		// 开启流程
+		ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
+		// 组装流程通用类
+		BladeFlow flow = new BladeFlow();
+		flow.setProcessInstanceId(processInstance.getId());
+		return flow;
+	}
+
+	@Override
+	public boolean completeTask(String taskId, String processInstanceId, String comment, Map<String, Object> variables) {
+		// 增加评论
+		if (StringUtil.isNoneBlank(processInstanceId, comment)) {
+			taskService.addComment(taskId, processInstanceId, comment);
+		}
+		// 非空判断
+		if (Func.isEmpty(variables)) {
+			variables = Kv.create();
+		}
+		// 完成任务
+		taskService.complete(taskId, variables);
+		return true;
+	}
+
+	@Override
+	public Object taskVariable(String taskId, String variableName) {
+		return R.data(taskService.getVariable(taskId, variableName));
+	}
+
+	@Override
+	public Map<String, Object> taskVariables(String taskId) {
+		return taskService.getVariables(taskId);
+	}
+
+}

+ 61 - 0
src/main/java/org/springblade/flow/core/constant/ProcessConstant.java

@@ -0,0 +1,61 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.core.constant;
+
+/**
+ * 流程常量.
+ *
+ * @author Chill
+ */
+public interface ProcessConstant {
+
+	/**
+	 * 请假流程标识
+	 */
+	String LEAVE_KEY = "Leave";
+
+	/**
+	 * 报销流程标识
+	 */
+	String Expense_KEY = "Expense";
+
+	/**
+	 * 同意标识
+	 */
+	String PASS_KEY = "pass";
+
+	/**
+	 * 同意代号
+	 */
+	String PASS_ALIAS = "ok";
+
+	/**
+	 * 同意默认批复
+	 */
+	String PASS_COMMENT = "同意";
+
+	/**
+	 * 驳回默认批复
+	 */
+	String NOT_PASS_COMMENT = "驳回";
+
+	/**
+	 * 创建人变量名
+	 */
+	String TASK_VARIABLE_CREATE_USER = "createUser";
+
+}

+ 175 - 0
src/main/java/org/springblade/flow/core/entity/BladeFlow.java

@@ -0,0 +1,175 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.core.entity;
+
+import lombok.Data;
+import org.springblade.flow.core.constant.ProcessConstant;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 工作流通用实体类
+ *
+ * @author Chill
+ */
+@Data
+public class BladeFlow implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 任务编号
+	 */
+	private String taskId;
+	/**
+	 * 任务名称
+	 */
+	private String taskName;
+	/**
+	 * 任务定义Key
+	 */
+	private String taskDefinitionKey;
+	/**
+	 * 任务执行人编号
+	 */
+	private String assignee;
+	/**
+	 * 任务执行人名称
+	 */
+	private String assigneeName;
+	/**
+	 * 流程分类
+	 */
+	private String category;
+	/**
+	 * 流程分类名
+	 */
+	private String categoryName;
+	/**
+	 * 创建时间
+	 */
+	private Date createTime;
+	/**
+	 * 结束时间
+	 */
+	private Date endTime;
+	/**
+	 * 签收时间
+	 */
+	private Date claimTime;
+	/**
+	 * 历史任务结束时间
+	 */
+	private Date historyTaskEndTime;
+	/**
+	 * 执行ID
+	 */
+	private String executionId;
+	/**
+	 * 流程实例ID
+	 */
+	private String processInstanceId;
+	/**
+	 * 流程ID
+	 */
+	private String processDefinitionId;
+	/**
+	 * 流程标识
+	 */
+	private String processDefinitionKey;
+	/**
+	 * 流程名
+	 */
+	private String processDefinitionName;
+	/**
+	 * 流程版本
+	 */
+	private int processDefinitionVersion;
+	/**
+	 * 流程说明
+	 */
+	private String processDefinitionDesc;
+	/**
+	 * 流程简图名
+	 */
+	private String processDefinitionDiagramResName;
+	/**
+	 * 流程重命名
+	 */
+	private String processDefinitionResName;
+	/**
+	 * 历史任务流程实例ID 查看流程图会用到
+	 */
+	private String historyProcessInstanceId;
+	/**
+	 * 流程实例是否结束
+	 */
+	private String processIsFinished;
+	/**
+	 * 历史活动流程
+	 */
+	private String historyActivityName;
+	/**
+	 * 历史活动耗时
+	 */
+	private String historyActivityDurationTime;
+	/**
+	 * 业务绑定Table
+	 */
+	private String businessTable;
+	/**
+	 * 业务绑定ID
+	 */
+	private String businessId;
+	/**
+	 * 任务状态
+	 */
+	private String status;
+	/**
+	 * 任务意见
+	 */
+	private String comment;
+	/**
+	 * 是否通过
+	 */
+	private boolean isPass;
+	/**
+	 * 是否通过代号
+	 */
+	private String flag;
+	/**
+	 * 开始查询日期
+	 */
+	private Date beginDate;
+	/**
+	 * 结束查询日期
+	 */
+	private Date endDate;
+	/**
+	 * 流程参数
+	 */
+	private Map<String, Object> variables;
+
+	/**
+	 * 获取是否通过
+	 */
+	public boolean isPass() {
+		return ProcessConstant.PASS_ALIAS.equals(flag) || ProcessConstant.PASS_COMMENT.equals(comment);
+	}
+
+}

+ 43 - 0
src/main/java/org/springblade/flow/core/entity/FlowEntity.java

@@ -0,0 +1,43 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.core.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * FlowEntity
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FlowEntity extends BaseEntity {
+
+	@TableField(exist = false)
+	private BladeFlow flow;
+
+	public BladeFlow getFlow() {
+		if (flow == null) {
+			flow = new BladeFlow();
+		}
+		return flow;
+	}
+
+}

+ 66 - 0
src/main/java/org/springblade/flow/core/utils/FlowUtil.java

@@ -0,0 +1,66 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.core.utils;
+
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.core.constant.ProcessConstant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 工作流工具类
+ *
+ * @author Chill
+ */
+public class FlowUtil {
+
+	/**
+	 * 定义流程key对应的表名
+	 */
+	private final static Map<String, String> BUSINESS_TABLE = new HashMap<>();
+
+	static {
+		BUSINESS_TABLE.put(ProcessConstant.LEAVE_KEY, "blade_process_leave");
+	}
+
+	/**
+	 * 通过流程key获取业务表名
+	 *
+	 * @param key 流程key
+	 */
+	public static String getBusinessTable(String key) {
+		String businessTable = BUSINESS_TABLE.get(key);
+		if (Func.isEmpty(businessTable)) {
+			throw new RuntimeException("流程启动失败,未找到相关业务表");
+		}
+		return businessTable;
+	}
+
+	/**
+	 * 获取业务标识
+	 *
+	 * @param businessTable 业务表
+	 * @param businessId    业务表主键
+	 * @return businessKey
+	 */
+	public static String getBusinessKey(String businessTable, String businessId) {
+		return StringUtil.format("{}:{}", businessTable, businessId);
+	}
+
+}

+ 71 - 0
src/main/java/org/springblade/flow/core/utils/TaskUtil.java

@@ -0,0 +1,71 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.core.utils;
+
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+
+import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
+
+/**
+ * 工作流任务工具类
+ *
+ * @author Chill
+ */
+public class TaskUtil {
+
+	/**
+	 * 获取任务用户格式
+	 *
+	 * @return taskUser
+	 */
+	public static String getTaskUser() {
+		return StringUtil.format("{}{}", TASK_USR_PREFIX, SecureUtil.getUserId());
+	}
+
+	/**
+	 * 获取任务用户格式
+	 *
+	 * @param userId 用户id
+	 * @return taskUser
+	 */
+	public static String getTaskUser(String userId) {
+		return StringUtil.format("{}{}", TASK_USR_PREFIX, userId);
+	}
+
+
+	/**
+	 * 获取用户主键
+	 *
+	 * @param taskUser 任务用户
+	 * @return userId
+	 */
+	public static Integer getUserId(String taskUser) {
+		return Func.toInt(StringUtil.removePrefix(taskUser, TASK_USR_PREFIX));
+	}
+
+	/**
+	 * 获取用户组格式
+	 *
+	 * @return candidateGroup
+	 */
+	public static String getCandidateGroup() {
+		return SecureUtil.getUserRole();
+	}
+
+}

+ 53 - 0
src/main/java/org/springblade/flow/engine/config/FlowableConfiguration.java

@@ -0,0 +1,53 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.config;
+
+import lombok.AllArgsConstructor;
+import org.flowable.spring.SpringProcessEngineConfiguration;
+import org.flowable.spring.boot.EngineConfigurationConfigurer;
+import org.flowable.spring.boot.FlowableProperties;
+import org.springblade.core.secure.registry.SecureRegistry;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Flowable配置类
+ *
+ * @author Chill
+ */
+@Configuration
+@AllArgsConstructor
+@EnableConfigurationProperties(FlowableProperties.class)
+public class FlowableConfiguration implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
+	private FlowableProperties flowableProperties;
+
+	@Override
+	public void configure(SpringProcessEngineConfiguration engineConfiguration) {
+		engineConfiguration.setActivityFontName(flowableProperties.getActivityFontName());
+		engineConfiguration.setLabelFontName(flowableProperties.getLabelFontName());
+		engineConfiguration.setAnnotationFontName(flowableProperties.getAnnotationFontName());
+	}
+
+	@Bean
+	public SecureRegistry secureRegistry() {
+		SecureRegistry secureRegistry = new SecureRegistry();
+		secureRegistry.excludePathPatterns("/manager/resource");
+		return secureRegistry;
+	}
+
+}

+ 52 - 0
src/main/java/org/springblade/flow/engine/constant/FlowConstant.java

@@ -0,0 +1,52 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.constant;
+
+/**
+ * 流程常量.
+ *
+ * @author zhuangqian
+ */
+public interface FlowConstant {
+
+	String FLOWABLE_BASE_PACKAGES = "org.flowable.ui";
+
+	String SUFFIX = ".bpmn20.xml";
+
+	String ACTIVE = "active";
+
+	String SUSPEND = "suspend";
+
+	String STATUS_TODO = "todo";
+
+	String STATUS_CLAIM = "claim";
+
+	String STATUS_SEND = "send";
+
+	String STATUS_DONE = "done";
+
+	String STATUS_FINISHED = "finished";
+
+	String STATUS_UNFINISHED = "unfinished";
+
+	String STATUS_FINISH = "finish";
+
+	String START_EVENT = "startEvent";
+
+	String END_EVENT = "endEvent";
+
+}

+ 66 - 0
src/main/java/org/springblade/flow/engine/controller/FlowFollowController.java

@@ -0,0 +1,66 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 流程状态控制器
+ *
+ * @author Chill
+ */
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/follow")
+@AllArgsConstructor
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+public class FlowFollowController {
+
+	private FlowEngineService flowEngineService;
+
+	/**
+	 * 流程状态列表
+	 */
+	@GetMapping("list")
+	@ApiOperation(value = "分页", notes = "传入notice", position = 1)
+	public R<IPage<FlowExecution>> list(Query query, @ApiParam(value = "流程实例id") String processInstanceId, @ApiParam(value = "流程key") String processDefinitionKey) {
+		IPage<FlowExecution> pages = flowEngineService.selectFollowPage(Condition.getPage(query), processInstanceId, processDefinitionKey);
+		return R.data(pages);
+	}
+
+	/**
+	 * 删除流程实例
+	 */
+	@PostMapping("delete-process-instance")
+	@ApiOperation(value = "删除", notes = "传入主键集合", position = 2)
+	public R deleteProcessInstance(@ApiParam(value = "流程实例id") @RequestParam String processInstanceId, @ApiParam(value = "删除原因") @RequestParam String deleteReason) {
+		boolean temp = flowEngineService.deleteProcessInstance(processInstanceId, deleteReason);
+		return R.status(temp);
+	}
+
+}

+ 119 - 0
src/main/java/org/springblade/flow/engine/controller/FlowManagerController.java

@@ -0,0 +1,119 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.utils.IntegerPool;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * 流程管理接口
+ *
+ * @author Chill
+ */
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/manager")
+@AllArgsConstructor
+@Api(value = "流程管理接口", tags = "流程管理接口")
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+public class FlowManagerController {
+
+	private FlowEngineService flowEngineService;
+
+	/**
+	 * 分页
+	 */
+	@GetMapping("list")
+	@ApiOperation(value = "分页", notes = "传入流程类型", position = 1)
+	public R<IPage<FlowProcess>> list(@ApiParam("流程类型") String category, Query query) {
+		IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category);
+		return R.data(pages);
+	}
+
+	/**
+	 * 变更流程状态
+	 *
+	 * @param state     状态
+	 * @param processId 流程id
+	 */
+	@PostMapping("change-state")
+	@ApiOperation(value = "变更流程状态", notes = "传入state,processId", position = 2)
+	public R changeState(@RequestParam String state, @RequestParam String processId) {
+		String msg = flowEngineService.changeState(state, processId);
+		return R.success(msg);
+	}
+
+	/**
+	 * 删除部署流程
+	 *
+	 * @param deploymentIds 部署流程id集合
+	 */
+	@PostMapping("delete-deployment")
+	@ApiOperation(value = "删除部署流程", notes = "部署流程id集合", position = 3)
+	public R deleteDeployment(String deploymentIds) {
+		return R.status(flowEngineService.deleteDeployment(deploymentIds));
+	}
+
+	/**
+	 * 上传部署流程文件
+	 *
+	 * @param files    流程文件
+	 * @param category 类型
+	 */
+	@PostMapping("deploy-upload")
+	@ApiOperation(value = "上传部署流程文件", notes = "传入文件", position = 4)
+	public R deployUpload(@RequestParam List<MultipartFile> files, @RequestParam String category) {
+		return R.status(flowEngineService.deployUpload(files, category));
+	}
+
+	/**
+	 * 资源展示
+	 *
+	 * @param processId    流程id
+	 * @param instanceId   实例id
+	 * @param resourceType 资源类型
+	 * @param response     响应
+	 */
+	@GetMapping("resource")
+	@ApiOperation(value = "资源展示", notes = "传入processId,instanceId,resourceType", position = 4)
+	public void resource(String processId, String instanceId, String resourceType, HttpServletResponse response) throws Exception {
+		InputStream resourceAsStream = flowEngineService.resource(processId, instanceId, resourceType);
+		byte[] b = new byte[1024];
+		int len;
+		while ((len = resourceAsStream.read(b, 0, IntegerPool.INT_1024)) != -1) {
+			response.getOutputStream().write(b, 0, len);
+		}
+	}
+
+}

+ 86 - 0
src/main/java/org/springblade/flow/engine/controller/FlowModelController.java

@@ -0,0 +1,86 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.Map;
+
+/**
+ * 流程模型控制器
+ *
+ * @author Chill
+ */
+@RestController
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/model")
+@AllArgsConstructor
+@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
+public class FlowModelController {
+
+	private FlowEngineService flowEngineService;
+
+	/**
+	 * 分页
+	 */
+	@GetMapping("/list")
+	@ApiImplicitParams({
+		@ApiImplicitParam(name = "modelKey", value = "模型标识", paramType = "query", dataType = "string"),
+		@ApiImplicitParam(name = "name", value = "模型名称", paramType = "query", dataType = "string")
+	})
+	@ApiOperation(value = "分页", notes = "传入notice", position = 1)
+	public R<IPage<FlowModel>> list(@ApiIgnore @RequestParam Map<String, Object> flow, Query query) {
+		IPage<FlowModel> pages = flowEngineService.page(Condition.getPage(query), Condition.getQueryWrapper(flow, FlowModel.class));
+		return R.data(pages);
+	}
+
+	/**
+	 * 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperation(value = "删除", notes = "传入主键集合", position = 2)
+	public R remove(@ApiParam(value = "主键集合") @RequestParam String ids) {
+		boolean temp = flowEngineService.removeByIds(Func.toStrList(ids));
+		return R.status(temp);
+	}
+
+	/**
+	 * 部署
+	 */
+	@PostMapping("/deploy")
+	@ApiOperation(value = "部署", notes = "传入模型id和分类", position = 3)
+	public R deploy(@ApiParam(value = "模型id") @RequestParam String modelId, @ApiParam(value = "工作流分类") @RequestParam String category) {
+		boolean temp = flowEngineService.deployModel(modelId, category);
+		return R.status(temp);
+	}
+
+}

+ 187 - 0
src/main/java/org/springblade/flow/engine/controller/FlowProcessController.java

@@ -0,0 +1,187 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.controller;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.common.engine.impl.util.IoUtil;
+import org.flowable.engine.*;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.image.ProcessDiagramGenerator;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 流程通用控制器
+ *
+ * @author Chill
+ */
+@Slf4j
+@RestController
+@AllArgsConstructor
+@RequestMapping(AppConstant.APPLICATION_FLOW_NAME + "/process")
+public class FlowProcessController {
+
+	private static final String IMAGE_NAME = "image";
+	private static final String XML_NAME = "xml";
+	private static final Integer INT_1024 = 1024;
+
+	private RepositoryService repositoryService;
+	private RuntimeService runtimeService;
+	private HistoryService historyService;
+	private ProcessEngine processEngine;
+	private FlowEngineService flowEngineService;
+
+	/**
+	 * 获取流转历史列表
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param startActivityId   开始节点id
+	 * @param endActivityId     结束节点id
+	 */
+	@GetMapping(value = "history-flow-list")
+	public R<List<BladeFlow>> historyFlowList(@RequestParam String processInstanceId, String startActivityId, String endActivityId) {
+		return R.data(flowEngineService.historyFlowList(processInstanceId, startActivityId, endActivityId));
+	}
+
+	/**
+	 * 流程图展示
+	 *
+	 * @param processDefinitionId 流程id
+	 * @param processInstanceId   实例id
+	 * @param resourceType        资源类型
+	 * @param response            响应
+	 */
+	@GetMapping("resource-view")
+	public void resourceView(@RequestParam String processDefinitionId, String processInstanceId, @RequestParam(defaultValue = IMAGE_NAME) String resourceType, HttpServletResponse response) throws Exception {
+		if (StringUtil.isAllBlank(processDefinitionId, processInstanceId)) {
+			return;
+		}
+		if (StringUtil.isBlank(processDefinitionId)) {
+			ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = processInstance.getProcessDefinitionId();
+		}
+		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
+		String resourceName = "";
+		if (resourceType.equals(IMAGE_NAME)) {
+			resourceName = processDefinition.getDiagramResourceName();
+		} else if (resourceType.equals(XML_NAME)) {
+			resourceName = processDefinition.getResourceName();
+		}
+		InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
+		byte[] b = new byte[1024];
+		int len;
+		while ((len = resourceAsStream.read(b, 0, INT_1024)) != -1) {
+			response.getOutputStream().write(b, 0, len);
+		}
+	}
+
+	/**
+	 * 获取流程节点进程图
+	 *
+	 * @param processInstanceId   流程实例id
+	 * @param httpServletResponse http响应
+	 */
+	@GetMapping(value = "diagram-view")
+	public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
+		diagram(processInstanceId, httpServletResponse);
+	}
+
+	/**
+	 * 根据流程节点绘图
+	 *
+	 * @param processInstanceId   流程实例id
+	 * @param httpServletResponse http响应
+	 */
+	private void diagram(String processInstanceId, HttpServletResponse httpServletResponse) {
+		// 获得当前活动的节点
+		String processDefinitionId;
+		// 如果流程已经结束,则得到结束节点
+		if (this.isFinished(processInstanceId)) {
+			HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = pi.getProcessDefinitionId();
+		} else {
+			// 如果流程没有结束,则取当前活动节点
+			// 根据流程实例ID获得当前处于活动状态的ActivityId合集
+			ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+			processDefinitionId = pi.getProcessDefinitionId();
+		}
+		List<String> highLightedActivities = new ArrayList<>();
+
+		// 获得活动的节点
+		List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
+
+		for (HistoricActivityInstance tempActivity : highLightedActivityList) {
+			String activityId = tempActivity.getActivityId();
+			highLightedActivities.add(activityId);
+		}
+
+		List<String> flows = new ArrayList<>();
+		// 获取流程图
+		BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+		ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
+
+		ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
+		InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, flows, engConf.getActivityFontName(),
+			engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
+		OutputStream out = null;
+		byte[] buf = new byte[1024];
+		int length;
+		try {
+			out = httpServletResponse.getOutputStream();
+			while ((length = in.read(buf)) != -1) {
+				out.write(buf, 0, length);
+			}
+		} catch (IOException e) {
+			log.error("操作异常", e);
+		} finally {
+			IoUtil.closeSilently(out);
+			IoUtil.closeSilently(in);
+		}
+	}
+
+	/**
+	 * 是否已完结
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @return bool
+	 */
+	private boolean isFinished(String processInstanceId) {
+		return historyService.createHistoricProcessInstanceQuery().finished()
+			.processInstanceId(processInstanceId).count() > 0;
+	}
+
+
+}

+ 50 - 0
src/main/java/org/springblade/flow/engine/entity/FlowExecution.java

@@ -0,0 +1,50 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 运行实体类
+ *
+ * @author Chill
+ */
+@Data
+public class FlowExecution implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private String id;
+	private String name;
+	private String startUserId;
+	private String startUser;
+	private Date startTime;
+	private String taskDefinitionId;
+	private String taskDefinitionKey;
+	private String category;
+	private String categoryName;
+	private String processInstanceId;
+	private String processDefinitionId;
+	private String processDefinitionKey;
+	private String activityId;
+	private int suspensionState;
+	private String executionId;
+
+}

+ 57 - 0
src/main/java/org/springblade/flow/engine/entity/FlowModel.java

@@ -0,0 +1,57 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 流程模型
+ *
+ * @author Chill
+ */
+@Data
+@TableName("ACT_DE_MODEL")
+public class FlowModel implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public static final int MODEL_TYPE_BPMN = 0;
+	public static final int MODEL_TYPE_FORM = 2;
+	public static final int MODEL_TYPE_APP = 3;
+	public static final int MODEL_TYPE_DECISION_TABLE = 4;
+	public static final int MODEL_TYPE_CMMN = 5;
+
+	private String id;
+	private String name;
+	private String modelKey;
+	private String description;
+	private Date created;
+	private Date lastUpdated;
+	private String createdBy;
+	private String lastUpdatedBy;
+	private Integer version;
+	private String modelEditorJson;
+	private String modelComment;
+	private Integer modelType;
+	private String tenantId;
+	private byte[] thumbnail;
+
+}

+ 59 - 0
src/main/java/org/springblade/flow/engine/entity/FlowProcess.java

@@ -0,0 +1,59 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.entity;
+
+import lombok.Data;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
+import org.springblade.flow.engine.utils.FlowCache;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * FlowProcess
+ *
+ * @author Chill
+ */
+@Data
+public class FlowProcess implements Serializable {
+
+	private String id;
+	private String name;
+	private String key;
+	private String category;
+	private String categoryName;
+	private Integer version;
+	private String deploymentId;
+	private String resourceName;
+	private String diagramResourceName;
+	private Integer suspensionState;
+	private Date deploymentTime;
+
+	public FlowProcess(ProcessDefinitionEntityImpl entity) {
+		this.id = entity.getId();
+		this.name = entity.getName();
+		this.key = entity.getKey();
+		this.category = entity.getCategory();
+		this.categoryName = FlowCache.getCategoryName(entity.getCategory());
+		this.version = entity.getVersion();
+		this.deploymentId = entity.getDeploymentId();
+		this.resourceName = entity.getResourceName();
+		this.diagramResourceName = entity.getDiagramResourceName();
+		this.suspensionState = entity.getSuspensionState();
+	}
+
+}

+ 46 - 0
src/main/java/org/springblade/flow/engine/mapper/FlowMapper.java

@@ -0,0 +1,46 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.flow.engine.entity.FlowModel;
+
+import java.util.List;
+
+/**
+ * FlowMapper.
+ *
+ * @author Chill
+ */
+public interface FlowMapper extends BaseMapper<FlowModel> {
+
+	/**
+	 * 自定义分页
+	 * @param page
+	 * @param flowModel
+	 * @return
+	 */
+	List<FlowModel> selectFlowPage(IPage page, FlowModel flowModel);
+
+	/**
+	 * 获取模型
+	 * @param parentModelId
+	 * @return
+	 */
+	List<FlowModel> findByParentModelId(String parentModelId);
+}

+ 53 - 0
src/main/java/org/springblade/flow/engine/mapper/FlowMapper.xml

@@ -0,0 +1,53 @@
+<?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.springblade.flow.engine.mapper.FlowMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="flowModelResultMap" type="org.springblade.flow.engine.entity.FlowModel">
+        <result column="id" property="id"/>
+        <result column="name" property="name"/>
+        <result column="model_key" property="modelKey"/>
+        <result column="description" property="description"/>
+        <result column="model_comment" property="modelComment"/>
+        <result column="created" property="created"/>
+        <result column="created_by" property="createdBy"/>
+        <result column="last_updated" property="lastUpdated"/>
+        <result column="last_updated_by" property="lastUpdatedBy"/>
+        <result column="version" property="version"/>
+        <result column="model_editor_json" property="modelEditorJson"/>
+        <result column="thumbnail" property="thumbnail"/>
+        <result column="model_type" property="modelType"/>
+        <result column="tenant_id" property="tenantId"/>
+    </resultMap>
+
+    <select id="selectFlowPage" resultMap="flowModelResultMap">
+        SELECT
+            a.id,
+            a.name,
+            a.model_key,
+            a.description,
+            a.model_comment,
+            a.created,
+            a.created_by,
+            a.last_updated,
+            a.last_updated_by,
+            a.version,
+            a.model_editor_json,
+            a.thumbnail,
+            a.model_type,
+            a.tenant_id
+        FROM
+            ACT_DE_MODEL a
+        WHERE
+            1 = 1
+        ORDER BY
+            a.created DESC
+    </select>
+
+    <select id="findByParentModelId" parameterType="string" resultMap="flowModelResultMap">
+        select model.* from ACT_DE_MODEL_RELATION modelrelation
+                                inner join ACT_DE_MODEL model on modelrelation.model_id = model.id
+        where modelrelation.parent_model_id = #{_parameter}
+    </select>
+
+</mapper>

+ 128 - 0
src/main/java/org/springblade/flow/engine/service/FlowEngineService.java

@@ -0,0 +1,128 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * FlowEngineService
+ *
+ * @author Chill
+ */
+public interface FlowEngineService extends IService<FlowModel> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page      分页工具
+	 * @param flowModel 流程模型
+	 * @return
+	 */
+	IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel);
+
+	/**
+	 * 流程管理列表
+	 *
+	 * @param page     分页工具
+	 * @param category 分类
+	 * @return
+	 */
+	IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category);
+
+	/**
+	 * 流程管理列表
+	 *
+	 * @param page                 分页工具
+	 * @param processInstanceId    流程实例id
+	 * @param processDefinitionKey 流程key
+	 * @return
+	 */
+	IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey);
+
+	/**
+	 * 获取流转历史列表
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param startActivityId   开始节点id
+	 * @param endActivityId     结束节点id
+	 * @return
+	 */
+	List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId);
+
+	/**
+	 * 变更流程状态
+	 *
+	 * @param state     状态
+	 * @param processId 流程ID
+	 * @return
+	 */
+	String changeState(String state, String processId);
+
+	/**
+	 * 删除部署流程
+	 *
+	 * @param deploymentIds 部署流程id集合
+	 * @return
+	 */
+	boolean deleteDeployment(String deploymentIds);
+
+	/**
+	 * 资源展示
+	 *
+	 * @param processId    流程ID
+	 * @param instanceId   流程实例ID
+	 * @param resourceType 资源类型(xml|image)
+	 * @return
+	 */
+	InputStream resource(String processId, String instanceId, String resourceType);
+
+	/**
+	 * 上传部署流程
+	 *
+	 * @param files    流程配置文件
+	 * @param category 流程分类
+	 * @return
+	 */
+	boolean deployUpload(List<MultipartFile> files, String category);
+
+	/**
+	 * 部署流程
+	 *
+	 * @param modelId  模型id
+	 * @param category 分类
+	 * @return
+	 */
+	boolean deployModel(String modelId, String category);
+
+	/**
+	 * 删除流程实例
+	 *
+	 * @param processInstanceId 流程实例id
+	 * @param deleteReason      删除原因
+	 * @return
+	 */
+	boolean deleteProcessInstance(String processInstanceId, String deleteReason);
+}

+ 378 - 0
src/main/java/org/springblade/flow/engine/service/impl/FlowEngineServiceImpl.java

@@ -0,0 +1,378 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.Process;
+import org.flowable.editor.language.json.converter.BpmnJsonConverter;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
+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.runtime.ProcessInstanceQuery;
+import org.flowable.engine.task.Comment;
+import org.springblade.common.cache.UserCache;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.flow.core.entity.BladeFlow;
+import org.springblade.flow.core.utils.TaskUtil;
+import org.springblade.flow.engine.constant.FlowConstant;
+import org.springblade.flow.engine.entity.FlowExecution;
+import org.springblade.flow.engine.entity.FlowModel;
+import org.springblade.flow.engine.entity.FlowProcess;
+import org.springblade.flow.engine.mapper.FlowMapper;
+import org.springblade.flow.engine.service.FlowEngineService;
+import org.springblade.flow.engine.utils.FlowCache;
+import org.springblade.modules.system.entity.User;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * 工作流服务实现类
+ *
+ * @author Chill
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class FlowEngineServiceImpl extends ServiceImpl<FlowMapper, FlowModel> implements FlowEngineService {
+	private static final String IMAGE_NAME = "image";
+	private static final String XML_NAME = "xml";
+	private static BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
+	private static BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
+	private ObjectMapper objectMapper;
+	private RepositoryService repositoryService;
+	private RuntimeService runtimeService;
+	private HistoryService historyService;
+	private TaskService taskService;
+
+	@Override
+	public IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel) {
+		return page.setRecords(baseMapper.selectFlowPage(page, flowModel));
+	}
+
+	@Override
+	public IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category) {
+		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion().orderByProcessDefinitionKey().asc();
+		if (StringUtils.isNotEmpty(category)) {
+			processDefinitionQuery.processDefinitionCategory(category);
+		}
+		List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage(Func.toInt(page.getCurrent() - 1), Func.toInt(page.getSize() * page.getCurrent()));
+		List<FlowProcess> flowProcessList = new ArrayList<>();
+		processDefinitionList.forEach(processDefinition -> {
+			String deploymentId = processDefinition.getDeploymentId();
+			Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
+			FlowProcess flowProcess = new FlowProcess((ProcessDefinitionEntityImpl) processDefinition);
+			flowProcess.setDeploymentTime(deployment.getDeploymentTime());
+			flowProcessList.add(flowProcess);
+		});
+		page.setTotal(processDefinitionQuery.count());
+		page.setRecords(flowProcessList);
+		return page;
+	}
+
+	@Override
+	public IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey) {
+		ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
+		if (StringUtil.isNotBlank(processInstanceId)) {
+			processInstanceQuery.processInstanceId(processInstanceId);
+		}
+		if (StringUtil.isNotBlank(processDefinitionKey)) {
+			processInstanceQuery.processDefinitionKey(processDefinitionKey);
+		}
+		List<FlowExecution> flowList = new ArrayList<>();
+		List<ProcessInstance> procInsList = processInstanceQuery.listPage(Func.toInt(page.getCurrent() - 1), Func.toInt(page.getSize() * page.getCurrent()));
+		procInsList.forEach(processInstance -> {
+			ExecutionEntityImpl execution = (ExecutionEntityImpl) processInstance;
+			FlowExecution flowExecution = new FlowExecution();
+			flowExecution.setId(execution.getId());
+			flowExecution.setName(execution.getName());
+			flowExecution.setStartUserId(execution.getStartUserId());
+			flowExecution.setStartUser(UserCache.getUserByTaskUser(execution.getStartUserId()).getName());
+			flowExecution.setStartTime(execution.getStartTime());
+			flowExecution.setExecutionId(execution.getId());
+			flowExecution.setProcessInstanceId(execution.getProcessInstanceId());
+			flowExecution.setProcessDefinitionId(execution.getProcessDefinitionId());
+			flowExecution.setProcessDefinitionKey(execution.getProcessDefinitionKey());
+			flowExecution.setSuspensionState(execution.getSuspensionState());
+			ProcessDefinition processDefinition = FlowCache.getProcessDefinition(execution.getProcessDefinitionId());
+			flowExecution.setCategory(processDefinition.getCategory());
+			flowExecution.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
+			flowList.add(flowExecution);
+		});
+		page.setTotal(processInstanceQuery.count());
+		page.setRecords(flowList);
+		return page;
+	}
+
+	@Override
+	public List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId) {
+		List<BladeFlow> flowList = new LinkedList<>();
+		List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list();
+		boolean start = false;
+		Map<String, Integer> activityMap = new HashMap<>(16);
+		for (int i = 0; i < historicActivityInstanceList.size(); i++) {
+			HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
+			// 过滤开始节点前的节点
+			if (StringUtil.isNotBlank(startActivityId) && startActivityId.equals(historicActivityInstance.getActivityId())) {
+				start = true;
+			}
+			if (StringUtil.isNotBlank(startActivityId) && !start) {
+				continue;
+			}
+			// 显示开始节点和结束节点,并且执行人不为空的任务
+			if (StringUtils.isNotBlank(historicActivityInstance.getAssignee())
+				|| FlowConstant.START_EVENT.equals(historicActivityInstance.getActivityType())
+				|| FlowConstant.END_EVENT.equals(historicActivityInstance.getActivityType())) {
+				// 给节点增加序号
+				Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
+				if (activityNum == null) {
+					activityMap.put(historicActivityInstance.getActivityId(), activityMap.size());
+				}
+				BladeFlow flow = new BladeFlow();
+				flow.setHistoryActivityName(historicActivityInstance.getActivityName());
+				flow.setCreateTime(historicActivityInstance.getStartTime());
+				flow.setEndTime(historicActivityInstance.getEndTime());
+				String durationTime = DateUtil.secondToTime(Func.toLong(historicActivityInstance.getDurationInMillis(), 0L) / 1000);
+				flow.setHistoryActivityDurationTime(durationTime);
+				// 获取流程发起人名称
+				if (FlowConstant.START_EVENT.equals(historicActivityInstance.getActivityType())) {
+					List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).orderByProcessInstanceStartTime().asc().list();
+					if (processInstanceList.size() > 0) {
+						if (StringUtil.isNotBlank(processInstanceList.get(0).getStartUserId())) {
+							String taskUser = processInstanceList.get(0).getStartUserId();
+							User user = UserCache.getUser(TaskUtil.getUserId(taskUser));
+							if (user != null) {
+								flow.setAssignee(historicActivityInstance.getAssignee());
+								flow.setAssigneeName(user.getName());
+							}
+						}
+					}
+				}
+				// 获取任务执行人名称
+				if (StringUtil.isNotBlank(historicActivityInstance.getAssignee())) {
+					User user = UserCache.getUser(TaskUtil.getUserId(historicActivityInstance.getAssignee()));
+					if (user != null) {
+						flow.setAssignee(historicActivityInstance.getAssignee());
+						flow.setAssigneeName(user.getName());
+					}
+				}
+				// 获取意见评论内容
+				if (StringUtil.isNotBlank(historicActivityInstance.getTaskId())) {
+					List<Comment> commentList = taskService.getTaskComments(historicActivityInstance.getTaskId());
+					if (commentList.size() > 0) {
+						flow.setComment(commentList.get(0).getFullMessage());
+					}
+				}
+				flowList.add(flow);
+			}
+			// 过滤结束节点后的节点
+			if (StringUtils.isNotBlank(endActivityId) && endActivityId.equals(historicActivityInstance.getActivityId())) {
+				boolean temp = false;
+				Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
+				// 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在
+				for (int j = i + 1; j < historicActivityInstanceList.size(); j++) {
+					HistoricActivityInstance hi = historicActivityInstanceList.get(j);
+					Integer activityNumA = activityMap.get(hi.getActivityId());
+					boolean numberTemp = activityNumA != null && activityNumA < activityNum;
+					boolean equalsTemp = StringUtils.equals(hi.getActivityId(), historicActivityInstance.getActivityId());
+					if (numberTemp || equalsTemp) {
+						temp = true;
+					}
+				}
+				if (!temp) {
+					break;
+				}
+			}
+		}
+		return flowList;
+	}
+
+	@Override
+	public String changeState(String state, String processId) {
+		if (state.equals(FlowConstant.ACTIVE)) {
+			repositoryService.activateProcessDefinitionById(processId, true, null);
+			return StringUtil.format("激活ID为 [{}] 的流程成功", processId);
+		} else if (state.equals(FlowConstant.SUSPEND)) {
+			repositoryService.suspendProcessDefinitionById(processId, true, null);
+			return StringUtil.format("挂起ID为 [{}] 的流程成功", processId);
+		} else {
+			return "暂无流程变更";
+		}
+	}
+
+	@Override
+	public boolean deleteDeployment(String deploymentIds) {
+		Func.toStrList(deploymentIds).forEach(deploymentId -> repositoryService.deleteDeployment(deploymentId, true));
+		return true;
+	}
+
+	@Override
+	public InputStream resource(String processId, String instanceId, String resourceType) {
+		if (StringUtils.isBlank(processId)) {
+			ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
+			processId = processInstance.getProcessDefinitionId();
+		}
+		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processId).singleResult();
+		String resourceName = StringPool.EMPTY;
+		if (resourceType.equals(IMAGE_NAME)) {
+			resourceName = processDefinition.getDiagramResourceName();
+		} else if (resourceType.equals(XML_NAME)) {
+			resourceName = processDefinition.getResourceName();
+		}
+		return repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
+	}
+
+	@Override
+	public boolean deployUpload(List<MultipartFile> files, String category) {
+		files.forEach(file -> {
+			try {
+				String fileName = file.getOriginalFilename();
+				InputStream fileInputStream = file.getInputStream();
+				Deployment deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
+				deploy(deployment, category);
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		});
+		return true;
+	}
+
+	@Override
+	public boolean deployModel(String modelId, String category) {
+		FlowModel model = this.getById(modelId);
+		if (model == null) {
+			throw new ServiceException("No model found with the given id: " + modelId);
+		}
+		byte[] bytes = getBpmnXML(model);
+		String processName = model.getName();
+		if (!StringUtil.endsWithIgnoreCase(processName, FlowConstant.SUFFIX)) {
+			processName += FlowConstant.SUFFIX;
+		}
+		Deployment deployment = repositoryService.createDeployment().addBytes(processName, bytes).name(model.getName()).key(model.getModelKey()).deploy();
+		return deploy(deployment, category);
+	}
+
+	@Override
+	public boolean deleteProcessInstance(String processInstanceId, String deleteReason) {
+		runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
+		return true;
+	}
+
+	private boolean deploy(Deployment deployment, String category) {
+		log.debug("流程部署--------deploy:  " + deployment + "  分类---------->" + category);
+		List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
+		StringBuilder logBuilder = new StringBuilder(500);
+		List<Object> logArgs = new ArrayList<>();
+		// 设置流程分类
+		for (ProcessDefinition processDefinition : list) {
+			if (StringUtil.isNotBlank(category)) {
+				repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
+			}
+			logBuilder.append("部署成功,流程ID={} \n");
+			logArgs.add(processDefinition.getId());
+		}
+		if (list.size() == 0) {
+			throw new ServiceException("部署失败,未找到流程");
+		} else {
+			log.info(logBuilder.toString(), logArgs.toArray());
+			return true;
+		}
+	}
+
+	private byte[] getBpmnXML(FlowModel model) {
+		BpmnModel bpmnModel = getBpmnModel(model);
+		return getBpmnXML(bpmnModel);
+	}
+
+	private byte[] getBpmnXML(BpmnModel bpmnModel) {
+		for (Process process : bpmnModel.getProcesses()) {
+			if (StringUtils.isNotEmpty(process.getId())) {
+				char firstCharacter = process.getId().charAt(0);
+				if (Character.isDigit(firstCharacter)) {
+					process.setId("a" + process.getId());
+				}
+			}
+		}
+		return bpmnXMLConverter.convertToXML(bpmnModel);
+	}
+
+	private BpmnModel getBpmnModel(FlowModel model) {
+		BpmnModel bpmnModel;
+		try {
+			Map<String, FlowModel> formMap = new HashMap<>(16);
+			Map<String, FlowModel> decisionTableMap = new HashMap<>(16);
+
+			List<FlowModel> referencedModels = baseMapper.findByParentModelId(model.getId());
+			for (FlowModel childModel : referencedModels) {
+				if (FlowModel.MODEL_TYPE_FORM == childModel.getModelType()) {
+					formMap.put(childModel.getId(), childModel);
+
+				} else if (FlowModel.MODEL_TYPE_DECISION_TABLE == childModel.getModelType()) {
+					decisionTableMap.put(childModel.getId(), childModel);
+				}
+			}
+			bpmnModel = getBpmnModel(model, formMap, decisionTableMap);
+		} catch (Exception e) {
+			log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
+			throw new ServiceException("Could not generate BPMN 2.0 model");
+		}
+		return bpmnModel;
+	}
+
+	private BpmnModel getBpmnModel(FlowModel model, Map<String, FlowModel> formMap, Map<String, FlowModel> decisionTableMap) {
+		try {
+			ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
+			Map<String, String> formKeyMap = new HashMap<>(16);
+			for (FlowModel formModel : formMap.values()) {
+				formKeyMap.put(formModel.getId(), formModel.getModelKey());
+			}
+			Map<String, String> decisionTableKeyMap = new HashMap<>(16);
+			for (FlowModel decisionTableModel : decisionTableMap.values()) {
+				decisionTableKeyMap.put(decisionTableModel.getId(), decisionTableModel.getModelKey());
+			}
+			return bpmnJsonConverter.convertToBpmnModel(editorJsonNode, formKeyMap, decisionTableKeyMap);
+		} catch (Exception e) {
+			log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
+			throw new ServiceException("Could not generate BPMN 2.0 model");
+		}
+	}
+
+}

+ 72 - 0
src/main/java/org/springblade/flow/engine/utils/FlowCache.java

@@ -0,0 +1,72 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.flow.engine.utils;
+
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springblade.common.cache.DictCache;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.core.tool.utils.StringPool;
+
+/**
+ * 流程缓存
+ *
+ * @author Chill
+ */
+public class FlowCache {
+
+	private static final String FLOW_CACHE = "flow:process";
+	private static final String FLOW_CACHE_ID_ = "definition:id";
+	private static RepositoryService repositoryService;
+
+	static {
+		repositoryService = SpringUtil.getBean(RepositoryService.class);
+	}
+
+	/**
+	 * 获得流程定义对象
+	 *
+	 * @param processDefinitionId 流程对象id
+	 * @return
+	 */
+	public static ProcessDefinition getProcessDefinition(String processDefinitionId) {
+		ProcessDefinition processDefinition = CacheUtil.get(FLOW_CACHE, FLOW_CACHE_ID_ + processDefinitionId, ProcessDefinition.class);
+		if (Func.isEmpty(processDefinition)) {
+			processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
+			if (Func.isNotEmpty(processDefinition)) {
+				CacheUtil.put(FLOW_CACHE, FLOW_CACHE_ID_ + processDefinitionId, processDefinition);
+			}
+		}
+		return processDefinition;
+	}
+
+	/**
+	 * 获取流程类型名
+	 *
+	 * @param category 流程类型
+	 * @return
+	 */
+	public static String getCategoryName(String category) {
+		if (Func.isEmpty(category)) {
+			return StringPool.EMPTY;
+		}
+		return DictCache.getValue(category.split(StringPool.UNDERSCORE)[0], Func.toInt(category.split(StringPool.UNDERSCORE)[1]));
+	}
+
+}

+ 2 - 1
src/main/java/org/springblade/modules/auth/AuthController.java

@@ -21,6 +21,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiSort;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.launch.constant.TokenConstant;
 import org.springblade.core.log.annotation.ApiLog;
 import org.springblade.core.secure.utils.SecureUtil;
@@ -46,7 +47,7 @@ import java.util.Map;
  */
 @RestController
 @AllArgsConstructor
-@RequestMapping("blade-auth")
+@RequestMapping(AppConstant.APPLICATION_AUTH_NAME)
 @ApiSort(1)
 @Api(value = "用户授权认证", tags = "授权接口")
 public class AuthController {

+ 2 - 1
src/main/java/org/springblade/modules/desk/controller/DashBoardController.java

@@ -3,6 +3,7 @@ package org.springblade.modules.desk.controller;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.support.Kv;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -22,7 +23,7 @@ import java.util.Map;
  */
 @ApiIgnore
 @RestController
-@RequestMapping("/blade-desk/dashboard")
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME + "/dashboard")
 @AllArgsConstructor
 @Api(value = "首页", tags = "首页")
 public class DashBoardController {

+ 2 - 1
src/main/java/org/springblade/modules/desk/controller/NoticeController.java

@@ -21,6 +21,7 @@ import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.common.cache.CacheNames;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
@@ -44,7 +45,7 @@ import java.util.Map;
  * @author Chill
  */
 @RestController
-@RequestMapping("/blade-desk/notice")
+@RequestMapping(AppConstant.APPLICATION_DESK_NAME + "/notice")
 @AllArgsConstructor
 @ApiSort(2)
 @Api(value = "用户博客", tags = "博客接口")

+ 2 - 1
src/main/java/org/springblade/modules/develop/controller/CodeController.java

@@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.annotation.PreAuth;
@@ -44,7 +45,7 @@ import java.util.Map;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-develop/code")
+@RequestMapping(AppConstant.APPLICATION_DEVELOP_NAME + "/code")
 @Api(value = "代码生成", tags = "代码生成")
 @PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
 public class CodeController extends BladeController {

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/AuthClientController.java

@@ -22,6 +22,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.annotation.PreAuth;
@@ -42,7 +43,7 @@ import javax.validation.Valid;
  */
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/client")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/client")
 @ApiIgnore
 @Api(value = "应用管理", tags = "接口")
 @PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/DeptController.java

@@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.annotation.PreAuth;
@@ -46,7 +47,7 @@ import java.util.Map;
  */
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/dept")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/dept")
 @ApiIgnore
 @Api(value = "部门", tags = "部门")
 @PreAuth(RoleConstant.HAS_ROLE_ADMIN)

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/DictController.java

@@ -19,6 +19,7 @@ package org.springblade.modules.system.controller;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.node.INode;
@@ -45,7 +46,7 @@ import static org.springblade.core.cache.constant.CacheConstant.DICT_VALUE;
  */
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/dict")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/dict")
 @ApiIgnore
 @Api(value = "字典", tags = "字典")
 public class DictController extends BladeController {

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/LogApiController.java

@@ -20,6 +20,7 @@ package org.springblade.modules.system.controller;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.log.model.LogApi;
 import org.springblade.core.log.model.LogApiVo;
 import org.springblade.core.mp.support.Condition;
@@ -46,7 +47,7 @@ import java.util.stream.Collectors;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-log/api")
+@RequestMapping(AppConstant.APPLICATION_LOG_NAME + "/api")
 public class LogApiController {
 
 	private ILogApiService logService;

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/LogErrorController.java

@@ -20,6 +20,7 @@ package org.springblade.modules.system.controller;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.log.model.LogError;
 import org.springblade.core.log.model.LogErrorVo;
 import org.springblade.core.mp.support.Condition;
@@ -46,7 +47,7 @@ import java.util.stream.Collectors;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-log/error")
+@RequestMapping(AppConstant.APPLICATION_LOG_NAME + "/error")
 public class LogErrorController {
 
 	private ILogErrorService errorLogService;

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/LogUsualController.java

@@ -20,6 +20,7 @@ package org.springblade.modules.system.controller;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.log.model.LogUsual;
 import org.springblade.core.log.model.LogUsualVo;
 import org.springblade.core.mp.support.Condition;
@@ -46,7 +47,7 @@ import java.util.stream.Collectors;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-log/usual")
+@RequestMapping(AppConstant.APPLICATION_LOG_NAME + "/usual")
 public class LogUsualController {
 
 	private ILogUsualService logService;

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/MenuController.java

@@ -19,6 +19,7 @@ package org.springblade.modules.system.controller;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.annotation.PreAuth;
@@ -49,7 +50,7 @@ import static org.springblade.core.cache.constant.CacheConstant.AUTH_ROUTES;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/menu")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/menu")
 @Api(value = "菜单", tags = "菜单")
 public class MenuController extends BladeController {
 

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/ParamController.java

@@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
@@ -43,7 +44,7 @@ import java.util.Map;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/param")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/param")
 @Api(value = "参数管理", tags = "接口")
 public class ParamController extends BladeController {
 

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/RoleController.java

@@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.annotation.PreAuth;
@@ -47,7 +48,7 @@ import java.util.Map;
 @ApiIgnore
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/role")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/role")
 @Api(value = "角色", tags = "角色")
 @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
 public class RoleController extends BladeController {

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/TenantController.java

@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.AllArgsConstructor;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.BladeUser;
@@ -43,7 +44,7 @@ import java.util.Map;
  */
 @RestController
 @AllArgsConstructor
-@RequestMapping("/blade-system/tenant")
+@RequestMapping(AppConstant.APPLICATION_SYSTEM_NAME + "/tenant")
 @ApiIgnore
 @Api(value = "租户管理", tags = "接口")
 public class TenantController extends BladeController {

+ 2 - 1
src/main/java/org/springblade/modules/system/controller/UserController.java

@@ -24,6 +24,7 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
+import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.secure.BladeUser;
@@ -51,7 +52,7 @@ import java.util.Map;
  */
 @ApiIgnore
 @RestController
-@RequestMapping("blade-user")
+@RequestMapping(AppConstant.APPLICATION_USER_NAME)
 @AllArgsConstructor
 public class UserController {
 

+ 1 - 1
src/main/resources/application-dev.yml

@@ -12,7 +12,7 @@ spring:
     #  nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
     #  commandTimeout: 5000
   datasource:
-    url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
+    url: jdbc:mysql://localhost:3306/bladex_boot?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
     username: root
     password: root
     driver-class-name: com.mysql.cj.jdbc.Driver

+ 1 - 1
src/main/resources/application-prod.yml

@@ -12,7 +12,7 @@ spring:
     #  nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
     #  commandTimeout: 5000
   datasource:
-    url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
+    url: jdbc:mysql://localhost:3306/bladex_boot?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
     username: root
     password: root
     driver-class-name: com.mysql.cj.jdbc.Driver

+ 1 - 1
src/main/resources/application-test.yml

@@ -12,7 +12,7 @@ spring:
     #  nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
     #  commandTimeout: 5000
   datasource:
-    url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
+    url: jdbc:mysql://localhost:3306/bladex_boot?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
     username: root
     password: root
     driver-class-name: com.mysql.cj.jdbc.Driver

+ 123 - 0
src/main/resources/processes/LeaveProcess.bpmn20.xml

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
+    <process id="Leave" name="请假流程" isExecutable="true">
+        <documentation>请假流程</documentation>
+        <startEvent id="start" name="开始" flowable:initiator="applyUser"></startEvent>
+        <userTask id="hrTask" name="人事审批" flowable:assignee="${taskUser}">
+            <extensionElements>
+                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
+            </extensionElements>
+        </userTask>
+        <exclusiveGateway id="judgeTask"></exclusiveGateway>
+        <userTask id="managerTak" name="经理审批" flowable:candidateGroups="manager"></userTask>
+        <userTask id="bossTask" name="老板审批" flowable:candidateGroups="boss"></userTask>
+        <endEvent id="end" name="结束"></endEvent>
+        <sequenceFlow id="flow1" sourceRef="start" targetRef="hrTask"></sequenceFlow>
+        <sequenceFlow id="managerPassFlow" name="通过" sourceRef="managerTak" targetRef="end">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+        </sequenceFlow>
+        <userTask id="userTask" name="调整申请" flowable:assignee="${applyUser}">
+            <extensionElements>
+                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
+            </extensionElements>
+        </userTask>
+        <sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="judgeMore" name="大于3天" sourceRef="judgeTask" targetRef="bossTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days > 3}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="managerNotPassFlow" name="驳回" sourceRef="managerTak" targetRef="userTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="userTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="hrPassFlow" name="同意" sourceRef="hrTask" targetRef="judgeTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="hrNotPassFlow" name="驳回" sourceRef="hrTask" targetRef="userTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="judgeLess" name="小于3天" sourceRef="judgeTask" targetRef="managerTak">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <= 3}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="userPassFlow" name="重新申请" sourceRef="userTask" targetRef="hrTask">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
+        </sequenceFlow>
+        <sequenceFlow id="userNotPassFlow" name="关闭申请" sourceRef="userTask" targetRef="end">
+            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
+        </sequenceFlow>
+    </process>
+    <bpmndi:BPMNDiagram id="BPMNDiagram_Leave">
+        <bpmndi:BPMNPlane bpmnElement="Leave" id="BPMNPlane_Leave">
+            <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
+                <omgdc:Bounds height="30.0" width="30.0" x="300.0" y="135.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="hrTask" id="BPMNShape_hrTask">
+                <omgdc:Bounds height="80.0" width="100.0" x="360.0" y="165.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
+                <omgdc:Bounds height="40.0" width="40.0" x="255.0" y="300.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="managerTak" id="BPMNShape_managerTak">
+                <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="75.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
+                <omgdc:Bounds height="80.0" width="100.0" x="450.0" y="420.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
+                <omgdc:Bounds height="28.0" width="28.0" x="705.0" y="390.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNShape bpmnElement="userTask" id="BPMNShape_userTask">
+                <omgdc:Bounds height="80.0" width="100.0" x="510.0" y="270.0"></omgdc:Bounds>
+            </bpmndi:BPMNShape>
+            <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+                <omgdi:waypoint x="327.9390183144677" y="157.4917313275668"></omgdi:waypoint>
+                <omgdi:waypoint x="360.0" y="176.05263157894737"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="hrPassFlow" id="BPMNEdge_hrPassFlow">
+                <omgdi:waypoint x="363.04347826086956" y="244.95000000000002"></omgdi:waypoint>
+                <omgdi:waypoint x="285.77299999999997" y="310.79999999999995"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="hrNotPassFlow" id="BPMNEdge_hrNotPassFlow">
+                <omgdi:waypoint x="459.95" y="236.21875000000006"></omgdi:waypoint>
+                <omgdi:waypoint x="513.9794844818516" y="270.0"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
+                <omgdi:waypoint x="274.3359375" y="300.66397214564284"></omgdi:waypoint>
+                <omgdi:waypoint x="274.3359375" y="115.0"></omgdi:waypoint>
+                <omgdi:waypoint x="554.9999999999982" y="115.0"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="userPassFlow" id="BPMNEdge_userPassFlow">
+                <omgdi:waypoint x="510.0" y="310.0"></omgdi:waypoint>
+                <omgdi:waypoint x="411.0" y="310.0"></omgdi:waypoint>
+                <omgdi:waypoint x="411.0" y="244.95000000000002"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
+                <omgdi:waypoint x="549.9499999999998" y="447.2146118721461"></omgdi:waypoint>
+                <omgdi:waypoint x="705.4331577666419" y="407.4567570622598"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
+                <omgdi:waypoint x="287.29730895645025" y="327.65205479452055"></omgdi:waypoint>
+                <omgdi:waypoint x="450.0" y="428.8888888888889"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="managerPassFlow" id="BPMNEdge_managerPassFlow">
+                <omgdi:waypoint x="620.7588235294118" y="154.95"></omgdi:waypoint>
+                <omgdi:waypoint x="713.8613704477151" y="390.96328050279476"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="userNotPassFlow" id="BPMNEdge_userNotPassFlow">
+                <omgdi:waypoint x="609.95" y="339.5301886792453"></omgdi:waypoint>
+                <omgdi:waypoint x="706.9383699359797" y="396.87411962686997"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
+                <omgdi:waypoint x="515.98" y="420.0"></omgdi:waypoint>
+                <omgdi:waypoint x="544.0" y="349.95000000000005"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+            <bpmndi:BPMNEdge bpmnElement="managerNotPassFlow" id="BPMNEdge_managerNotPassFlow">
+                <omgdi:waypoint x="595.438344721373" y="154.95"></omgdi:waypoint>
+                <omgdi:waypoint x="567.9366337262223" y="270.0"></omgdi:waypoint>
+            </bpmndi:BPMNEdge>
+        </bpmndi:BPMNPlane>
+    </bpmndi:BPMNDiagram>
+</definitions>

+ 1 - 1
src/test/java/org/springblade/test/BladeTest.java

@@ -12,7 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
  * @author Chill
  */
 @SpringBootTest
-@BladeBootTest("blade-runner")
+@BladeBootTest(appName = "blade-runner", profile = "test")
 @RunWith(BladeSpringRunner.class)
 public class BladeTest {
 

Some files were not shown because too many files changed in this diff