后台发起流程
业务需求
采购明细单中增加一个处理人字段,每行采购物料都要通知这个指定人员处理,当采购申请单审核通过后,会根据明细表行数,自动发起流程
发起原理
f12抓取
f12打开调试,正常发起流程,我们可以发现就是正常发起http请求,调用create接口。我们使用后台调用效果应该一致
接口测试工具发起流程
根据f12抓取的信息,就可以使用Apipost等接口调用工具发起一个新流程。当然也就可以用java后台发起。
基本信息
接口URL: http://localhost:8008/instance/create
请求方式: POST
Content-Type: application/json
header
参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
---|---|---|---|---|
Zctoken | Gh8pNiL9YNbbQTIiYGCpRAZYv6yyTtnv | 否 | 暂无描述 | |
body参数及说明
{
"definitionKey": "Process_R6KpeaUqiJU0BNXvWuNcZ",//流程编码
"version": null,
"initiator": {
"ouMemberSid": "be7cbd6b-3a65-474f-9d06-70c6a278c1f6"//携带部门职位等信息的人员编码
},
"formData": {
"erp": {
"buyer": [
{
"__zcRowGuid": "h2maR6gx93B1A2zc-vJvT",
"user_name": "11",
"item_name": "11",
"qty": null,
"busniess_key": null,
"id": null,
"sn": null,
"business_key": null
}
]
}
}
}
流程准备
当然想要从后台发起流程,首先我们要有一个流程。 这里我新建了一个采购人员,很简单,只有三个字段。
审批人设置为数据表内的账号,使用采购人员
就可以,方便观察效果。
之后后台发起流程,想要知道发起哪个流程,就需要流程编码。流程编码就在下图所在位置。
后端接口
调用逻辑,ZeroCloud首先调用OpenFlowController,使用一个Map接受参数。
为了方便演示此案例业务逻辑我直接写在了controller里,编写了一个request方法。
request方法里有两点需要注意的。
请求流程接口需要sid,因为流程启动不光需要一个人的账号信息,还需要这个人的职位信息,部门信息等等。所以我们要使用 账号换取sid。 使用的方式就是微服务之间的调用,feign。
接口信息可以从上文的Api文档中查看。hutool工具类,我使用hutool工具类的jsonObject,把我利用f12从后台抓取到的发起[采购人员]流程的请求体,转成了jsonObject,之后修改了这个jsonObject对象作为新的请求体。
使用hutool工具类发起了http请求,来发起流程。
OpenFlowController
package com.zerocloud.custom.service.api.controller;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zerocloud.common.core.model.ou.ZcOUMember;
import com.zerocloud.common.core.model.ou.ZcUser;
import com.zerocloud.common.core.model.result.ZcResult;
import com.zerocloud.custom.service.api.feign.RemoteSystemService;
import com.zerocloud.custom.service.api.model.ApiResponse;
import com.zerocloud.custom.service.api.service.StockService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Api(tags = "打开流程")
@RestController
@RequestMapping("/openFlows")
public class OpenFlowController {
private final RemoteSystemService remoteSyStemService;
@Autowired
public OpenFlowController(StockService stockService,RemoteSystemService remoteSyStemService) {
this.remoteSyStemService = remoteSyStemService;
}
@PostMapping("/byName")
@ApiOperation("根据用户名称发起流程")
public ZcResult<String> openFlow(@RequestBody Map map) {
//获取流程推送的参数
String string = map.toString();
//转换成jsonObject
JSONObject param = JSONUtil.parseObj(map);
JSONArray buyers = param.getJSONArray("buyer");
String zctoken = param.getStr("Zctoken");
for (Object obj : buyers) {
JSONObject buyer = (JSONObject) obj;
//通过http调用的方式发起流程
this.request(buyer,zctoken);
}
return ZcResult.success("接口调用成功");
}
//利用hutool工具类处理字符串,把json格式的字符串直接转换成jsonObject
//利用hutool工具类发起http请求
public void request(JSONObject buyerParam,String zctoken){
// 在这里处理每个 buyer 对象
String userName = buyerParam.getStr("user_name");
String item_name = buyerParam.getStr("item_name");
String qty = buyerParam.getStr("qty");
//根据用户名称获取用户信息
ZcResult<List<ZcOUMember>> ouUser = remoteSyStemService.getOuUser(userName);
List<ZcOUMember> data = ouUser.getData();
//获取sid
String ouMemberSid = data.get(0).getOuMemberSid();
// 创建流程url
String url = "http://localhost:8004/process/instance/create";
// 请求参数 从f12抓到的
String requestBody = "{\"definitionKey\":\"Process_R6KpeaUqiJU0BNXvWuNcZ\",\"version\":null,\"initiator\":{\"ouMemberSid\":\"be7cbd6b-3a65-474f-9d06-70c6a278c1f6\"},\"formData\":{\"erp\":{\"buyer\":[{\"__zcRowGuid\":\"h2maR6gx93B1A2zc-vJvT\",\"user_name\":\"11\",\"item_name\":\"11\",\"qty\":null,\"busniess_key\":null,\"id\":null,\"sn\":null,\"business_key\":null}]}}}";
// 将请求体字符串转换为JSON对象,直接修改对象,就不用new了,直接new也一样
JSONObject jsonBody = new JSONObject(requestBody);
JSONObject initiator = jsonBody.getJSONObject("initiator");
initiator.set("ouMemberSid",ouMemberSid);
jsonBody.set("initiator",initiator);
JSONObject formData = jsonBody.getJSONObject("formData");
JSONObject erp = formData.getJSONObject("erp");
JSONArray buyer = erp.getJSONArray("buyer");
// 获取buyer数组中的第一个对象
JSONObject buyerObject = buyer.getJSONObject(0);
// 删除__zcRowGuid属性 若无子表嵌套,不需要这个字段
buyerObject.remove("__zcRowGuid");
// 给buyer对象重新赋值
buyerObject.put("user_name", userName);
buyerObject.put("item_name", item_name);
buyerObject.put("qty", qty);
//放回数组结构中
buyer.put(0, buyerObject);
erp.set("buyer",buyer);
formData.set("erp",erp);
jsonBody.set("formData",formData);
String string = jsonBody.toString();
// 请求头
HttpRequest request = HttpRequest.post(url)
.header("Zctoken", zctoken)
.body(jsonBody.toString());
// 发送请求
HttpResponse response = request.execute();
// 获取响应结果
int statusCode = response.getStatus();
String responseBody = response.body();
// 打印响应结果
System.out.println("Status code: " + statusCode);
System.out.println("Response body: " + responseBody);
}
}
RemoteSystemService
SpringCloud 微服务之间的远程调用,实际就是http请求。这里使用了Feign,使得远程调用就像调用方法一样容易。大家根据接口文档,使用http调用可以达成一样的效果。
package com.zerocloud.custom.service.api.feign;
import com.zerocloud.common.core.model.ou.ZcOUMember;
import com.zerocloud.common.core.model.ou.ZcUser;
import com.zerocloud.common.core.model.result.ZcResult;
import com.zerocloud.custom.service.api.model.ApiResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "zerocloud-auth")
public interface RemoteSystemService {
@GetMapping("ou-member/user")
//通过用户账号,获取用户信息
//接口信息从swagger里找一下
//http://localhost:8002/swagger-ui.html#/%E9%83%A8%E9%97%A8%E6%88%90%E5%91%98/kUsingGET
ApiResponse<ZcUser> getOuUserInfo(@RequestParam("userName") String userName);
@GetMapping("ou-member/search")
//通过用户账号,获取部门成员信息
//接口信息从swagger里找一下
//http://localhost:8002/swagger-ui.html#/%E9%83%A8%E9%97%A8%E6%88%90%E5%91%98/kUsingGET
ZcResult<List<ZcOUMember>> getOuUser(@RequestParam("keyword") String userName);
}
前台配置
下面这段代码的逻辑就是封装了一个map,携带Zctoken及一个数组。循环明细行给这个数组赋值。后台收到这些参数,用来发起一个新流程。
//这版groovy上下文用*引入的jackson对象,不写全称找不到对象
//这版也不要尝试import 会报错
//再过一两个版本会换成利用js上下文结构选择的形式,直接cv就可以。
com.fasterxml.jackson.databind.node.ObjectNode map = new com.fasterxml.jackson.databind.ObjectMapper().createObjectNode();
map.put("Zctoken", loginUser.getTokenValue());
com.fasterxml.jackson.databind.node.ArrayNode buyers = new com.fasterxml.jackson.databind.ObjectMapper().createArrayNode();
for (java.util.Map < String, Object > row : formData.get("zerocloud_data").get("purchase_t")) {
com.fasterxml.jackson.databind.node.ObjectNode buyer = new com.fasterxml.jackson.databind.ObjectMapper().createObjectNode();
com.fasterxml.jackson.databind.JsonNode rowNode = new com.fasterxml.jackson.databind.ObjectMapper().valueToTree(row);
buyer.put("user_name", rowNode.get("work_user"));
buyer.put("item_name", rowNode.get("item_name"));
buyer.put("qty", rowNode.get("qty"));
buyers.add(buyer);
}
map.put("buyer", buyers)
return map.toString();
正确实现方式
以上实现方式是有漏洞的,究其原因是因为获取OuMemberSid那一步,调用的接口实际是一个模糊搜索 举个例子,我传递y1,返回数组里会有y10,y11,y12用户。 而且会有一人多职的情况存在,一个userName对应多个ouMemberSid,那么数组里的对象会更多。 那针对以上业务需求显然不太合适了。
以下是正确实现思路,上文示例可以作为Feign调用的示例。
修改表单
原先直接填写UserName的位置,替换为使用选择用户
组件,使用选择用户组件可以直接获得OuMemberSid
这样我们传递给后台的work_user,直接就是OuMemberSid,无需再通过接口查询了。 后台代码可以把之前案例中通过feign调用接口获取OuMemberSid的位置直接换成前台传递的参数。 这样流程发起人就可以锁定了