【Aactiviti7 从入门到放弃】(三)实战:后端接口设计与实现

目录

可视化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

![在这里插入图片描述](https://img-blog.csdnimg.cn/1a88946a28c8416ea9d3cbfe1fe9bf0e.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAZWRkaWVfazI=,size_20,color_FFFFFF,t_70,g_se,x_16

表单 - FormProperty_0srfhac
在这里插入图片描述
表单 - FormProperty_21afbdo
在这里插入图片描述

表单的key 是复制 任务的key, 为什么要搞这个,请继续看下去~

PostMan 流程:

  1. 登录 - /login
  2. 上传BPMN流媒体 - /uploadStreamAndDeployment
  3. 启动流程实例 - /startProcess
  4. 获取我的代办任务 - /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 流程:

  1. 登录 - /login
  2. 上传BPMN流媒体 - /uploadStreamAndDeployment
  3. 启动流程实例 - /startProcess (processDefinitionKey = Process_1fromv2)
  4. 获取我的代办任务 - /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}
  • 下载 BPMN 文件

图1

在这里插入图片描述
图2
在这里插入图片描述

7.14.2.3 使用 PostMan 测试

PostMan 流程:

  1. 登录 - /login
  2. 上传BPMN流媒体 - /uploadStreamAndDeployment
  3. 启动流程实例 - /startProcess (processDefinitionKey = Process_1formparam )
  4. 获取我的代办任务 - /getTasks
  5. 任务表单渲染 - /formDataShow
  6. 任务表单保存 - /formDataSave (参考图3
  7. 获取我的代办任务 - /getTasks(参考图5
    a. 在查询代办时候,就会看到已经去到 八戒表单3,因为条件 大于 3 岁

其余都和前面差不多,就不截图了

图3
在这里插入图片描述
图4
在这里插入图片描述图5

在这里插入图片描述

7.14.2.4 总结
  1. bpmn-js 表单无法传入参数到后端,所以通过拼接参数方式来达到功能实现。
  2. 因为获取不了表单key,所以在bpmnjs时候就需要拿任务key作为表单key
  3. 后端通过前端传来的字符串,拆分成每个控件,每个参数

回顾一下约束格式:

前端传入格式:
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 流程:

  1. 登录 - /login (八戒)
  2. 上传BPMN流媒体 - /uploadStreamAndDeployment
  3. 启动流程实例 - /startProcess (processDefinitionKey = Process_1InputData )
  4. 获取我的代办任务 - /getTasks
  5. 任务表单渲染 - /formDataShow
  6. 任务表单保存 - /formDataSave
  7. 然后,切换登录用户 (悟空)
  8. 获取我的代办任务 - /getTasks (拿到 taskID )
  9. 任务表单渲染 - /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 测试
  1. 使用 wukong 登录,“iDo”: [] 是为空的,因为他没有执行过任务
    在这里插入图片描述

  2. 使用 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eddie_k2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值