目录
可视化UML工作流系统
7.1 返回值与配置工具类
自定义类名 | 类的描述 |
---|---|
GlobalConfig | 枚举与静态常量 |
AjaxResponse | 数据返回给前端的处理 |
PathMapping | 路径映射,发布不同操作系统路径不同 |
7.1.1 枚举与静态常量
public class GlobalConfig {
public static final Boolean Test = true;
@Getter
@AllArgsConstructor
public enum ResponseCode {
SUCCESS(0, "成功"),
ERROR(1, "错误");
private final int code;
private final String desc;
}
}
7.1.2 数据返回给前端的处理
@Getter
@Setter
@AllArgsConstructor
public class AjaxResponse {
private Integer status;
private String msg;
private Object obj;
public static AjaxResponse AjaxData(Integer status, String msg, Object obj) {
return new AjaxResponse(status, msg, obj);
}
}
7.2 登录接口
有了上面两个工具类,接下来就改造登录成功与失败的返回值。
7.2.1 改造登录成功处理
@Component("loginSuccessHandler")
@RequiredArgsConstructor
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功1");
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Authentication authentication)
throws IOException, ServletException {
logger.info("登录成功2");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(
AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
authentication.getName()
)));
}
}
7.2.2 改造登录失败处理
@Component("loginFailureHandler")
@RequiredArgsConstructor
public class LoginFailureHandler implements AuthenticationFailureHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e)
throws IOException, ServletException {
logger.info("登录失败");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(
AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
GlobalConfig.ResponseCode.ERROR.getDesc(),
"登录失败:"+e.getMessage()
)));
}
}
7.3 流程定义接口
7.3.1 获取流程定义列表
@RestController
@RequestMapping("/processDefinition")
@RequiredArgsConstructor
public class ProcessDefinitionController {
private final RepositoryService repositoryService;
/**
* 获取流程定义列表
* @return AjaxResponse
*/
@GetMapping(value = "/getDefinitions")
public AjaxResponse getDefinitions() {
try {
List<HashMap<String, Object>> listMap = new ArrayList<>();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
// Lambda 升序排列
list.sort((y, x) -> x.getVersion() - y.getVersion());
repositoryService.createProcessDefinitionQuery().list().forEach(processDefinition -> {
// 每次创建新 hashmap
HashMap<String, Object> hashMap = new HashMap<>(16);
//System.out.println("流程定义ID:"+processDefinition.getId());
hashMap.put("processDefinitionID", processDefinition.getId());
hashMap.put("name", processDefinition.getName());
hashMap.put("key", processDefinition.getKey());
hashMap.put("resourceName", processDefinition.getResourceName());
hashMap.put("deploymentID", processDefinition.getDeploymentId());
hashMap.put("version", processDefinition.getVersion());
listMap.add(hashMap);
});
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap
);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"获取流程定义失败",
e.toString()
);
}
}
}
7.4.1 上传BPMN流媒体
com.edcode.activiti.controller.ProcessDefinitionController#uploadStreamAndDeployment
/**
* 上传BPMN流媒体
* @param multipartFile 文件
* @return AjaxResponse
*/
@PostMapping(value = "/uploadStreamAndDeployment")
public AjaxResponse uploadStreamAndDeployment(@RequestParam("processFile") MultipartFile multipartFile) {
// 获取上传的文件名
String fileName = multipartFile.getOriginalFilename();
try {
// 得到输入流(字节流)对象
InputStream fileInputStream = multipartFile.getInputStream();
// 文件的扩展名
String extension = FilenameUtils.getExtension(fileName);
Deployment deployment = null;
String isZip = "zip";
if (isZip.equals(extension)) {
ZipInputStream zip = new ZipInputStream(fileInputStream);
//初始化流程
deployment = repositoryService.createDeployment()
.addZipInputStream(zip)
.name("流程部署名称可通过接口传递现在写死")
.deploy();
} else {
//初始化流程
deployment = repositoryService.createDeployment()
.addInputStream(fileName, fileInputStream)
.name("流程部署名称可通过接口传递现在写死")
.deploy();
}
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
deployment.getId()+";"+fileName);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"部署流程失败",
e.toString());
}
}
使用 BPMN-JS 绘图
7.4.1.1 修改 BPMN-JS 绘图页面编码
- resources/bpmnjs/app/index.html
- resources/bpmnjs/dist/index.html
确认是否添加了
<meta charset="UTF-8">
> 已经添加好了,重新启动 BPMN-JS,使用:npm run dev
7.4.1.2 打开BPMN-JS WEB页面绘图
- 输入地址:localhost:8080/bpmnjs/dist/index.html
- 随便画个图,然后下载 *.bpmn 文件到本地磁盘
7.4.1.3 使用 PostMan 上传 *.bpmn 文件
http://localhost:8080/processDefinition/uploadStreamAndDeployment
7.4.1.4 通过获取流程定义列表,查看是否上传成功
http://localhost:8080/processDefinition/getDefinitions
7.4.2 添加流程定义通过在线提交 BPMN 的 XML
com.edcode.activiti.controller.ProcessDefinitionController#addDeploymentByString
/**
* 部署BPMN字符
* 添加流程定义通过在线提交 BPMN 的 XML
* @param stringBPMN
* @return
*/
@PostMapping(value = "/addDeploymentByString")
public AjaxResponse addDeploymentByString(
@RequestParam("stringBPMN") String stringBPMN,
@RequestParam("deploymentName")String deploymentName) {
try {
Deployment deployment = repositoryService.createDeployment()
.addString("CreateWithBPMNJS.bpmn",stringBPMN)
.name("不知道在哪显示的部署名称")
.deploy();
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
deployment.getId());
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"string部署流程失败",
e.toString());
}
}
7.4.2.1 使用 PostMan 提交字符 BPMN
- 首先,打开刚刚下载的 *.bpmn 文件,全部复制
- 然后,黏贴到 stringBPMN 字段的 VALUE 里面
在使用【获取流程定义列表】接口,请求一下,是否有新的版本
7.5.1 获取流程定义XML
/**
* 获取流程定义XML
* @param response 获取前端的属性
* @param deploymentId 流程定义ID
* @param resourceName BPMN文件名称
*/
@GetMapping(value = "/getDefinitionXML")
public void getProcessDefineXML(
HttpServletResponse response,
@RequestParam("deploymentId") String deploymentId,
@RequestParam("resourceName") String resourceName) {
try {
InputStream inputStream = repositoryService.getResourceAsStream(deploymentId,resourceName);
int count = inputStream.available();
byte[] bytes = new byte[count];
response.setContentType("text/xml");
OutputStream outputStream = response.getOutputStream();
while (inputStream.read(bytes) != -1) {
outputStream.write(bytes);
}
inputStream.close();
} catch (Exception e) {
e.toString();
}
}
7.5.1.1 使用 PostMan 获取定义列表,在获取定义xml
- 获取流程定义列表
- “deploymentID”: “60136721-786f-11ec-97e1-5e879ca31830”
- “resourceName”: “CreateWithBPMNJS.bpmn”
从【获取流程定义列表】得到了流程id与名称后【获取流程定义xml】
7.5.2 获取流程部署列表
/**
* 获取流程部署列表
* @return AjaxResponse
*/
@GetMapping(value = "/getDeployments")
public AjaxResponse getDeployments() {
try {
List<HashMap<String, Object>> listMap= new ArrayList<>();
repositoryService.createDeploymentQuery().list().forEach(deployment -> {
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("Id", deployment.getId());
hashMap.put("Name", deployment.getName());
// 部署时间
hashMap.put("DeploymentTime", deployment.getDeploymentTime());
listMap.add(hashMap);
});
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"查询失败",
e.toString());
}
}
7.5.2.1 使用 PostMan 请求测试
时间的格式问题,后续会讲解
7.5.3 删除流程定义
/**
* 删除流程定义
* @param pdID 流程定义ID
* @return AjaxResponse
*/
@GetMapping(value = "/delDefinition")
public AjaxResponse delDefinition(@RequestParam("pdID") String pdID) {
try {
repositoryService.deleteDeployment(pdID, true);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
null);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"删除流程定义失败",
e.toString());
}
}
7.5.3.1 使用 PostMan 请求测试
先查 “deploymentID”: “60136721-786f-11ec-97e1-5e879ca31830”
然后删除 pdID:60136721-786f-11ec-97e1-5e879ca31830
7.6 流程实例接口
7.6.1 获取流程实例列表
查询流程实例
com.edcode.activiti.controller.ProcessInstanceController#getInstances
@RestController
@RequestMapping("/processInstance")
@RequiredArgsConstructor
public class ProcessInstanceController {
private final RepositoryService repositoryService;
private final ProcessRuntime processRuntime;
private final SecurityUtil securityUtil;
private final String TEST_USER = "bajie";
/**
* 查询流程实例:
* 注解 @AuthenticationPrincipal 认证用的
* @param userInfoBean
* @return AjaxResponse
*/
@GetMapping(value = "/getInstances")
public AjaxResponse getInstances(@AuthenticationPrincipal UserInfoBean userInfoBean) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
Page<ProcessInstance> processInstances = processRuntime.processInstances(
Pageable.of(0, 100)
);
List<ProcessInstance> list = processInstances.getContent();
// Lambda 降序排序
list.sort((y, x) -> x.getStartDate().toString().compareTo(y.getStartDate().toString()));
// jdk8 写法
List<HashMap<String, Object>> listMap = processInstances.getContent().stream().map(processInstance -> {
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("id", processInstance.getId());
hashMap.put("name", processInstance.getName());
hashMap.put("status", processInstance.getStatus());
hashMap.put("processDefinitionId", processInstance.getProcessDefinitionId());
hashMap.put("processDefinitionKey", processInstance.getProcessDefinitionKey());
hashMap.put("startDate", processInstance.getStartDate());
hashMap.put("processDefinitionVersion", processInstance.getProcessDefinitionVersion());
// TODO 这块之后 LEFT JOIN 语句会更有效率
//因为processRuntime.processDefinition("流程部署ID")查询的结果没有部署流程与部署ID,所以用repositoryService查询
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
// 仅选择具有给定id的流程定义
.processDefinitionId(processInstance.getProcessDefinitionId())
.singleResult();
// 部署流程名称
hashMap.put("resourceName", processDefinition.getResourceName());
// 部署ID
hashMap.put("deploymentId", processDefinition.getDeploymentId());
return hashMap;
}).collect(Collectors.toList());
return AjaxResponse.AjaxData(
ResponseCode.SUCCESS.getCode(),
ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
ResponseCode.ERROR.getCode(),
"获取流程实例失败",
e.toString());
}
}
}
7.6.1.1 PostMan 效果图
因为 Aactiviti7 本身定义的实体与SQL关系,所以在循环里面在查询数据库,其实不可取,只是为了方便。建议后续用关联查询
7.6.2 启动流程实例
@RestController
@RequestMapping("/processInstance")
@RequiredArgsConstructor
public class ProcessInstanceController {
private final RepositoryService repositoryService;
private final ProcessRuntime processRuntime;
private final SecurityUtil securityUtil;
private final String TEST_USER = "bajie";
/**
* 查询流程实例:
* 注解 @AuthenticationPrincipal 认证用的
* @param userInfoBean
* @return AjaxResponse
*/
@GetMapping(value = "/getInstances")
public AjaxResponse getInstances(@AuthenticationPrincipal UserInfoBean userInfoBean) {
// ... 省略
}
/**
* 启动流程实例
* @param processDefinitionKey
* @param instanceName
* @return
*/
@GetMapping(value = "/startProcess")
public AjaxResponse startProcess(
// 流程定义的Key
@RequestParam("processDefinitionKey") String processDefinitionKey,
@RequestParam("instanceName") String instanceName
){
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}else{
securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName());
}
// org.activiti.api.process.model.payloads.StartProcessPayload
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey(processDefinitionKey)
// 自定义名称
.withName(instanceName)
//.withVariable("content", instanceVariable)
//.withVariable("参数2", "参数2的值")
.withBusinessKey("自定义BusinessKey")
.build());
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
processInstance.getName()+";"+processInstance.getId()
);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"创建流程实例失败", e.toString()
);
}
}
}
7.6.2.1 启动流程,PostMan步骤
- 【获取流程定义列表】的Key
- 传入key到,【启动流程实例】的processDefinitionKey
- 【获取流程实例列表】查看是否多了条记录,状态:RUNNING
7.7 流程实例接口
7.7.1 挂起流程实例
com.edcode.activiti.controller.ProcessInstanceController#suspendInstance
/**
* 挂起流程实例
* 描述:在当前运行中的流程实例,用户暂时不想使用,又不想删除,所以只能挂起
* @param instanceID 实例ID
* @return AjaxResponse
*/
@GetMapping(value = "/suspendInstance")
public AjaxResponse suspendInstance(@RequestParam("instanceID") String instanceID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder
.suspend()
.withProcessInstanceId(instanceID)
.build()
);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
processInstance.getName());
}
catch(Exception e)
{
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"挂起流程实例失败",
e.toString());
}
}
7.7.1.1 使用 Post 请求测试
-
获取流程实例列表
-
挂起流程实例
-
再次查看获取流程实例列表
7.7.2 激活流程实例
/**
* 激活流程实例
* @param instanceID
* @return
*/
@GetMapping(value = "/resumeInstance")
public AjaxResponse resumeInstance(@RequestParam("instanceID") String instanceID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
ProcessInstance processInstance = processRuntime.resume(ProcessPayloadBuilder
.resume()
.withProcessInstanceId(instanceID)
.build()
);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
processInstance.getName());
}
catch(Exception e)
{
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"激活流程实例失败",
e.toString());
}
}
7.7.2.1 使用 Post 请求测试
7.7.3 删除流程实例
/**
* 删除流程实例
* @param instanceID
* @return
*/
@GetMapping(value = "/deleteInstance")
public AjaxResponse deleteInstance(@RequestParam("instanceID") String instanceID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
ProcessInstance processInstance = processRuntime.delete(ProcessPayloadBuilder
.delete()
.withProcessInstanceId(instanceID)
.build()
);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
processInstance.getName());
}
catch(Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"删除流程实例失败",
e.toString());
}
}
7.7.4 获取流程参数
/**
* 获取流程参数
* @param instanceID 实例ID
* @return
*/
@GetMapping(value = "/variables")
public AjaxResponse variables(@RequestParam("instanceID") String instanceID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
List<VariableInstance> variableInstance = processRuntime.variables(ProcessPayloadBuilder
.variables()
.withProcessInstanceId(instanceID)
.build());
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
variableInstance);
}
catch(Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"获取流程参数失败",
e.toString());
}
}
7.8 工作任务接口
7.8.1 获取我的代办任务
com.edcode.activiti.TaskController#getTasks
/**
* 获取我的代办任务
* @return AjaxResponse
*/
@GetMapping(value = "/getTasks")
public AjaxResponse getTasks() {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
Page<Task> tasks = taskRuntime.tasks(
Pageable.of(0, 100)
);
List<HashMap<String, Object>> listMap = tasks.getContent().stream().map(tk -> {
ProcessInstance processInstance = processRuntime.processInstance(tk.getProcessInstanceId());
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("id", tk.getId());
hashMap.put("name", tk.getName());
hashMap.put("status", tk.getStatus());
hashMap.put("createdDate", tk.getCreatedDate());
//执行人,null时前台显示未拾取
if (tk.getAssignee() == null) {
hashMap.put("assignee", "待拾取任务");
} else {
hashMap.put("assignee", tk.getAssignee());
}
hashMap.put("instanceName", processInstance.getName());
return hashMap;
}).collect(Collectors.toList());
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"获取我的代办任务失败",
e.toString());
}
}
7.8.1.1 修改 GlobalConfig 设置非测试环境
com.edcode.activiti.util.GlobalConfig#Test 修改为 false
public static final Boolean Test = false;
7.8.1.2 使用 PostMan 模拟获取代办任务
不同登录用户,然后对应查询谁的代办任务
wukong 的代办任务
7.8.2 完成待办任务
com.edcode.activiti.controller.TaskController#completeTask
public static final Boolean Test = true;
/**
* 完成待办任务
* @param taskID
* @return
*/
@GetMapping(value = "/completeTask")
public AjaxResponse completeTask(@RequestParam("taskID") String taskID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs(TEST_USER);
}
Task task = taskRuntime.task(taskID);
// 判断执行人是否null
if (task.getAssignee() == null) {
// 那么就是候选人, 就直接拾取任务
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
}
// 完成任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId())
//.withVariable("num", "2")//执行环节设置变量
.build());
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
null);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"完成任务 {"+taskID+"} 失败",
e.toString());
}
}
7.8.2.1 使用 PostMan 测试
如果返回成功,但是什么没有,那么有可能当前登录人没有代办任务,还有一种情况会报错,就是挂起的流程,不能给拾取的,需要重新激活
7.9 历史查询接口
7.9.1 用户历史
/**
* 用户历史
* @return AjaxResponse
*/
@GetMapping(value = "/getInstancesByUserName")
public AjaxResponse instancesByUser() {
try {
List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
// 按历史任务实例结束时间排序
.orderByHistoricTaskInstanceEndTime().asc()
// 登录人
.taskAssignee("bajie")
.list();
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
historicTaskInstances);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"获取历史任务失败",
e.toString());
}
}
7.9.1.1 使用 PostMan 测试
7.9.2 根据流程实例ID查询历史任务
/**
* 根据流程实例ID查询任务:
* 任务实例历史
* @param piID 流程实例ID
* @return
*/
@GetMapping(value = "/getInstancesByPiID")
public AjaxResponse getInstancesByPiID(@RequestParam("piID") String piID) {
try {
List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceEndTime().asc()
.processInstanceId(piID)
.list();
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
historicTaskInstances);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"获取历史任务失败",
e.toString());
}
}
7.9.1.2 使用 PostMan 测试
- 先查询流程实例列表:http://localhost:8080/processInstance/getInstances
- 随便复制一个 PiID (流程实例ID)
7.10 动态表单渲染方案(7.1.0.M4版本)
该方案解决的是在 v6 版本是可以获取到任务的key, 现在 v7 版本反而获取不到,不过可以使用另外一种方式,就是使用表单的key
7.10.1 通过 BPMN-JS 绘制流程
http://localhost:9013
常规 - Activity_0pj6mls
表单 - FormProperty_0srfhac
表单 - FormProperty_21afbdo
表单的key 是复制 任务的key, 为什么要搞这个,请继续看下去~
PostMan 流程:
- 登录 - /login
- 上传BPMN流媒体 - /uploadStreamAndDeployment
- 启动流程实例 - /startProcess
- 获取我的代办任务 - /getTasks
7.10.2 编写方法,Debug模式启动
com.edcode.activiti.controller.TaskController#formDataShow
/**
* 渲染表单
* @param taskID
* @return
*/
@GetMapping(value = "/formDataShow")
public AjaxResponse formDataShow(@RequestParam("taskID") String taskID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs("bajie");
}
Task task = taskRuntime.task(taskID);
// 通过任务id,可以拿到流程定义ID
UserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId())
// 在 v6 版本是可以获取到任务的key, 现在 v7 版本反而获取不到,不过可以使用另外一种方式,就是使用表单的key
.getFlowElement(task.getFormKey());
List<FormProperty> formProperties = userTask.getFormProperties();
for (FormProperty fp : formProperties) {
}
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
null);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"失败",
e.toString());
}
}
Debug 放在 第一个 return,接着上面PostMan流程拿到任务的ID,然后请求【任务表单渲染 - /formDataShow】
经过 debug 会发现,除了获取到 表单字段的“编号”和“类型”,其他【7.10.1】输入的表单参数没有显示,是因为 7.1.0.M4 版本的问题,可能后续更新版本就会有更友好的返回
7.11 动态表单渲染接口
7.11.1 Aactiviti 表单字段约定内容(自定义约定)
表单控件命名约束**:
FormProperty_0ueitp2-_!类型-_!名称-_!默认值-_!是否参数
说明:
默认值:无、字符常量、FormProperty_开头定义过的空间ID
是否参数:f为不是参数,s是字符,t是时间(不需要int,因为这里 int 等价于 string)
例子:
FormProperty_0rg77oq-_!string-_!姓名-_!请输入姓名-_!f
FormProperty_1iu6onu-_!int!-_!年龄-_!无-_!s
注意:
表单Key必需要任务编号一模一样,因为参数需要任务key,但是无法获取,只能获取表单key"task.getFormKey()"当做任务key
可以通过上述约束,在编号哪里拼接参数
这里表单编号修改成:Process_1fromv2
如图:
复制下面的格式到【表单 -> 表单字段 -> 编号】
FormProperty_0rg77oq-_!string-_!姓名-_!请输入姓名-_!f
FormProperty_02og61s-_!int!-_!年龄-_!无-_!s
然后下载 BPMN,名称自定义,反正我是:diagram002.bpmn
PostMan 流程:
- 登录 - /login
- 上传BPMN流媒体 - /uploadStreamAndDeployment
- 启动流程实例 - /startProcess (processDefinitionKey = Process_1fromv2)
- 获取我的代办任务 - /getTasks
然后,Debug 启动IDEA,调用 【任务表单渲染 - /formDataShow】,也是上一次一样,断点在第一个return
已经获取到表单字段的编号里面的参数
7.11.2 根据表单拼接编号获取参数
com.edcode.activiti.controller.TaskController#formDataShow 进行获取参数
/**
* 渲染表单
* @param taskID
* @return
*/
@GetMapping(value = "/formDataShow")
public AjaxResponse formDataShow(@RequestParam("taskID") String taskID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs("bajie");
}
Task task = taskRuntime.task(taskID);
//本实例所有保存的表单数据HashMap,为了快速读取控件以前环节存储的值
HashMap<String, String> controlistMap = new HashMap<>();
// 通过任务id,可以拿到流程定义ID
UserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId())
// 在 v6 版本是可以获取到任务的key, 现在 v7 版本反而获取不到,不过可以使用另外一种方式,就是使用表单的key
.getFlowElement(task.getFormKey());
if (userTask == null) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
"无表单");
}
List<FormProperty> formProperties = userTask.getFormProperties();
List<HashMap<String, Object>> listMap = new ArrayList<>();
for (FormProperty fp : formProperties) {
// FormProperty_0rg77oq-_!string-_!姓名-_!请输入姓名-_!f
String[] splitFP = fp.getId().split("-_!");
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("id", splitFP[0]);
// 控制类型
hashMap.put("controlType", splitFP[1]);
// 控制标签
hashMap.put("controlLiable", splitFP[2]);
// 默认值
hashMap.put("controlDevalue", splitFP[3]);
// 参数
hashMap.put("controlParam", splitFP[4]);
listMap.add(hashMap);
}
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"失败",
e.toString());
}
}
7.11.3 使用 PostMan 测试
7.12 动态表单解析提交数据
7.12.1 Activiti 动态表单提交
- 解析提交数据
- 提交表单内容写入数据库
- 参数数据作为 UEL 表达式参数
7.12.2 Activiti 表单提交参数约定内容(自定义约定)
动态表单提交内容约定:
formaData:控件id-_!控件值-_!是否参数!_!控件id-_!控件值-_!是否参数
是否参数:f为不是参数,s是字符,t是时间(不需要int,因为这里 int 等价于 string)
分割多个控件:!_!
例子:
FormProperty_0rg77oq-_!不是参数-_!f!_!FormProperty_02og61s-_!我是参数-_!s
7.12.3 根据拼接参数解析提交数据
com.edcode.activiti.controller.TaskController#formDataSave
/**
* 保存表单
* @param taskID 任务ID
* @param formData 表单数据
* @return
*/
@PostMapping(value = "/formDataSave")
public AjaxResponse formDataSave(
@RequestParam("taskID") String taskID,
@RequestParam("formData") String formData) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs("bajie");
}
Task task = taskRuntime.task(taskID);
List<HashMap<String, Object>> listMap = new ArrayList<>();
//前端传来的字符串,拆分成每个控件
String[] formDataList = formData.split("!_!");
for (String controlItem : formDataList) {
String[] formDataItem = controlItem.split("-_!");
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("PROC_DEF_ID_", task.getProcessDefinitionId());
hashMap.put("PROC_INST_ID_", task.getProcessInstanceId());
hashMap.put("FORM_KEY_", task.getFormKey());
// 从 formData 获取
hashMap.put("Control_ID_", formDataItem[0]);
hashMap.put("Control_VALUE_", formDataItem[1]);
listMap.add(hashMap);
}
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"提交任务表单失败",
e.toString());
}
}
7.23.3.1 使用 PostMan 测试
http://localhost:8080/task/formDataSave?taskID=dc776f33-7997-11ec-a471-5e879ca31830&formData=FormProperty_0rg77oq-!不是参数-!f!!FormProperty_02og61s-!我是参数-_!s
7.13 动态表单提交入库
7.13.1 创建 Mapper
@Mapper
@Component
public interface ActivitiMapper {
/**
* 写入表单
* @param maps
* @return int
* @sql insert into formdata (PROC_DEF_ID_,PROC_INST_ID_,FORM_KEY_,Control_ID_,Control_VALUE_)
* values (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?)
*/
@Insert("<script> insert into formdata (PROC_DEF_ID_,PROC_INST_ID_,FORM_KEY_,Control_ID_,Control_VALUE_)" +
" values" +
" <foreach collection=\"maps\" item=\"formData\" index=\"index\" separator=\",\">" +
" (#{formData.PROC_DEF_ID_,jdbcType=VARCHAR}," +
" #{formData.PROC_INST_ID_,jdbcType=VARCHAR}," +
" #{formData.FORM_KEY_,jdbcType=VARCHAR}, " +
" #{formData.Control_ID_,jdbcType=VARCHAR}," +
" #{formData.Control_VALUE_,jdbcType=VARCHAR})" +
" </foreach> </script>")
int insertFormData(@Param("maps") List<HashMap<String, Object>> maps);
}
com.edcode.activiti.controller.TaskController#formDataSave
int result = mapper.insertFormData(listMap);
7.13.2 使用 PostMan 测试
【7.23.3.1】 一样请求,然后层查询数据库
7.14 动态表单UEL表达式
7.14.1 题外话 - 变量覆盖的问题
taskService.complete() 与 taskRuntime.complete() 区别
描述区别的BPMN,不用你画图
角色:
请假人:八戒
1号审批人:a (3天内找他)
2号审批人:b (大于3天找他)
流程<排他网关>:
八戒需要请假6天,根据条件直接找 b 审批人,但是 b 不通过审核,又回到八戒。
八戒这次请假天数修改2天,直接去到 a 审批人,然后就通过
问题:
通常这种修改天数,都是使用 UEL 表达式 ${day<3}, 就会出现覆盖参数来改变原有的天数变量
如果使用 taskService.complete() 这种方式完成任务,变量是不会覆盖
需要使用 taskRuntime.complete() 这种方式完成任务,变量是可以多次覆盖
7.14.2 保存表单的整个步骤
7.14.2.1 完整保存表单代码片
com.edcode.activiti.controller.TaskController#formDataSave
/**
* 保存表单
*
* @param taskID 任务ID
* @param formData 表单数据
* @return AjaxResponse
* <p>
* formData:控件id-_!控件值-_!是否参数!_!控件id-_!控件值-_!是否参数 <p>
* FormProperty_0rg77oq-_!不是参数-_!f!_!FormProperty_02og61s-_!我是参数-_!s
*/
@PostMapping(value = "/formDataSave")
public AjaxResponse formDataSave(
@RequestParam("taskID") String taskID,
@RequestParam("formData") String formData) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs("bajie");
}
Task task = taskRuntime.task(taskID);
HashMap<String, Object> variables = new HashMap<>(16);
//没有任何参数
boolean hasVariables = false;
List<HashMap<String, Object>> listMap = new ArrayList<>();
//前端传来的字符串,拆分成每个控件
String[] formDataList = formData.split("!_!");
for (String controlItem : formDataList) {
String[] formDataItem = controlItem.split("-_!");
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("PROC_DEF_ID_", task.getProcessDefinitionId());
hashMap.put("PROC_INST_ID_", task.getProcessInstanceId());
hashMap.put("FORM_KEY_", task.getFormKey());
// 从 formData 获取
hashMap.put("Control_ID_", formDataItem[0]);
hashMap.put("Control_VALUE_", formDataItem[1]);
// hashMap.put("Control_PARAM_", formDataItem[2]);
listMap.add(hashMap);
// 构建参数集合
// 是否参数:f为不是参数,s是字符,t是时间,b是布尔值(不需要int,因为这里 int 等价于 string)
switch (formDataItem[2]) {
case "f":
System.out.println("控件值不作为参数");
break;
case "s":
// key=控件ID, value=控件的值
variables.put(formDataItem[0], formDataItem[1]);
hasVariables = true;
break;
case "t":
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
variables.put(formDataItem[0], timeFormat.parse(formDataItem[2]));
hasVariables = true;
break;
case "b":
variables.put(formDataItem[0], BooleanUtils.toBoolean(formDataItem[2]));
hasVariables = true;
break;
default:
System.out.println("控件参数类型配置错误:" + formDataItem[0] + "的参数类型不存在," + formDataItem[2]);
}
}
if (hasVariables) {
//带参数完成任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskID)
.withVariables(variables)
.build());
} else {
// 没有任何参数
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskID)
.build());
}
//写入数据库
mapper.insertFormData(listMap);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"提交任务表单失败",
e.toString());
}
}
7.14.2.2 BPMN 绘流程图
- 工具:BPMN-JS
- 流程定义Key:Process_1formparam
- 流程定义名称:动态表单测试
- 代理人都填写:bajie
- 表单名称分别是:八戒表单1、八戒表单2、八戒表单3
- 八戒表单1 (参考图1)
- 创建两个表单字段(控件)
- FormProperty_2okcuq1-!string-!姓名-!请填写姓名-!f
- FormProperty_1sgo5d6-!long-!年龄-!无-!s
- 任务的Key (Activity_0slwlbs)
- 复制到表单的key的框里面
- 创建两个表单字段(控件)
- 排他网关 (参考图2)
- 连接的条件从原来 ${day < 3} 的 day 修改成控件id
- ${FormProperty_1sgo5d6<=18}
- ${FormProperty_1sgo5d6>18}
- 连接的条件从原来 ${day < 3} 的 day 修改成控件id
- 下载 BPMN 文件
图1
图2
7.14.2.3 使用 PostMan 测试
PostMan 流程:
- 登录 - /login
- 上传BPMN流媒体 - /uploadStreamAndDeployment
- 启动流程实例 - /startProcess (processDefinitionKey = Process_1formparam )
- 获取我的代办任务 - /getTasks
- 任务表单渲染 - /formDataShow
- 任务表单保存 - /formDataSave (参考图3)
- 获取我的代办任务 - /getTasks(参考图5)
a. 在查询代办时候,就会看到已经去到 八戒表单3,因为条件 大于 3 岁
其余都和前面差不多,就不截图了
图3
图4
图5
7.14.2.4 总结
- bpmn-js 表单无法传入参数到后端,所以通过拼接参数方式来达到功能实现。
- 因为获取不了表单key,所以在bpmnjs时候就需要拿任务key作为表单key
- 后端通过前端传来的字符串,拆分成每个控件,每个参数
回顾一下约束格式:
前端传入格式:
FormProperty_0ueitp2-_!类型-_!名称-_!默认值-_!是否参数
输入到bpmnjs表单控件里面:
FormProperty_2okcuq1-_!string-_!姓名-_!请填写姓名-_!f
FormProperty_1sgo5d6-_!long-_!年龄-_!无-_!s
然后通过渲染拿到json格式,对应换成保存表单的数据:
{
"status": 0,
"msg": "成功",
"obj": [
{
"controlType": "string",
"controlLiable": "姓名",
"controlParam": "f",
"id": "FormProperty_2okcuq1",
"controlDevalue": "请填写姓名"
},
{
"controlType": "long",
"controlLiable": "年龄",
"controlParam": "s",
"id": "FormProperty_1sgo5d6",
"controlDevalue": "无"
}
]
}
保存表单的格式:
前端传入格式formaData:控件id-_!控件值-_!是否参数!_!控件id-_!控件值-_!是否参数
拼接后:FormProperty_2okcuq1-_!请填写姓名-_!f!_!FormProperty_1sgo5d6-_!99-_!s
7.15 动态表单读取历史数据接口
7.15.1 改动部分
构建表单控件历史数据字典
hashMap.put(“controlDevalue”, splitFP[3]); 修改为动态
7.15.2 暂时完整代码 - 渲染表单
com.edcode.activiti.controller.TaskController#formDataShow
@GetMapping(value = "/formDataShow")
public AjaxResponse formDataShow(@RequestParam("taskID") String taskID) {
try {
if (GlobalConfig.Test) {
securityUtil.logInAs("bajie");
}
Task task = taskRuntime.task(taskID);
//-----------------------构建表单控件历史数据字典-----------------------
// 本实例所有保存的表单数据HashMap,为了快速读取控件以前环节存储的值
HashMap<String, String> controlistMap = new HashMap<>(16);
// 本实例所有保存的表单数据
List<HashMap<String, Object>> tempControlList = mapper.selectFormData(task.getProcessInstanceId());
for (HashMap<String, Object> ls : tempControlList) {
controlistMap.put(ls.get("Control_ID_").toString(), ls.get("Control_VALUE_").toString());
}
/*
FormProperty_0ueitp2-_!类型-_!名称-_!默认值-_!是否参数
例子:
FormProperty_0lovri0-_!string-_!姓名-_!请输入姓名-_!f
FormProperty_1iu6onu-_!int-_!年龄-_!请输入年龄-_!s
默认值:无、字符常量、FormProperty_开头定义过的控件ID
是否参数:f为不是参数,s是字符,t是时间(不需要int,因为这里int等价于string)
注:类型是可以获取到的,但是为了统一配置原则,都配置到
*/
// 注意!!!!!!!!:表单Key必须要任务编号一模一样,因为参数需要任务key,但是无法获取,只能获取表单key“task.getFormKey()”当做任务key
UserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId())
// 在 v6 版本是可以获取到任务的key, 现在 v7 版本反而获取不到,不过可以使用另外一种方式,就是使用表单的key
.getFlowElement(task.getFormKey());
if (userTask == null) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
"无表单");
}
List<FormProperty> formProperties = userTask.getFormProperties();
List<HashMap<String, Object>> listMap = new ArrayList<>();
for (FormProperty fp : formProperties) {
// FormProperty_0rg77oq-_!string-_!姓名-_!请输入姓名-_!f
String[] splitFP = fp.getId().split("-_!");
HashMap<String, Object> hashMap = new HashMap<>(16);
hashMap.put("id", splitFP[0]);
// 控制类型
hashMap.put("controlType", splitFP[1]);
// 控制标签
hashMap.put("controlLiable", splitFP[2]);
// 参数
hashMap.put("controlParam", splitFP[4]);
//默认值,如果是表单控件ID
if (splitFP[3].startsWith("FormProperty_")) {
//控件ID存在
if (controlistMap.containsKey(splitFP[3])) {
hashMap.put("controlDefValue", controlistMap.get(splitFP[3]));
} else {
//控件ID不存在
hashMap.put("controlDefValue", "读取失败,检查" + splitFP[0] + "配置");
}
} else {
//默认值如果不是表单控件ID则写入默认值
hashMap.put("controlDefValue", splitFP[3]);
}
listMap.add(hashMap);
}
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
listMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"失败",
e.toString());
}
}
com.edcode.activiti.mapper.ActivitiMapper#selectFormData
/**
* 读取表单
* @param PROC_INST_ID
* @return
*/
@Select("SELECT Control_ID_,Control_VALUE_ from formdata where PROC_INST_ID_ = #{PROC_INST_ID}")
List<HashMap<String,Object>> selectFormData(@Param("PROC_INST_ID") String PROC_INST_ID);
7.16 动态表单读取历史数据操作
7.16.1 BPMN 绘图
7.16.2 数据准备
动态表单配置规则 (动态表单必需看)
控件命名约束:FormProperty_0ueitp2-_!类型-_!名称-_!默认值-_!是否参数
ID:自行标号同一流程定义无重复
类型:string、long、cUser(cUser为自定义类型读取用户列表)
默认值:无、字符、FormProperty_开头定义过的控件ID
是否参数:f为不是参数,s是字符,t是时间(不需要int,因为这里int等价于string)
例子:FormProperty_0lovri0-_!string-_!姓名-_!请输入姓名-_!f
FormProperty_1iu6onu-_!long-_!年龄-_!请输入年龄-_!s
FormProperty_2rd4dtv-_!cUser-_!执行人-_!无-_!s
注意:表单Key必须要任务编号一致
(因为参数需要任务key,但是无法获取,只能获取表单key“task.getFormKey()”当做任务key)
参数模板:FormProperty_0ueitp2-_!类型-_!名称-_!默认值-_!是否参数
前端模板:控件id-_!控件值-_!是否参数!_!控件id-_!控件值-_!是否参数
------bpmn------
流程定义Key:Process_1InputData
名称:输入数据后读取
------八戒输入数据------
FormProperty_2sj12b0-_!string-_!姓名-_!请输入姓名-_!f
FormProperty_0a70k5s-_!string-_!性别-_!是男是女-_!f
------悟空查看数据------
FormProperty_3j976ub-_!string-_!姓名-_!悟空的爱好-_!f
FormProperty_1jj12tv-_!string-_!性别A-_!FormProperty_0a70k5s-_!f
------前端数据------
FormProperty_2sj12b0-_!我是八戒-_!f!_!FormProperty_0a70k5s-_!男-_!f
7.16.3 使用 PostMan 测试
7.16.3.1 让下一个环节获取上一个环节的默认值
PostMan 流程:
- 登录 - /login (八戒)
- 上传BPMN流媒体 - /uploadStreamAndDeployment
- 启动流程实例 - /startProcess (processDefinitionKey = Process_1InputData )
- 获取我的代办任务 - /getTasks
- 任务表单渲染 - /formDataShow
- 任务表单保存 - /formDataSave
- 然后,切换登录用户 (悟空)
- 获取我的代办任务 - /getTasks (拿到 taskID )
- 任务表单渲染 - /formDataShow
切换用户之后,为什么悟空性别是男?
答:第一次前端传入【我是八戒】和【男】的时候,已经保存了。而悟空在查询任务渲染的时候,使用了 FormProperty_0a70k5s 作为它的默认值,所以悟空性别默认是跟前面传入的【男】一样
这个逻辑的核心代码部分:
if (splitFP[3].startsWith("FormProperty_")) {
//控件ID存在
if (controlistMap.containsKey(splitFP[3])) {
hashMap.put("controlDefValue", controlistMap.get(splitFP[3]));
} else {
//控件ID不存在
hashMap.put("controlDefValue", "读取失败,检查" + splitFP[0] + "配置");
}
} else {
//默认值如果不是表单控件ID则写入默认值
hashMap.put("controlDefValue", splitFP[3]);
}
最简单的一些场景,比如:有一些任务是李总审批了,然后需要张总去审批,然后又回到李总哪里再次确认审批, 那么这次的李总就可以使用动态表单方式
7.17 高亮历史流程渲染接口 - 获取线与任务块的信息
7.17.1 BPMN绘图
线条、任务块最好【常规】-> 【编号】填写一看看得明白的,一阵debug方便查看。
7.17.2 首先黏贴高亮部分方法
@GetMapping("/gethighLine")
public AjaxResponse gethighLine(@RequestParam("instanceId") String instanceId, @AuthenticationPrincipal UserInfoBean UuserInfoBean) {
try {
//查询当前实例的历史流程
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
//读取bpmn,获取bpmnModel对象
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
//因为我们这里只定义了一个Process 所以获取集合中的第一个即可
Process process = bpmnModel.getProcesses().get(0);
//获取所有的FlowElement信息 (线信息)
Collection<FlowElement> flowElements = process.getFlowElements();
Map<String, String> map = new HashMap<>(16);
for (FlowElement flowElement : flowElements) {
//判断是否是连线
if (flowElement instanceof SequenceFlow) {
SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
String ref = sequenceFlow.getSourceRef();
String targetRef = sequenceFlow.getTargetRef();
map.put(ref + targetRef, sequenceFlow.getId());
}
}
//获取流程实例 历史节点(全部)
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.list();
//各个历史节点 两两组合 key
Set<String> keyList = new HashSet<>();
for (HistoricActivityInstance i : list) {
for (HistoricActivityInstance j : list) {
if (i != j) {
keyList.add(i.getActivityId() + j.getActivityId());
}
}
}
// 待续.....
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
null);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"渲染历史流程失败",
e.toString());
}
}
7.12.2.1 PostMan & Debug
http://localhost:8080/activitiHistory/gethighLine?instanceId=xxxxxx
从图片可以,查看到 Map 已经获取到模块的所有编号,这里显示四个是因为,我执行过一次完成任务,把 高亮一,完成了。
通过传入的 实例Id(instanceId) 获取流程实例集合, 也就是获取到: 开始节点与第一个任务块的信息,或者之后任务快与任务快,任务快与结束节点
7.18 高亮历史流程渲染接口
7.18.1 Debug 查看流程图状态
@GetMapping("/gethighLine")
public AjaxResponse gethighLine(@RequestParam("instanceId") String instanceId, @AuthenticationPrincipal UserInfoBean UuserInfoBean) {
try {
//..... 省略
//
//各个历史节点 两两组合 key
Set<String> keyList = new HashSet<>();
for (HistoricActivityInstance i : list) {
for (HistoricActivityInstance j : list) {
if (i != j) {
keyList.add(i.getActivityId() + j.getActivityId());
}
}
}
//高亮连线ID
Set<String> highLine = new HashSet<>();
//将连线的信息放入新的Set集合
keyList.forEach(s -> highLine.add(map.get(s)));
//获取流程实例 历史节点(已完成)
List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.finished() // 完成
.list();
//已经完成的节点高亮
Set<String> highPoint = new HashSet<>();
//高亮节点ID
listFinished.forEach(s -> highPoint.add(s.getActivityId()));
//获取流程实例 历史节点(待办节点)
List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.unfinished() // 未完成(待办)
.list();
//待办高亮节点
Set<String> waitingToDo = new HashSet<>();
listFinished.forEach(s -> waitingToDo.add(s.getActivityId()));
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
null);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"渲染历史流程失败",
e.toString());
}
}
从 highLine(线)、highPoint(节点)、waitingToDo(待办节点)一目了然,当然也可以用语句拼接一起,查询在返回不同集合
7.18.2 流程图高亮 - 完整的代码快
留意注释
/**
* 流程图高亮
* @param instanceId 流程实例id
* @param UuserInfoBean 当前登录人实体
* @return AjaxResponse
*
* 主要是看最后封装Map就懂了,分三个环节:
* 1. 获取高亮的任务节点
* 2. 获取高亮的连线
* 3. 获取现在还有个环节需要完成的
* 4. 获取当前登录人做过的任务
* 5. 以上四点封装 Map 返回
*/
@GetMapping("/gethighLine")
public AjaxResponse gethighLine(@RequestParam("instanceId") String instanceId, @AuthenticationPrincipal UserInfoBean UuserInfoBean) {
try {
//查询当前实例的历史流程
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
//读取bpmn,获取bpmnModel对象
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
//因为我们这里只定义了一个Process 所以获取集合中的第一个即可
Process process = bpmnModel.getProcesses().get(0);
//获取所有的FlowElement信息 (线信息)
Collection<FlowElement> flowElements = process.getFlowElements();
Map<String, String> map = new HashMap<>(16);
for (FlowElement flowElement : flowElements) {
//判断是否是连线
if (flowElement instanceof SequenceFlow) {
SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
String ref = sequenceFlow.getSourceRef();
String targetRef = sequenceFlow.getTargetRef();
map.put(ref + targetRef, sequenceFlow.getId());
}
}
//获取流程实例 历史节点(全部)
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.list();
//各个历史节点 两两组合 key
Set<String> keyList = new HashSet<>();
for (HistoricActivityInstance i : list) {
for (HistoricActivityInstance j : list) {
if (i != j) {
keyList.add(i.getActivityId() + j.getActivityId());
}
}
}
//高亮连线ID
Set<String> highLine = new HashSet<>();
//将连线的信息放入新的Set集合
keyList.forEach(s -> highLine.add(map.get(s)));
//获取流程实例 历史节点(已完成)
List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.finished() // 完成
.list();
//已经完成的节点高亮
Set<String> highPoint = new HashSet<>();
//高亮节点ID
listFinished.forEach(s -> highPoint.add(s.getActivityId()));
//获取流程实例 历史节点(待办节点)
List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId)
.unfinished() // 未完成(待办)
.list();
//待办高亮节点
Set<String> waitingToDo = new HashSet<>();
listUnFinished.forEach(s -> waitingToDo.add(s.getActivityId()));
// 当前用户的任务
String assigneeName = null;
if (GlobalConfig.Test) {
assigneeName = TestUserConstant.TEST_USER;
} else {
assigneeName = UuserInfoBean.getUsername();
}
// 创建历史任务实例查询
List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
// 执行人
.taskAssignee(assigneeName)
// 已完成的
.finished()
// 当前实例
.processInstanceId(instanceId).list();
// 待办高亮节点
Set<String> iDo = new HashSet<>();
// 任务实例列表, 添加任务定义Key
taskInstanceList.forEach(s -> iDo.add(s.getTaskDefinitionKey()));
// 组装 Map 返回以上的结果集的集合
Map<String, Object> reMap = new HashMap<>(16);
// 高亮的任务节点
reMap.put("highPoint", highPoint);
// 高亮的连线
reMap.put("highLine", highLine);
// 现在还有个环节需要完成的
reMap.put("waitingToDo", waitingToDo);
// 我做过的任务
reMap.put("iDo", iDo);
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
reMap);
} catch (Exception e) {
return AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
"渲染历史流程失败",
e.toString());
}
}
7.18.2.1 使用 PostMan 测试
-
使用 wukong 登录,“iDo”: [] 是为空的,因为他没有执行过任务
-
使用 bajie 登录,“iDo”: [] 是有返回 bajie 执行过的任务
7.19 自定义用户控件
动态表单配置规则有一条是:
FormProperty_2rd4dtv-_!cUser-_!执行人-_!无-_!s
cUser 这个非 string、long… 就可以看作自定义用户控件,或者视频控件等等。 百变不离其中!
7.20 统计查询语句
7.20.1 查询流程定义产生的流程实例数
SELECT
p.NAME_,
COUNT( DISTINCT e.PROC_INST_ID_ ) AS PiNUM
FROM
ACT_RU_EXECUTION AS e
RIGHT JOIN ACT_RE_PROCDEF AS p ON e.PROC_DEF_ID_ = p.ID_
WHERE
p.NAME_ IS NOT NULL
GROUP BY
p.NAME_
7.20.2 流程定义数
SELECT COUNT(ID_) from ACT_RE_PROCDEF
7.20.3 进行中的流程实例
SELECT COUNT(DISTINCT PROC_INST_ID_) from act_ru_execution