企业内部的计划管理、项目立项、预算变更等审批流程,常常需要与钉钉/企业微信打通,以实现移动端审批。本文以JVS企业计划系统为例,详细讲解如何通过API和回调机制实现审批流的双向集成,包含Java代码示例、异常处理及常见坑点。
一、集成目标与背景
在企业日常运营中,计划管理系统中的审批环节往往成为效率瓶颈:
- 审批人经常出差,无法及时登录PC端处理
- 多个审批流程分散在不同模块,缺乏统一待办入口
- 审批完成后,业务单据状态需要手动同步,易遗漏
通过与钉钉审批流集成,可以实现:
- 审批人直接在钉钉端查看并处理待办
- 审批结果实时回写至业务系统
- 无需额外开发移动端审批页面
二、准备工作
钉钉侧:
- 登录钉钉开放平台,创建企业内部应用
- 获取 AppKey、AppSecret、AgentId、CorpId
- 申请审批流接口权限(包括获取模板、发起实例、接收回调)
计划系统侧:
- 确认系统版本支持API扩展(本文示例基于v2.5)
- 配置好需要审批的业务单据(如项目立项、预算调整)
三、集成步骤详解
3.1 钉钉审批模板配置
- 在钉钉管理后台创建审批模板(如“项目立项审批”)
- 记录模板ID(processCode)
- 确定表单字段与业务单据字段的映射关系
3.2 计划系统配置
- 进入“集成中心” → “钉钉集成”
- 填写 CorpId、AppKey、AppSecret,测试连接
- 绑定业务单据类型与钉钉审批模板ID
- 设置回调URL(系统自动生成),并在钉钉应用中配置该URL为“事件订阅”地址
四、核心代码实现
4.1 发起审批实例
当用户在计划系统中提交审批时,调用钉钉API创建审批实例:
java
// 构建审批表单数据(钉钉要求的格式)
List<FormComponentValue> formValues = new ArrayList<>();
formValues.add(new FormComponentValue("项目名称", project.getName()));
formValues.add(new FormComponentValue("预算金额", String.valueOf(project.getBudget())));
formValues.add(new FormComponentValue("申请理由", project.getReason()));
// 调用钉钉API
DingTalkClient client = new DefaultDingTalkClient(
"https://oapi.dingtalk.com/topapi/processinstance/create"
);
OapiProcessinstanceCreateRequest req = new OapiProcessinstanceCreateRequest();
req.setProcessCode(dingtalkTemplateId);
req.setFormComponentValues(formValues);
req.setOriginatorUserId(userId); // 钉钉用户ID
OapiProcessinstanceCreateResponse rsp = client.execute(req, accessToken);
if (rsp.isSuccess()) {
// 保存钉钉返回的审批实例ID,用于后续回调匹配
project.setDingtalkInstanceId(rsp.getProcessInstanceId());
projectService.save(project);
}
4.2 接收审批结果回调
钉钉审批完成后,会向配置的回调URL推送结果,接口实现如下:
java
@PostMapping("/api/dingtalk/callback")
public String callback(@RequestBody DingTalkCallbackBody body) {
String instanceId = body.getProcessInstanceId();
String result = body.getResult(); // "agree" 或 "refuse"
// 匹配业务单据
Project project = projectService.findByDingtalkInstanceId(instanceId);
if (project == null) {
return "fail";
}
// 更新状态
if ("agree".equals(result)) {
project.setStatus(ProjectStatus.APPROVED);
} else if ("refuse".equals(result)) {
project.setStatus(ProjectStatus.REJECTED);
}
projectService.save(project);
return "success";
}
4.3 稳定性增强:重试与幂等
1. 获取accessToken失败重试
java
private String getAccessTokenWithRetry(int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
return getAccessToken();
} catch (Exception e) {
if (i == maxRetries - 1) throw e;
Thread.sleep(1000L * (1 << i)); // 指数退避
}
}
return null;
}
2. 回调幂等处理
钉钉可能重复推送回调,需保证接口幂等:
java
// 使用Redis分布式锁
String lockKey = "dingtalk:callback:" + instanceId;
if (!redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS)) {
return "processing";
}
try {
Project p = projectService.findByDingtalkInstanceId(instanceId);
if (p.getStatus() == ProjectStatus.APPROVED ||
p.getStatus() == ProjectStatus.REJECTED) {
return "success"; // 已处理过
}
// 正常处理...
} finally {
redisLock.unlock();
}
五、支持企业微信
同样的逻辑可应用于企业微信审批流,只需替换API地址和认证方式。
| 对比项 | 钉钉 | 企业微信 |
|---|---|---|
| API网关 | oapi.dingtalk.com | qyapi.weixin.qq.com |
| 认证参数 | AppKey+AppSecret | CorpId+Secret |
| 审批发起接口 | /topapi/processinstance/create | /cgi-bin/oa/applyevent |
| 回调格式 | JSON含processInstanceId、result | JSON含sp_no、sp_status |
六、常见问题与排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 回调接收不到 | 回调URL不可达、防火墙拦截 | 检查网络连通性;将服务器IP加入钉钉白名单 |
| accessToken获取失败 | AppKey/Secret错误或IP不在白名单 | 核对参数;添加出口IP到白名单 |
| 审批无法发起 | 模板ID错误或用户ID不存在 | 确认模板ID;确保发起人存在于钉钉通讯录 |
| 回调重复处理 | 钉钉重试机制 | 实现幂等(根据instanceId去重) |
七、总结
通过API对接,企业可以低成本、高效率地将计划系统的审批流程延伸至钉钉/企微。本文提供的代码示例已在生产环境稳定运行,核心要点包括:
- 审批模板与业务单据的映射关系配置
- 发起审批实例与回调接收的完整闭环
- 重试、幂等、日志等可靠性设计
后续可根据业务需要,扩展至更多审批场景(如采购申请、合同审批等)。
你的团队在集成钉钉审批时遇到过哪些坑?欢迎评论区分享。