企业微信外部联系人同步

This commit is contained in:
Guoxianpeng
2026-04-22 12:19:24 +08:00
parent 906684c1ed
commit 6e37c6ec05
25 changed files with 3362 additions and 325 deletions
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace app\common\command;
use app\adminapi\logic\qywx\CustomerLogic;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
/**
* 企业微信客户同步命令
* 用法: php think qywx:sync_customer
*/
class QywxSyncCustomer extends Command
{
protected function configure()
{
$this->setName('qywx:sync_customer')
->setDescription('同步企业微信客户到本地数据库')
->addArgument('action', Argument::OPTIONAL, '执行操作(sync/status)', 'sync');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action') ?: 'sync';
switch ($action) {
case 'sync':
$this->syncCustomers($output);
break;
case 'status':
$this->showStatus($output);
break;
default:
$output->error('未知操作: ' . $action);
break;
}
}
/**
* 同步客户
*/
private function syncCustomers(Output $output)
{
$output->info('开始同步企业微信客户...');
$result = CustomerLogic::syncCustomers();
if ($result === false) {
$output->error('同步失败: ' . CustomerLogic::getError());
return;
}
$output->info('同步完成!');
$output->info('总计同步: ' . ($result['sync_count'] ?? 0) . ' 个客户');
$output->info('新增: ' . ($result['new_count'] ?? 0));
$output->info('更新: ' . ($result['update_count'] ?? 0));
}
/**
* 显示同步状态
*/
private function showStatus(Output $output)
{
$stats = CustomerLogic::getStats();
$output->info('=== 企业微信客户同步状态 ===');
$output->info('客户总数: ' . $stats['total']);
$output->info('今日新增: ' . $stats['today']);
$output->info('同步状态: ' . $stats['syncStatusText']);
$output->info('最后同步: ' . $stats['lastSync']);
}
}
@@ -47,14 +47,15 @@ class LikeAdminAllowMiddleware
*/
public function handle($request, Closure $next, ?array $header = []): mixed
{
// 如果是OPTIONS预检请求,直接返回成功响应,不继续处理
if (strtoupper($request->method()) === 'OPTIONS') {
$this->setCorsHeaders();
return response('', 200);
}
// 设置跨域头
$this->setCorsHeaders();
// 如果是OPTIONS请求,直接返回响应
if (strtoupper($request->method()) === 'OPTIONS') {
return response();
}
// 安装检测
$install = file_exists(root_path() . '/config/install.lock');
if (!$install) {
@@ -72,10 +73,11 @@ class LikeAdminAllowMiddleware
*/
private function setCorsHeaders(): void
{
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Headers' => implode(', ', self::ALLOWED_HEADERS),
'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, post',
'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Max-Age' => '1728000',
'Access-Control-Allow-Credentials' => 'true'
];
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace app\common\model;
use think\Model;
/**
* 客户添加日志模型
*/
class QywxCustomerAddLog extends Model
{
protected $table = 'zyt_qywx_customer_add_log';
protected $autoWriteTimestamp = true;
// 字段类型
protected $type = [
'staff_id' => 'integer',
'add_time' => 'datetime',
'created_at' => 'datetime',
];
// 隐藏字段
protected $hidden = [];
// 验证规则
protected $rule = [
'external_user_id' => 'require|max:50',
'staff_id' => 'require|integer',
'staff_name' => 'require|max:50',
'add_time' => 'require|datetime',
'user_info' => 'require',
'source' => 'require|max:20',
];
// 错误信息
protected $message = [
'external_user_id.require' => '外部用户ID不能为空',
'external_user_id.max' => '外部用户ID不能超过50个字符',
'staff_id.require' => '员工ID不能为空',
'staff_id.integer' => '员工ID必须是整数',
'staff_name.require' => '员工姓名不能为空',
'staff_name.max' => '员工姓名不能超过50个字符',
'add_time.require' => '添加时间不能为空',
'add_time.datetime' => '添加时间格式不正确',
'user_info.require' => '用户信息不能为空',
'source.require' => '来源不能为空',
'source.max' => '来源不能超过20个字符',
];
/**
* @notes 获取员工加粉统计
*/
public static function getStaffAddCount($staffId, $startDate, $endDate)
{
return self::where('staff_id', $staffId)
->where('add_time', '>=', $startDate)
->where('add_time', '<=', $endDate)
->count();
}
/**
* @notes 获取所有员工加粉统计
*/
public static function getStaffAddCountList($startDate, $endDate)
{
return self::field('staff_id, staff_name, count(*) as add_count')
->where('add_time', '>=', $startDate)
->where('add_time', '<=', $endDate)
->group('staff_id, staff_name')
->select();
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace app\common\model;
use think\Model;
/**
* 客户操作日志模型
*/
class QywxCustomerOperationLog extends Model
{
protected $table = 'zyt_qywx_customer_operation_log';
protected $autoWriteTimestamp = true;
// 字段类型
protected $type = [
'operator_id' => 'integer',
'staff_id' => 'integer',
'created_at' => 'datetime',
];
// 隐藏字段
protected $hidden = [];
// 验证规则
protected $rule = [
'operation_type' => 'require|max:20',
'operator_id' => 'require|integer',
'operator_name' => 'require|max:50',
];
// 错误信息
protected $message = [
'operation_type.require' => '操作类型不能为空',
'operation_type.max' => '操作类型不能超过20个字符',
'operator_id.require' => '操作人ID不能为空',
'operator_id.integer' => '操作人ID必须是整数',
'operator_name.require' => '操作人姓名不能为空',
'operator_name.max' => '操作人姓名不能超过50个字符',
];
/**
* @notes 记录操作日志
*/
public static function record($operationType, $operatorId, $operatorName, $externalUserId = null, $staffId = null, $content = null)
{
$log = new self();
$log->operation_type = $operationType;
$log->operator_id = $operatorId;
$log->operator_name = $operatorName;
$log->external_user_id = $externalUserId;
$log->staff_id = $staffId;
$log->content = $content;
$log->save();
return $log;
}
}
@@ -4,16 +4,14 @@ declare(strict_types=1);
namespace app\common\model;
use think\model\concern\SoftDelete;
/**
* 企业微信外部联系人模型
*/
class QywxExternalContact extends BaseModel
{
use SoftDelete;
protected $name = 'qywx_external_contact';
protected $autoWriteTimestamp = 'datetime';
protected $createTime = 'created_at';
protected $updateTime = 'updated_at';
protected $deleteTime = 'delete_time';
protected $defaultSoftDelete = 0;
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace app\common\model;
use think\Model;
/**
* 员工二维码模型
*/
class QywxStaffQrcode extends Model
{
protected $table = 'zyt_qywx_staff_qrcode';
protected $autoWriteTimestamp = true;
// 字段类型
protected $type = [
'staff_id' => 'integer',
'expire_time' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// 隐藏字段
protected $hidden = [];
// 验证规则
protected $rule = [
'staff_id' => 'require|integer',
'staff_name' => 'require|max:50',
'scene' => 'require|max:100|unique:qywx_staff_qrcode',
'qrcode_url' => 'require|max:255',
'expire_time' => 'require|datetime',
];
// 错误信息
protected $message = [
'staff_id.require' => '员工ID不能为空',
'staff_id.integer' => '员工ID必须是整数',
'staff_name.require' => '员工姓名不能为空',
'staff_name.max' => '员工姓名不能超过50个字符',
'scene.require' => '场景值不能为空',
'scene.max' => '场景值不能超过100个字符',
'scene.unique' => '场景值已存在',
'qrcode_url.require' => '二维码URL不能为空',
'qrcode_url.max' => '二维码URL不能超过255个字符',
'expire_time.require' => '过期时间不能为空',
'expire_time.datetime' => '过期时间格式不正确',
];
}
+1 -1
View File
@@ -9,5 +9,5 @@ namespace app\common\model;
*/
class QywxSyncSettings extends BaseModel
{
protected $name = 'qywx_sync_settings';
protected $table = 'zyt_qywx_sync_settings';
}
@@ -15,12 +15,21 @@ class WechatWorkService
{
private $corpId;
private $secret;
private $contactSecret;
private $accessToken;
private $contactAccessToken;
public function __construct()
{
$this->corpId = config('pay.wechat_work.corp_id');
$this->secret = config('pay.wechat_work.external_pay_secret');
$this->contactSecret = config('pay.wechat_work.contact_secret');
Log::info('企业微信配置: ' . json_encode([
'corp_id' => $this->corpId,
'secret' => substr($this->secret, 0, 10) . '...',
'contact_secret' => substr($this->contactSecret, 0, 10) . '...'
]));
}
/**
@@ -63,12 +72,80 @@ class WechatWorkService
throw new \Exception('获取access_token失败: ' . ($response['errmsg'] ?? '未知错误'));
}
/**
* @notes 获取客户联系的access_token
* @see https://developer.work.weixin.qq.com/document/path/92113
*/
private function getContactAccessToken(): string
{
if ($this->contactAccessToken) {
return $this->contactAccessToken;
}
// 从缓存获取
$cacheKey = 'qywx_contact_access_token';
$token = Cache::get($cacheKey);
if ($token) {
$this->contactAccessToken = $token;
return $token;
}
// 尝试使用 contactSecret 获取 token
$token = $this->requestToken($this->contactSecret, $cacheKey, '客户联系');
if ($token) {
$this->contactAccessToken = $token;
return $token;
}
// 如果 contactSecret 失败,尝试使用 secret
Log::info('企业微信-使用 contactSecret 获取 token 失败,尝试使用 secret');
$token = $this->requestToken($this->secret, $cacheKey, '普通应用');
if ($token) {
$this->contactAccessToken = $token;
return $token;
}
throw new \Exception('获取客户联系access_token失败');
}
/**
* @notes 请求获取 token
*/
private function requestToken(string $secret, string $cacheKey, string $type): string
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken';
$params = [
'corpid' => $this->corpId,
'corpsecret' => $secret,
];
Log::info('企业微信-获取' . $type . 'AccessToken请求: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
$response = $this->httpGet($url, $params);
Log::info('企业微信-获取' . $type . 'AccessToken响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
if (isset($response['access_token'])) {
$token = $response['access_token'];
$expiresIn = $response['expires_in'] ?? 7200;
// 缓存token,提前5分钟过期
Cache::set($cacheKey, $token, $expiresIn - 300);
return $token;
}
Log::error('获取' . $type . 'token失败: ' . json_encode($response));
return '';
}
/**
* @notes 获取部门成员列表
* @see https://developer.work.weixin.qq.com/document/path/90200
*/
public function getDepartmentUserList(int $departmentId = 1, bool $fetchChild = true): array
{
// 获取部门成员列表使用 external_pay_secret(普通应用Secret
$accessToken = $this->getAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/user/list';
$params = [
@@ -77,6 +154,8 @@ class WechatWorkService
'fetch_child' => $fetchChild ? 1 : 0,
];
Log::info('企业微信-获取部门成员列表请求: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
$response = $this->httpGet($url, $params);
Log::info('企业微信-获取部门成员列表响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
@@ -93,17 +172,37 @@ class WechatWorkService
* @notes 获取成员的客户列表
* @see https://developer.work.weixin.qq.com/document/path/92113
* @param string $userId 成员ID
* @param string $cursor 分页游标,上次返回的 next_cursor
* @param int $limit 最大记录数,最大 100,默认 50
* @return array|false 返回客户列表数组,权限错误返回false
*/
public function getExternalContactList(string $userId)
public function getExternalContactList(string $userId, string $cursor = '', int $limit = 50)
{
$accessToken = $this->getAccessToken();
// 生成缓存键
$cacheKey = 'qywx_external_contact_list_' . $userId . '_' . $cursor . '_' . $limit;
// 尝试从缓存获取
$cachedResult = Cache::get($cacheKey);
if ($cachedResult) {
Log::info('从缓存获取客户列表: ' . $userId);
return $cachedResult;
}
$accessToken = $this->getContactAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list';
$params = [
'access_token' => $accessToken,
'userid' => $userId,
];
// 添加分页参数
if (!empty($cursor)) {
$params['cursor'] = $cursor;
}
if ($limit > 0) {
$params['limit'] = min($limit, 100); // 最大100
}
$response = $this->httpGet($url, $params);
Log::info('企业微信-获取成员客户列表响应 userId=' . $userId . ': ' . json_encode($response, JSON_UNESCAPED_UNICODE));
@@ -115,25 +214,102 @@ class WechatWorkService
}
if (isset($response['external_userid'])) {
return $response['external_userid'];
$result = [
'external_userid' => $response['external_userid'],
'next_cursor' => $response['next_cursor'] ?? ''
];
// 缓存结果,有效期5分钟
Cache::set($cacheKey, $result, 300);
return $result;
}
// 如果没有客户,返回空数组(不记录错误)
if (isset($response['errcode']) && $response['errcode'] == 0) {
return [];
$result = [
'external_userid' => [],
'next_cursor' => ''
];
// 缓存结果,有效期5分钟
Cache::set($cacheKey, $result, 300);
return $result;
}
Log::error('获取客户列表失败: ' . json_encode($response));
return [];
return false;
}
/**
* @notes 获取外部联系人信息
* @see https://developer.work.weixin.qq.com/document/path/92114
* @param string $externalUserId 外部用户ID
* @return array|false 返回外部联系人信息,失败返回false
*/
public function getExternalContactInfo(string $externalUserId)
{
$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);
Log::info('企业微信-获取外部联系人信息响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
if (isset($response['external_contact'])) {
return $response['external_contact'];
}
Log::error('获取外部联系人信息失败: ' . json_encode($response));
return false;
}
/**
* @notes 生成带参数的二维码
* @see https://developer.work.weixin.qq.com/document/path/90239
* @param string $scene 场景值
* @param int $expireSeconds 过期时间(秒),最大2592000
* @return array|false 返回二维码信息,失败返回false
*/
public function createQrCode(string $scene, int $expireSeconds = 2592000)
{
$accessToken = $this->getAccessToken();
$url = "https://qyapi.weixin.qq.com/cgi-bin/qrcode/create?access_token={$accessToken}";
$data = [
"action_info" => [
"scene" => [
"scene_str" => $scene
]
],
"expire_seconds" => $expireSeconds,
"action_name" => "QR_SCENE"
];
Log::info('企业微信-生成二维码请求: ' . json_encode($data, JSON_UNESCAPED_UNICODE));
$response = $this->httpPost($url, json_encode($data));
Log::info('企业微信-生成二维码响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
if (isset($response['ticket'])) {
return $response;
}
Log::error('生成二维码失败: ' . json_encode($response));
return false;
}
/**
* @notes 获取客户详情
* @see https://developer.work.weixin.qq.com/document/path/92114
*/
public function getExternalContactDetail(string $externalUserId): array
{
$accessToken = $this->getAccessToken();
$accessToken = $this->getContactAccessToken();
$url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get';
$params = [
'access_token' => $accessToken,
@@ -150,6 +326,240 @@ class WechatWorkService
return [];
}
/**
* @notes 获取所有员工的客户列表
* @return array 所有员工的客户ID列表,格式:[staff_id => [external_userid1, external_userid2]]
*/
public function getAllStaffCustomers(): array
{
$result = [];
// 获取所有员工
$staffList = $this->getDepartmentUserList();
if (empty($staffList)) {
Log::info('企业微信-未获取到员工列表');
return $result;
}
Log::info('企业微信-开始获取员工客户列表,共 ' . count($staffList) . ' 名员工');
foreach ($staffList as $staff) {
$userId = $staff['userid'] ?? '';
if (empty($userId)) {
continue;
}
$customerResult = $this->getExternalContactList($userId);
if ($customerResult === false) {
Log::error('企业微信-员工 ' . $userId . ' 获取客户列表权限不足');
continue;
}
$customerIds = $customerResult['external_userid'] ?? [];
if (!empty($customerIds)) {
$result[$userId] = [
'name' => $staff['name'] ?? '',
'customers' => $customerIds
];
Log::info('企业微信-员工 ' . $userId . '(' . ($staff['name'] ?? '') . ') 有 ' . count($customerIds) . ' 个客户');
}
}
Log::info('企业微信-共获取到 ' . count($result) . ' 名员工的客户');
return $result;
}
/**
* @notes 同步所有客户到本地数据库
* @return array 同步结果统计
*/
public function syncAllExternalContacts(): array
{
$stats = [
'total_staff' => 0,
'total_customers' => 0,
'added' => 0,
'updated' => 0,
'errors' => 0
];
// 获取所有员工的客户
$allStaffCustomers = $this->getAllStaffCustomers();
if (empty($allStaffCustomers)) {
Log::info('企业微信-没有客户需要同步');
return $stats;
}
$stats['total_staff'] = count($allStaffCustomers);
foreach ($allStaffCustomers as $staffId => $staffData) {
$staffName = $staffData['name'] ?? '';
$customers = $staffData['customers'] ?? [];
foreach ($customers as $externalUserId) {
$stats['total_customers']++;
try {
// 获取客户详情
$customerDetail = $this->getExternalContactDetail($externalUserId);
if (empty($customerDetail) || !isset($customerDetail['external_contact'])) {
Log::error('企业微信-获取客户详情失败: ' . $externalUserId);
$stats['errors']++;
continue;
}
$contactInfo = $customerDetail['external_contact'];
// 更新到数据库
$result = $this->saveExternalContact($staffId, $staffName, $contactInfo);
if ($result) {
$stats[$result]++;
}
} catch (\Exception $e) {
Log::error('企业微信-同步客户异常: ' . $e->getMessage());
$stats['errors']++;
}
}
}
Log::info('企业微信-同步完成: ' . json_encode($stats));
return $stats;
}
/**
* @notes 批量获取成员的客户详情(支持分页)
* @param string $userId 成员ID
* @param string $cursor 分页游标,上次返回的 next_cursor
* @param int $limit 最大记录数,最大 100,默认 50
* @return array|false 返回客户详情列表,权限错误返回false
*/
public function batchGetCustomerDetails(string $userId, string $cursor = '', int $limit = 50)
{
// 生成缓存键
$cacheKey = 'qywx_batch_customer_details_' . $userId . '_' . $cursor . '_' . $limit;
// 尝试从缓存获取
$cachedResult = Cache::get($cacheKey);
if ($cachedResult) {
Log::info('从缓存获取客户详情: ' . $userId);
return $cachedResult;
}
// 先获取客户ID列表
$listResult = $this->getExternalContactList($userId, $cursor, $limit);
if ($listResult === false) {
return false;
}
$externalUserIds = $listResult['external_userid'] ?? [];
$nextCursor = $listResult['next_cursor'] ?? '';
$customers = [];
// 批量获取客户详情
foreach ($externalUserIds as $externalUserId) {
$detail = $this->getExternalContactDetail($externalUserId);
if (!empty($detail) && isset($detail['external_contact'])) {
$customers[] = $detail;
}
}
$result = [
'customers' => $customers,
'next_cursor' => $nextCursor,
'count' => count($customers)
];
// 缓存结果,有效期5分钟
Cache::set($cacheKey, $result, 300);
return $result;
}
/**
* @notes 保存外部联系人到数据库
* @param string $staffId 员工ID
* @param string $staffName 员工姓名
* @param array $contactInfo 联系人信息
* @return string|false 返回added/updated,失败返回false
*/
private function saveExternalContact(string $staffId, string $staffName, array $contactInfo)
{
$externalUserId = $contactInfo['external_userid'] ?? '';
if (empty($externalUserId)) {
return false;
}
// 查询是否已存在
$model = new \app\common\model\QywxExternalContact();
$exists = $model->where('external_user_id', $externalUserId)->find();
$data = [
'external_user_id' => $externalUserId,
'name' => $contactInfo['name'] ?? '',
'avatar' => $contactInfo['avatar'] ?? '',
'type' => $contactInfo['type'] ?? 1,
'gender' => $contactInfo['gender'] ?? 0,
'unionid' => $contactInfo['unionid'] ?? '',
'position' => $contactInfo['position'] ?? '',
'corp_name' => $contactInfo['corp_name'] ?? '',
'corp_full_name' => $contactInfo['corp_full_name'] ?? '',
'external_profile' => json_encode($contactInfo['external_profile'] ?? [], JSON_UNESCAPED_UNICODE),
'update_time' => time()
];
// 构建跟进人信息
$followUser = [
'userid' => $staffId,
'name' => $staffName,
'add_time' => time()
];
if ($exists) {
// 更新已有记录
$existsData = json_decode($exists['follow_users'] ?? '{"followers":[]}', true);
if (!isset($existsData['followers'])) {
$existsData = ['followers' => []];
}
// 检查是否已存在该跟进人
$found = false;
foreach ($existsData['followers'] as &$follower) {
if ($follower['userid'] === $staffId) {
$follower = $followUser;
$found = true;
break;
}
}
unset($follower);
if (!$found) {
$existsData['followers'][] = $followUser;
}
$data['follow_users'] = json_encode($existsData, JSON_UNESCAPED_UNICODE);
$data['update_time'] = time();
$exists->save($data);
return 'updated';
} else {
// 新增记录
$data['follow_users'] = json_encode(['followers' => [$followUser]], JSON_UNESCAPED_UNICODE);
$data['create_time'] = time();
$model->save($data);
return 'added';
}
}
/**
* @notes 检查应用权限(用于诊断)
* @return array 返回应用的权限信息
@@ -181,7 +591,85 @@ class WechatWorkService
];
}
}
/**
* @notes 创建外部客户(扫码添加)
* @param string $staffId 员工ID
* @param string $staffName 员工姓名
* @param array $customerInfo 客户信息
* @return array|false 返回创建结果,失败返回false
*/
public function createExternalContact(string $staffId, string $staffName, array $customerInfo)
{
try {
$accessToken = $this->getContactAccessToken();
$url = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact?access_token={$accessToken}";
$data = [
"userid" => $staffId,
"external_contact" => [
"name" => $customerInfo['name'] ?? '未知客户',
"mobile" => $customerInfo['mobile'] ?? '',
"position" => $customerInfo['position'] ?? '',
"corp_name" => $customerInfo['corp_name'] ?? '',
"avatar" => $customerInfo['avatar'] ?? ''
]
];
Log::info('企业微信-创建外部客户请求: ' . json_encode($data, JSON_UNESCAPED_UNICODE));
$response = $this->httpPost($url, json_encode($data));
Log::info('企业微信-创建外部客户响应: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
if (isset($response['external_userid'])) {
return $response;
}
Log::error('创建外部客户失败: ' . json_encode($response));
return false;
} catch (\Exception $e) {
Log::error('创建外部客户异常: ' . $e->getMessage());
return false;
}
}
/**
* @notes HTTP POST请求
*/
private function httpPost(string $url, string $data): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$output = 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($output, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('JSON解析失败: ' . $output);
return [];
}
return $result;
}
/**
* @notes HTTP GET请求
*/