Files
zyt/server/app/common/service/wechat/WechatWorkService.php
T
2026-04-18 15:54:14 +08:00

275 lines
8.4 KiB
PHP

<?php
declare(strict_types=1);
namespace app\common\service\wechat;
use think\facade\Cache;
use think\facade\Log;
/**
* 企业微信服务类
* @see https://developer.work.weixin.qq.com/document/
*/
class WechatWorkService
{
private $corpId;
private $secret;
private $accessToken;
public function __construct()
{
$this->corpId = config('pay.wechat_work.corp_id');
$this->secret = config('pay.wechat_work.external_pay_secret');
}
/**
* @notes 获取access_token
* @see https://developer.work.weixin.qq.com/document/path/91039
*/
private function getAccessToken(): string
{
if ($this->accessToken) {
return $this->accessToken;
}
// 从缓存获取
$cacheKey = 'qywx_access_token';
$token = Cache::get($cacheKey);
if ($token) {
$this->accessToken = $token;
return $token;
}
// 请求新token
$url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken';
$params = [
'corpid' => $this->corpId,
'corpsecret' => $this->secret,
];
$response = $this->httpGet($url, $params);
if (isset($response['access_token'])) {
$token = $response['access_token'];
$expiresIn = $response['expires_in'] ?? 7200;
// 缓存token,提前5分钟过期
Cache::set($cacheKey, $token, $expiresIn - 300);
$this->accessToken = $token;
return $token;
}
throw new \Exception('获取access_token失败: ' . ($response['errmsg'] ?? '未知错误'));
}
/**
* @notes 获取部门成员列表
* @see https://developer.work.weixin.qq.com/document/path/90200
*/
public function getDepartmentUserList(int $departmentId = 1, bool $fetchChild = true): array
{
$accessToken = $this->getAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/user/list';
$params = [
'access_token' => $accessToken,
'department_id' => $departmentId,
'fetch_child' => $fetchChild ? 1 : 0,
];
$response = $this->httpGet($url, $params);
Log::info('企业微信-获取部门成员列表响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
if (isset($response['userlist'])) {
return $response['userlist'];
}
Log::error('获取部门成员列表失败: ' . json_encode($response));
return [];
}
/**
* @notes 获取成员的客户列表
* @see https://developer.work.weixin.qq.com/document/path/92113
* @param string $userId 成员ID
* @return array|false 返回客户列表数组,权限错误返回false
*/
public function getExternalContactList(string $userId)
{
$accessToken = $this->getAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list';
$params = [
'access_token' => $accessToken,
'userid' => $userId,
];
$response = $this->httpGet($url, $params);
Log::info('企业微信-获取成员客户列表响应 userId=' . $userId . ': ' . json_encode($response, JSON_UNESCAPED_UNICODE));
// 检查是否是权限错误
if (isset($response['errcode']) && $response['errcode'] == 48002) {
Log::error('API权限不足(48002): 请在企业微信管理后台开启"客户联系"权限');
return false;
}
if (isset($response['external_userid'])) {
return $response['external_userid'];
}
// 如果没有客户,返回空数组(不记录错误)
if (isset($response['errcode']) && $response['errcode'] == 0) {
return [];
}
Log::error('获取客户列表失败: ' . json_encode($response));
return [];
}
/**
* 批量获取指定成员添加的客户详情(分页,单次最多 100 条)
*
* @see https://developer.work.weixin.qq.com/document/path/92994
* @return array<string, mixed> 含 errcode、external_contact_list、next_cursor 等
*/
public function batchGetExternalContactByUser(string $userId, string $cursor = '', int $limit = 100): array
{
$accessToken = $this->getAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user?access_token=' . urlencode($accessToken);
$body = [
'userid_list' => [$userId],
'cursor' => $cursor,
'limit' => min(100, max(1, $limit)),
];
return $this->httpPostJson($url, $body);
}
/**
* @notes 获取客户详情
* @see https://developer.work.weixin.qq.com/document/path/92114
*/
public function getExternalContactDetail(string $externalUserId): array
{
$accessToken = $this->getAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get';
$params = [
'access_token' => $accessToken,
'external_userid' => $externalUserId,
];
$response = $this->httpGet($url, $params);
if (isset($response['external_contact'])) {
return $response;
}
Log::error('获取客户详情失败: ' . json_encode($response));
return [];
}
/**
* @notes 检查应用权限(用于诊断)
* @return array 返回应用的权限信息
*/
public function checkAppPermissions(): array
{
try {
$accessToken = $this->getAccessToken();
// 尝试获取应用详情
$url = 'https://qyapi.weixin.qq.com/cgi-bin/agent/get';
$params = [
'access_token' => $accessToken,
'agentid' => config('pay.wechat_work.agent_id', 1000002),
];
$response = $this->httpGet($url, $params);
return [
'success' => isset($response['agentid']),
'data' => $response,
'message' => isset($response['errmsg']) ? $response['errmsg'] : 'OK',
];
} catch (\Throwable $e) {
return [
'success' => false,
'data' => [],
'message' => $e->getMessage(),
];
}
}
/**
* @notes HTTP GET请求
*/
private function httpGet(string $url, array $params = []): array
{
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
Log::error('HTTP请求失败: ' . $url . ', HTTP Code: ' . $httpCode);
return [];
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('JSON解析失败: ' . $response);
return [];
}
return $result;
}
/**
* POST JSON
*/
private function httpPostJson(string $url, array $body): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=UTF-8',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
Log::error('HTTP POST 失败: ' . $url . ', HTTP Code: ' . $httpCode);
return [];
}
$result = json_decode((string) $response, true);
if (!is_array($result)) {
Log::error('JSON 解析失败: ' . $response);
return [];
}
return $result;
}
}