本文目录导读:

- 📖 目录导读
- 钉钉审批流到底是什么?—— 核心概念与适用场景
- PHP对接钉钉审批流的三种主流方案对比
- 实战前置准备:创建应用、获取凭证、配置权限
- 核心接口详解:发起审批、查询实例、回调通知
- 代码实战:PHP封装钉钉审批流SDK(附完整示例)
- 高频踩坑与性能优化
- 常见问题问答(FAQ)
- 总结与延伸学习建议
PHP如何集成钉钉审批流?从零到实战的完整指南(附代码示例)
📖 目录导读
- 钉钉审批流到底是什么?—— 核心概念与适用场景
- PHP对接钉钉审批流的三种主流方案对比
- 实战前置准备:创建应用、获取凭证、配置权限
- 核心接口详解:发起审批、查询实例、回调通知
- 代码实战:PHP封装钉钉审批流SDK(附完整示例)
- 高频踩坑与性能优化(含超时、幂等、签名)
- 常见问题问答(FAQ)
- 总结与延伸学习建议
钉钉审批流到底是什么?—— 核心概念与适用场景
钉钉审批流是钉钉开放平台提供的一套企业级工作流引擎,允许开发者通过API创建、查询、撤销审批实例,并接收审批结果回调,它解决了企业内部“请假、报销、采购、合同”等场景的线上化流转问题。
对于PHP开发者来说,核心需求通常是:将自建系统(如OA、ERP)中的业务数据,通过钉钉审批流实现多人会签、或签、条件分支等复杂流程。
适用场景举例:
- 员工在自研系统中发起请假,审批人收到钉钉待办。
- 财务系统推送报销单,部门主管 + 财务总监依次审批。
- 合同系统发起用印申请,法务与总经理并行审批。
PHP对接钉钉审批流的三种主流方案对比
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 官方API + 自研封装 | 灵活、可控、无额外依赖 | 开发量大需处理细节 | 有后端团队、需要深度定制 |
| 使用开源SDK(如dingtalk-sdk) | 开发快、社区维护 | 依赖第三方,需审查代码 | 中小型项目、追求效率 |
| 低代码/HTTP触发(如钉钉连接器) | 零代码、配置即用 | 灵活性差、数据模型受限 | 轻量场景、快速验证 |
✅ 本文推荐方案1,因为PHP生态中针对钉钉审批流的成熟SDK较少,且自研封装能让你彻底掌握底层逻辑。
实战前置准备:创建应用、获取凭证、配置权限
在写代码之前,必须完成以下3步:
1 创建企业内部应用
- 登录 钉钉开放平台 → 开发者后台 → 创建应用。
- 记录 AppKey 和 AppSecret(后续获取access_token用)。
2 配置权限范围
- 在应用权限管理中,必须添加:
Contact.User.Read(获取用户信息)Process.Instance.Create(发起审批)Process.Instance.Read(读取实例)Process.Instance.Write(撤销/审批)
- 申请权限后需要管理员审批,开发阶段可选用“测试企业”。
3 设置服务器出口IP白名单
钉钉API要求请求IP在白名单内,否则返回Forbidden错误,务必在应用安全设置中配置服务器公网IP。
核心接口详解:发起审批、查询实例、回调通知
钉钉审批流最核心的3个接口(基于旧版API v1.0,新版为v2.0,但逻辑一致):
1 获取AccessToken
GET https://oapi.dingtalk.com/gettoken?appkey=xxx&appsecret=xxx
返回access_token,有效期7200秒,需缓存。
2 发起审批实例(processinstance/create)
必传参数:
process_code:审批流模板编号(在钉钉管理后台-审批模板中获取)originator_user_id:发起人useriddept_id:发起人部门IDform_component_values:表单值数组,格式如:[ {"name":"请假类型","value":"年假"}, {"name":"开始时间","value":"2025-01-01 09:00"} ]approvers:审批人列表(逗号分隔),或使用approvers_v2(支持会签/或签)
3 查询审批实例详情(processinstance/get)
传入process_instance_id,返回包括当前审批人、状态(running/completed/terminated)、操作记录等。
4 回调通知
- 在应用后台配置审批事件回调URL(如
https://yourdomain.com/webhook/dingtalk)。 - 钉钉会推送
bpms_instance_change事件,需做加解密(AES + token)+ 响应success字符串。
代码实战:PHP封装钉钉审批流SDK(附完整示例)
我们先写一个基础的DingTalkClient类,包含认证和发起审批方法。
<?php
class DingTalkClient {
private $appKey;
private $appSecret;
private $accessToken;
private $tokenExpireTime = 0;
public function __construct($appKey, $appSecret) {
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}
// 获取access_token(带缓存)
public function getAccessToken() {
if ($this->accessToken && time() < $this->tokenExpireTime) {
return $this->accessToken;
}
$url = 'https://oapi.dingtalk.com/gettoken?appkey=' . $this->appKey . '&appsecret=' . $this->appSecret;
$result = $this->httpRequest($url);
if (isset($result['access_token'])) {
$this->accessToken = $result['access_token'];
$this->tokenExpireTime = time() + 7000; // 提前200秒失效
return $this->accessToken;
}
throw new Exception('获取token失败: ' . json_encode($result));
}
// 发起审批
public function createProcessInstance($processCode, $userId, $deptId, $formData, $approvers) {
$token = $this->getAccessToken();
$url = 'https://oapi.dingtalk.com/topapi/processinstance/create?access_token=' . $token;
$params = [
'process_code' => $processCode,
'originator_user_id' => $userId,
'dept_id' => $deptId,
'form_component_values' => $formData,
'approvers_v2' => $approvers // 示例:[{"user_ids":["manager123"],"type":"AND"}]
];
$result = $this->httpRequest($url, json_encode($params), 'POST');
return $result;
}
// 查询审批实例详情
public function getProcessInstance($instanceId) {
$token = $this->getAccessToken();
$url = 'https://oapi.dingtalk.com/topapi/processinstance/get?access_token=' . $token;
$params = ['process_instance_id' => $instanceId];
$result = $this->httpRequest($url, json_encode($params), 'POST');
return $result['process_instance'] ?? null;
}
private function httpRequest($url, $postData = null, $method = 'GET') {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
} else {
curl_setopt($ch, CURLOPT_HTTPGET, 1);
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// 使用示例
$client = new DingTalkClient('your_appkey', 'your_secret');
$formData = [
['name' => '请假事由', 'value' => '生病'],
['name' => '请假天数', 'value' => '2']
];
$approvers = [
['user_ids' => ['manager001'], 'type' => 'AND']
];
$result = $client->createProcessInstance('PROC-XXXX', 'user123', 123456, $formData, $approvers);
print_r($result);
关键点解释:
approvers_v2中的AND表示会签(所有人都要审批),OR表示或签(一人通过即可)。- 表单控件Name必须与钉钉后台审批模板的控件标识完全一致(通常是中文)。
高频踩坑与性能优化
1 坑1:AccessToken缓存问题
若每个请求都重新获取token,会被限流(每秒最多20次)。必须使用Redis或进程内缓存。
2 坑2:审批人userid获取
originator_user_id和审批人必须是userid而非手机号或邮箱,可通过user/getbyunionid接口根据unionid换取。
3 坑3:回调URL需要内网穿透测试
本地开发时使用ngrok或cpolar映射公网地址,并确保回调URL能响应success(不带引号)。
4 性能优化建议
- 开启curl长连接(
CURLOPT_TCP_KEEPALIVE),复用TCP。 - 批量查询审批状态时,使用
processinstance/batchget(每次最多50个)。 - 日志记录请求耗时与钉钉返回的
requestid,便于排障。
常见问题问答(FAQ)
Q1:PHP发起审批时报错“审批模板不存在”,怎么排查?
检查
process_code是否从钉钉管理后台复制完整,注意大小写,另需确认应用是否有权限访问该模板(在审批模板的“可见范围”中添加应用)。
Q2:如何实现“指定角色”审批,而非指定人?
使用
approvers_v2中的type:"AND"+user_ids为空,同时传入cc_position参数?不,正确做法是使用approvers_v2的acting_type字段,或者将角色成员通过接口动态拉取并传入具体人员。
Q3:钉钉回调事件验签失败怎么办?
确保正确使用AES密钥解密,且加密方式为官方要求的“加解密库”,PHP实现可参考官方的
DingTalkEncryptor类,注意Token和AesKey必须与应用后台完全一致。
Q4:审批完成后如何自动通知业务系统?
在回调事件中监听
bpms_instance_change状态为finish,然后写队列触发业务逻辑(如更新订单状态、发送邮件)。
总结与延伸学习建议
通过本文,你已经掌握了PHP对接钉钉审批流的全链路:从创建应用到发起审批,再到处理回调,核心要点是:
- 使用官方API自研封装,保持灵活性。
- 妥善处理token缓存和错误重试。
- 利用回调机制实现状态同步而非轮询。
延伸建议:
- 深入学习钉钉新版API
v2.0(路径/v2.0/processes),支持更多高级特性如条件分支。 - 如果审批流逻辑复杂(并发、驳回重填),考虑引入状态机设计模式管理审批生命周期。
- 可部署为微服务,通过RabbitMQ解耦审批与业务主链路。
就打开你的IDE,尝试用PHP跑通第一个审批实例吧!如果有具体报错,欢迎在评论区留言讨论。
(文章完)