本文目录导读:

这是一个很典型的PHP企业开发场景,主要涉及企业微信(企业号) 与企业通讯录的对接。
通常指的是以下几个核心需求:
- 应用授权与登录:让用户通过企业微信扫码或在企业微信客户端内免密登录公司的PHP系统(如OA、CRM)。
- 通讯录同步:将企业微信后台的组织架构(部门、成员)同步到PHP系统本地数据库,或从PHP系统反向同步到企业微信。
- 消息推送:通过PHP系统向指定员工或部门发送应用消息(告警、通知、审批待办)。
下面我重点讲通讯录同步的具体实现,并给出代码示例,同时覆盖企业微信API调用的常见坑点。
准备工作 (PHP端)
获取企业微信凭证
- CorpID:企业的唯一ID,在后台“我的企业”->“企业信息”底部查看。
- CorpSecret:在“应用管理”->“自建应用”->“查看Secret”。
获取AccessToken
所有API调用的前提(有效期7200秒,需要缓存,建议存MySQL或Redis)。
<?php
function getAccessToken($corpId, $corpSecret) {
$url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={$corpId}&corpsecret={$corpSecret}";
$result = file_get_contents($url);
$data = json_decode($result, true);
if ($data['errcode'] == 0) {
return $data['access_token'];
}
// 处理错误 (大概率Secret错误)
throw new Exception("获取Token失败: " . $data['errmsg']);
}
提示:生产环境不能用
file_get_contents,建议用curl加超时。
核心:通讯录同步
企业微信通讯录API支持:
user/get:获取单个成员详情user/simplelist:获取部门成员列表(仅姓名和账号)user/list:获取部门成员详情(含手机、邮箱、职位等)department/list:获取部门列表
同步部门结构
<?php
function syncDepartments($accessToken) {
$url = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token={$accessToken}";
$result = file_get_contents($url);
$data = json_decode($result, true);
if ($data['errcode'] != 0) {
// 常见错误: 40001 (token过期), 60020 (IP不在白名单)
throw new Exception("获取部门失败: " . $data['errmsg']);
}
foreach ($data['department'] as $dept) {
$departmentId = $dept['id'];
$parentId = $dept['parentid'];
$name = $dept['name'];
$order = $dept['order']; // 排序
// 存入本地数据库 departments 表
// INSERT INTO departments (wx_dept_id, parent_id, name, sort_order)
// VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE ...
}
}
同步部门成员
<?php
function syncUsersByDepartment($accessToken, $departmentId, $fetchChild = true) {
// 注意: user/list 会返回手机号、邮箱等敏感信息,需要企业微信后台开启“通讯录权限”并设置应用可见范围
$url = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token={$accessToken}&department_id={$departmentId}&fetch_child=" . ($fetchChild ? 1 : 0);
$result = file_get_contents($url);
$data = json_decode($result, true);
if (isset($data['userlist'])) {
foreach ($data['userlist'] as $user) {
$userId = $user['userid'];
$name = $user['name'];
$mobile = $user['mobile'] ?? '';
$email = $user['email'] ?? '';
$position = $user['position'] ?? ''; // 职务
$status = $user['status']; // 1=已激活, 2=已禁用, 4=未激活, 5=退出企业
// 处理部门数组,成员可能属于多个部门
$departments = $user['department']; // 数组
// 存入本地 users 表
// INSERT INTO users (wx_userid, name, mobile, email, position, status) VALUES (?,?,?,?,?,?)
// 同时更新 user_department 关联表
// DELETE FROM user_department WHERE wx_userid=?; 然后重新插入 departments 关联
}
}
}
处理离职/删除的员工
企业微信不会通知删除事件,建议:
- 遍历获取到的所有用户ID,与本地数据库对比,不在API返回列表中的标记为离职。
- 或者利用回调URL(企业微信“回调配置”),实时接收成员变更事件。
重要事项与常见坑点
| 问题 | 解决方案 |
|---|---|
| AccessToken过期 | 每次调用API前检查本地缓存(如Redis里的过期时间),提前续期 |
| IP白名单 | 企业微信后台->管理工具->通讯录同步,必须添加PHP服务器公网IP(否则报60020) |
| 应用可见范围 | 自建应用必须设置可见的部门/成员,否则API无法获取范围外的通讯录 |
| 频率限制 | 每分钟最多调用1000次,同步大量员工时要加 usleep(500000) 减慢请求 |
| 全量同步 vs 增量更新 | 全量同步每1小时一次足够;高频场景使用回调+定时检查 |
| 部门ID的数字类型 | 企业微信部门ID是整型,不要用字符串类型存表(排序会出错) |
完整示例:使用PHP脚本同步通讯录到本地
<?php
// config.php
$corpId = 'wwxxxxxx';
$corpSecret = 'xxxxx';
$dbHost = 'localhost';
$dbUser = 'root';
$dbPass = 'pass';
$dbName = 'company_contacts';
// connection
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
// 1. 获取Token(实际生产应加缓存)
$tokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$corpId&corpsecret=$corpSecret";
$tokenResp = json_decode(file_get_contents($tokenUrl), true);
$accessToken = $tokenResp['access_token'];
// 2. 获取所有部门
$deptUrl = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=$accessToken";
$deptResp = json_decode(file_get_contents($deptUrl), true);
foreach ($deptResp['department'] as $dept) {
$stmt = $pdo->prepare("INSERT INTO departments (wx_dept_id, parent_id, name) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name=VALUES(name)");
$stmt->execute([$dept['id'], $dept['parentid'], $dept['name']]);
}
// 3. 遍历部门获取成员(这里只取根部门1,递归请自行实现)
foreach ($deptResp['department'] as $dept) {
$depId = $dept['id'];
$userUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=$accessToken&department_id=$depId&fetch_child=1";
$userResp = json_decode(file_get_contents($userUrl), true);
if (isset($userResp['userlist'])) {
foreach ($userResp['userlist'] as $user) {
$stmt = $pdo->prepare("INSERT INTO users (wx_userid, name, mobile, email, status) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name=VALUES(name), mobile=VALUES(mobile), status=VALUES(status)");
$stmt->execute([$user['userid'], $user['name'], $user['mobile'], $user['email'], $user['status']]);
// 处理多部门关系
$pdo->prepare("DELETE FROM user_department WHERE wx_userid=?")->execute([$user['userid']]);
foreach ($user['department'] as $did) {
$pdo->prepare("INSERT INTO user_department (wx_userid, wx_dept_id) VALUES (?, ?)")->execute([$user['userid'], $did]);
}
}
}
sleep(0.3); // 控制API调用频率
}
echo "通讯录同步完成";
进阶:使用企业微信JS-SDK实现通讯录选择
如果需要在Web端选择企业微信联系人(例如审批流程选择审批人),可以使用:
// 前端引入JS-SDK,调用选择接口
wx.invoke('selectExternalContact', {
filterType: 0, // 0全部/1仅选择单个
}, function(res){
// res.userId 或者 res.userIds (数组)
});
前提:需要在企业微信JS接口安全域名中配置你的网页域名。
企业微信通讯录同步的核心在于:
- 正确处理Token缓存(避免重复获取和过期)。
- 处理好部门树形结构(递归或队列方式同步)。
- 注意API的频率限制(大批量同步要减速)。
- 用户敏感数据的保护(手机号、邮箱不要明文存日志)。
如果你的PHP项目是Laravel或ThinkPHP框架,可以考虑使用对应的企业微信SDK包,
- w7corp/easywechat(非常成熟,支持企业微信通讯录/消息/会话存档等)
- crmeb/wechat (国内常见)
需要我进一步展示 EasyWeChat 在 Laravel 中的使用方式 或 消息推送(如发送文本卡片消息) 的代码吗?