文章目录
1.背景
由于之前做了一个财务收付款关联管理的项目中接入了钉钉流程,用户会通过手机端提交一些钉钉收付款的流程,通过第三方接入的内部的酷应用实现自动化的控制,当用户提交的流程已经走到财务出纳审批的时候,到这一步会在我们后台中进行收付款的关联,然后会进行调用钉钉的流程接口去自动更改出纳这一步的task任务的状态的操作,这一步骤看似简单只要按照官方给的实列代码调用接口即可,其实不然,里面暗藏这很大的坑,官方也没有给出明确的说明,只有自己亲子踩过之后才知道,遇到这种无法解决的报错就算给官方提几个工单,没有付费官方人员也不晓得是啥问题,随便回复下你,这就是没有付费使用官方的态度,找个技术支持都找不到,这一步骤是这个项目的关键,调试了半个月都没啥进展,最后问题还是在官方的文档上找到的,参看下面第3点接入流程的第5点调用服务端OA相关API里面有相关新版流程和旧版流程的分辨,这一点恰恰是解决该问题的关键。
2.新版服务端的java依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>1.4.89</version>
</dependency>
3.接入流程
官方说明
https://open.dingtalk.com/document/orgapp-server/approve-or-reject-the-new-version
3.1 创建企业内部应用
3.2 获取AppKey和AppSecret
3.3 添加接口权限
3.4 获取应用访问凭证accessToken
注意:服务端API差异详情参见新旧版规范服务端API区别。
- 服务端API接口SDK下载,详情参见服务端SDK下载。
- 新版服务端API接口SDK下载,详情参见新版服务端SDK下载。
以下接口均使用服务端API接口,SDK下载详情参见新版服务端SDK。
根据步骤二中的AppKey和AppSecret,获取应用访问凭证获取企业内部应用的accessToken。
public void getAccessToken() throws Exception {
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
GetAccessTokenRequest accessTokenRequest = new GetAccessTokenRequest()
.setAppKey("din*********hgn")
.setAppSecret("9G_O************mBkhgGIO");
GetAccessTokenResponse accessToken = client.getAccessToken(accessTokenRequest);
System.out.println(JSON.toJSONString(accessToken.getBody()));
}
3.5 调用服务端OA相关API
调用新版服务端API-创建或更新审批表单模板接口,获取模板的唯一编码processCode
。
说明 :若没有保存接口返回的模板编码processCode
,钉钉管理后台版本不同,获取processCode的方式不同。登录钉钉管理后台,在首页查看版本。如下图所示,页面展示回到旧版和新版反馈,说明当前是新版。
新版钉钉管理后台:在审批模板编辑页-基础设置-页面底部查看。
旧版钉钉管理后台:在审批模板编辑页的URL中查看。
4.问题及其解决办法
问题一:“errcode”:88,“sub_code”:“60011”,“sub_msg”:"没有调用该接口的权限
{"errcode":88,"sub_code":"60011","sub_msg":"没有调用该接口的权限,接口权限申请参考:https://open.dingtalk.com/document/orgapp-server/add-api-permission","errmsg":"ding talk error[subcode=60011,submsg=没有调用该接口的权限,接口权限申请参考:https://open.dingtalk.com/document/orgapp-server/add-api-permission]","request_id":"15rf1m82ty8nz"}
DingTalkClient client = new DefaultDingTalkClient(dingDingOldApiUrlConfig.getTaskUpdateUrl());
OapiProcessWorkrecordTaskUpdateRequest req = new OapiProcessWorkrecordTaskUpdateRequest();
OapiProcessWorkrecordTaskUpdateRequest.UpdateTaskRequest obj1 = new OapiProcessWorkrecordTaskUpdateRequest.UpdateTaskRequest();
obj1.setAgentid(agentId);
obj1.setProcessInstanceId(processInstanceId);
List<OapiProcessWorkrecordTaskUpdateRequest.TaskTopVo> list3 = new ArrayList<OapiProcessWorkrecordTaskUpdateRequest.TaskTopVo>();
OapiProcessWorkrecordTaskUpdateRequest.TaskTopVo obj4 = new OapiProcessWorkrecordTaskUpdateRequest.TaskTopVo();
obj4.setTaskId(taskId);
obj4.setStatus(status);
obj4.setResult(result);
list3.add(obj4);
obj1.setTasks(list3);
req.setRequest(obj1);
OapiProcessWorkrecordTaskUpdateResponse rsp = null;
try {
rsp = client.execute(req, this.getOldAccessToken());
log.info("TaskUpdate.req:{}",JSON.toJSONString(req));
if (Objects.nonNull(rsp) && StringUtils.isNotEmpty(rsp.getCode()) && rsp.isSuccess()) {
log.info("TaskUpdate.result:true");
return Boolean.TRUE;
}
} catch (ApiException e) {
log.error(e.getMessage());
}
该问题是流程表单是旧版本的流程,修改流程任务状态的接口用的自有OA审批的接口,所以需要将接口换成官方OA审批的接口。
问题二:com.aliyun.tea.TeaException: code: 400, 审批状态异常,无法执行
com.aliyun.tea.TeaException: code: 400, 审批状态异常,无法执行 request id: 9BE05FB8-1253-7C11-994E-43A49C08B294 at com.aliyun.teaopenapi.Client.doROARequest(Client.java:403) at com.aliyun.dingtalkworkflow_1_0.Client.executeProcessInstanceWithOptions(Client.java:345)
该问题是流程表单是旧版本的流程,使用的是新版的API导致的,正确的做法是新建一个新版本的流程然后提交一个钉钉流程实例然后在使用这个新版的流程的实例id,去修改这个流程实列的Task中的某一个任务的状态即可,这个官方文档也没有明文说明,老版本的流程没有这个API,所以使用的老版本的流程在调用新版的API来修改流程实列的任务状态就会报这个错,给官方提了工单也是好几天才回复,他们也就随便应付下,他们都不晓得是啥问题,只能靠自己来解决问题了,没有出钱想要找到技术支持那是白日做梦,就想着白嫖,人家官方的态度就是随便应付一下我们,真TMD坑,当时是心中有万马奔腾但是问题还是得解决。
5.新版API相关代码分享
DingDingConfig配置类
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "dingding")
public class DingDingConfig {
private List<DingDingProperties> ddp;
}
DingDingProperties类
@Data
public class DingDingProperties {
private String appKey;
private String appSecret;
}
新版api代码接口
@Service
@Slf4j
public class DingDingServiceImpl{
@Autowired
private DingDingConfig dingDingConfig;
//获取token接口
@Override
public String getAccessToken() {
List<DingDingProperties> ddp = dingDingConfig.getDdp();
if (CollectionUtils.isEmpty(ddp)) {
throw new RuntimeException("请检查钉钉配置");
}
DingDingProperties dingDingProperties = ddp.get(0);
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
try {
com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest().setAppKey(dingDingProperties.getAppKey()).setAppSecret(dingDingProperties.getAppSecret());
GetAccessTokenResponse accessToken = client.getAccessToken(getAccessTokenRequest);
if (Objects.nonNull(accessToken) && Objects.nonNull(accessToken.getBody())) {
GetAccessTokenResponseBody body = accessToken.getBody();
log.info("DingDing accessToken :{}", body);
if (StringUtils.isNotEmpty(body.getAccessToken())) {
return body.getAccessToken();
}
}
} catch (TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(err.getMessage());
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(_err.getMessage());
}
return null;
}
//根据流程实例id获取流程实例
@Override
public GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult getProcessInstance(String processInstanceId) {
log.info("getProcessInstance.param:{}", processInstanceId);
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
try {
com.aliyun.dingtalkworkflow_1_0.Client client = new com.aliyun.dingtalkworkflow_1_0.Client(config);
com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();
getProcessInstanceHeaders.xAcsDingtalkAccessToken = this.getAccessToken();
com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest().setProcessInstanceId(processInstanceId);
GetProcessInstanceResponse processInstanceWithOptions = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new RuntimeOptions());
if (Objects.nonNull(processInstanceWithOptions)) {
GetProcessInstanceResponseBody body = processInstanceWithOptions.getBody();
if (Objects.nonNull(body) && Objects.nonNull(body.getSuccess()) && body.getSuccess().equals("true")) {
GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result = body.getResult();
log.info("getProcessInstance.result :{}", result);
return result;
}
}
} catch (TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(err.getMessage());
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(err.getMessage());
}
return null;
}
//更新流程task的状态
@Override
public Boolean TaskUpdate(String processInstanceId, Long taskId, String actionerUserId, String result) {
log.info("TaskUpdate.processInstanceId:{},taskId:{},actionerUserId:{},result:{}", processInstanceId, taskId, actionerUserId, result);
try {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
com.aliyun.dingtalkworkflow_1_0.Client client = new com.aliyun.dingtalkworkflow_1_0.Client(config);
com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceHeaders executeProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceHeaders();
executeProcessInstanceHeaders.xAcsDingtalkAccessToken = this.getAccessToken();
/*com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest.ExecuteProcessInstanceRequestFileAttachments fileAttachments0 = new com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest.ExecuteProcessInstanceRequestFileAttachments()
.setSpaceId("123")
.setFileSize("1024")
.setFileId("B1oQixxxx")
.setFileName("文件名称。")
.setFileType("file");
com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest.ExecuteProcessInstanceRequestFile file = new com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest.ExecuteProcessInstanceRequestFile()
.setPhotos(java.util.Arrays.asList(
"https://url1"
))
.setAttachments(java.util.Arrays.asList(
fileAttachments0
));*/
com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest executeProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.ExecuteProcessInstanceRequest()
.setProcessInstanceId(processInstanceId)
.setRemark("同意。")
.setResult(result)
.setActionerUserId(actionerUserId)
.setTaskId(taskId);
//.setFile(file);
ExecuteProcessInstanceResponse rsp = client.executeProcessInstanceWithOptions(executeProcessInstanceRequest, executeProcessInstanceHeaders, new RuntimeOptions());
if (Objects.nonNull(rsp)) {
ExecuteProcessInstanceResponseBody body = rsp.getBody();
if (Objects.nonNull(body) && body.getSuccess() && body.getResult()) {
log.info("TaskUpdate.result:true");
return Boolean.TRUE;
}
}
} catch (TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
err.printStackTrace();
log.error(err.getMessage());
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
err.printStackTrace();
log.error(err.getMessage());
}
return Boolean.FALSE;
}
//获取流程审批的附件下载地址
@Override
public String getDingDingProcessFileDownLoadUrl(String processInstanceId, String fileId) {
log.info("getDingDingProcessFileDownLoadUrl.processInstanceId:{},fileId:{}", processInstanceId, fileId);
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
try {
String token = this.getAccessToken();
com.aliyun.dingtalkworkflow_1_0.Client client = new com.aliyun.dingtalkworkflow_1_0.Client(config);
com.aliyun.dingtalkworkflow_1_0.models.GrantProcessInstanceForDownloadFileHeaders grantProcessInstanceForDownloadFileHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GrantProcessInstanceForDownloadFileHeaders();
grantProcessInstanceForDownloadFileHeaders.xAcsDingtalkAccessToken = token;
com.aliyun.dingtalkworkflow_1_0.models.GrantProcessInstanceForDownloadFileRequest grantProcessInstanceForDownloadFileRequest = new com.aliyun.dingtalkworkflow_1_0.models.GrantProcessInstanceForDownloadFileRequest().setProcessInstanceId("FTTrgrqLRGa0MN2cvNnKFw07551665450943").setFileId("70572744764");
GrantProcessInstanceForDownloadFileResponse grantProcessInstanceForDownloadFileResponse = client.grantProcessInstanceForDownloadFileWithOptions(grantProcessInstanceForDownloadFileRequest, grantProcessInstanceForDownloadFileHeaders, new RuntimeOptions());
if (Objects.nonNull(grantProcessInstanceForDownloadFileResponse) && Objects.nonNull(grantProcessInstanceForDownloadFileResponse.getBody())) {
GrantProcessInstanceForDownloadFileResponseBody body = grantProcessInstanceForDownloadFileResponse.getBody();
if (body.getSuccess()) {
log.info("spaceId:{}", body.getResult().getSpaceId());
log.info("fileId:{}", body.getResult().getFileId());
log.info("downloadUri:{}", body.getResult().getDownloadUri());
return body.getResult().getDownloadUri();
}
}
} catch (TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(err.getMessage());
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
log.error(_err.getMessage());
}
return "";
}
}