在上面的《十一、外部系统通过api发起流程》和《十二、通知外部系统流程状态变化》两个步骤中看出:
外部系统对接流程平台,需要编写3个操作:
1.向流程平台发起流程
2.给流程平台回调的post接口
3.用实例id从流程平台查询流程状态
为了方便对接,开发了一个简易的sdk,方便外部系统对接,钉钉也是类似的有sdk。
sdk核心方法:
1.发起流程
2.根核流程平台分配给外部系统的appId、appSecret生成接口签名
sdk代码模块:
签名工具类:
/**
* @Author: ltx
* @Date: 2020-10-20 17:29:40
* @Description: 签名工具类
*/
public class SignUtils {
private static final Logger log = LoggerFactory.getLogger(SignUtils.class);
/**
* 生成签名header
* @param appId
* @param appSecret
* @param body
* @return Map<String, String>
*/
public static Map<String, String> sign(String appId, String appSecret, Map<String, Object> body) {
if (StrUtil.isBlank(appId)) {
throw new ActException("appId不能为空!");
}
if (StrUtil.isBlank(appSecret)) {
throw new ActException("appSecret不能为空!");
}
//参数-header
Map<String, String> header = new HashMap<>(16);
//app标识-标识来自于哪个第三方应用
header.put("appId", appId);
//使用雪花算法生成流水id
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
header.put("nonce", String.valueOf(snowflake.nextId()));
//时间戳-接口有效期,比如此调用超过10分钟失效
header.put("timestamp", "" + System.currentTimeMillis());
String stringA = MapUtil.sortJoin(header, "&", "=", true);
String stringB = MapUtil.sortJoin(body, "&", "=", true);
//生成签名--使用摘要算法
String stringSignTemp = stringA + stringB + appSecret;
//头数据字符串+请求参数字符串+key
String sign = SecureUtil.sha256(stringSignTemp);
// log.info("sign: {}", sign.toUpperCase());
// 头里面放入sign
header.put("sign", sign.toUpperCase());
// String stringH = MapUtil.sortJoin(header, "&", "=", true);
// log.debug("header: {}", stringH);
// log.debug("body: {}", stringB);
return header;
}
/**
* 生成签名header
* @param appId
* @param appSecret
* @return Map<String, String>
*/
public static Map<String, String> sign(String appId, String appSecret) {
return sign(appId, appSecret, null);
}
}
审批流sdk测试用例:
/**
* 审批流sdk测试
* @Author: ltx
* @Date: 2020/10/20 09:44
* @Description:
*/
public class ActTest {
private static final Logger log = LoggerFactory.getLogger(ActTest.class);
/**
* 这几个建议放配置文件
*/
private String appId = "869988981";
private String appSecret = "XAycmHSwRPkLOagfPKqJpDvUVRWdGVRdWi5y247wzq41l94KQIRQ6FmrPW489Mm9";
//审批流url
private String serverUrl = "http://localhost:8888";
//启动流程接口
private String startProcessInstanceUrl = "/openApi/startProcessInstance";
//查询流程状态接口
private String getProcessInstanceStatusById = "/openApi/getProcessInstanceStatusById/";
//查询流程明细接口
private String getProcessInstanceById = "/openApi/getProcessInstanceById/";
/**
* 发起流程
*/
@Test
public void startProcessInstance() {
//请求地址
String apiUrl = serverUrl + startProcessInstanceUrl;
//生成签名客户端
IActClient actClient = new ActClientImpl(apiUrl, appId, appSecret);
//构建发起流程的对象
StartActDataDto startActDataDto = new StartActDataDto();
//类型编码
startActDataDto.setProcessCode("89171873-0076-4E81-8652-48D2A0B724DA");
//发起人id
startActDataDto.setStartUserId(1019);
//审批实例发起人的手机号, 发起流程, startUserId和startUserPhone, 要必填一个
// startActDataDto.setStartUserPhone("19107220912");
//表单数据
HashMap<String, Object> formValues = new HashMap<>(16);
formValues.put("事由", "提点用用, 望批准!");
formValues.put("提现金额", 88);
startActDataDto.setFormValues(formValues);
String processInstanceId = actClient.startProcessInstance(startActDataDto);
log.info("实例id: {}", processInstanceId);
}
/**
* 查询流程状态
*/
@Test
public void getProcessInstanceStatusById() {
//请求地址
String apiUrl = serverUrl + getProcessInstanceStatusById;
//生成签名客户端
IActClient actClient = new ActClientImpl(apiUrl, appId, appSecret);
PostDataDto postDataDto = actClient.getProcessInstanceStatusById("39cd9ab7-1371-11eb-853f-1831bfdf48c6");
log.info("流程状态对象: {}", postDataDto);
}
}
提供给流程平台post回调的接口实例:
@ApiOperation("xxxx回调接口")
@PostMapping(value = "/actBack")
public R<String> miaodiApplyLogNewReview(@RequestBody @ApiParam(value = "审批流程返回数据") PostDataDto postDataDto)throws Exception {
log.info("回调postDataDto: {}", postDataDto);
//TODO 你的代码
return R.ok("ok");
}
PostDataDto实体对象:
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "回调业务系统参数dto")
public class PostDataDto implements Serializable {
@ApiModelProperty(value = "流程实例id")
private String processInstanceId;
@ApiModelProperty(value = "审批流的唯一码")
private String processCode;
@ApiModelProperty(value = "状态(1:审核通过,2:审核不通过,3:待审核,4:已撤单)")
private Integer status = -1;
@ApiModelProperty(value = "实例创建时间")
private Date createTime;
@ApiModelProperty(value = "审批结束时间")
private Date finishTime;
}
sdk打包时候注意:打包配置将sdk的pom项目依赖打到一个jar包里
可以打下源码包,方便外部系统查看sdk源码,最终打包文件:
外部系统使用sdk:
1.将act-sdk-1.0.0-jar-with-dependencies.jar放项目的lib目录下。
2.pom.xml里面增加:
<!--审批流sdk-->
<dependency>
<groupId>com.qmy</groupId>
<artifactId>act-sdk</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/act-sdk-1.0.0-jar-with-dependencies.jar</systemPath>
</dependency>
3.加includeSystemScope:本地可以运行,但是只要使用maven打包就不行,因为maven没有将本地的jar也打到生成的包中
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 作用:项目打成jar的同时将本地jar包也引入进去 -->
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
项目整合sdk最佳实践方案
sdk为公共模块,可在各个场景中使用,常见的公共模块使用场景和最佳实践:
- 一方库:本工程中的各模块的相互依赖,比如微服务的公共模块;(sdk不适用此方案,sdk本来就是要提供给外部团队使用的)
- 二方库:sdk给公司内部的其他团队使用;(可以使用nexus搭建企业自己的私有maven仓库,将sdk传到nexus私有仓库中供企业内部其他团队使用,nexus私有仓库搭建详见我的另外一篇文章《Nexus部署和使用(笔记)》 )
- 三方库:sdk给公司之外的第三方团队使用;(本文以上主要讲解的内容为此方案)