0) { Cache::set($cacheKey, $token, $ttl); } return $token; } /** * @return array|null */ private static function httpGetJson(string $url): ?array { $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, 15); $result = curl_exec($ch); curl_close($ch); if ($result === false || $result === '') { return null; } $decoded = json_decode($result, true); return is_array($decoded) ? $decoded : null; } /** * @return array|null */ private static 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_HTTPHEADER, ['Content-Type: application/json; charset=utf-8']); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 15); $result = curl_exec($ch); curl_close($ch); if ($result === false || $result === '') { return null; } $decoded = json_decode($result, true); return is_array($decoded) ? $decoded : null; } /** * 向指定成员发送文本消息(userid 为企业微信成员账号,与后台 work_wechat_userid 一致) * * @return array{ok: bool, message?: string, errcode?: int} */ public static function sendTextToUser(string $toUser, string $content): array { $toUser = trim($toUser); $content = trim($content); if ($toUser === '' || $content === '') { return ['ok' => false, 'message' => '接收人或消息内容为空']; } [$corpId, $agentId, $secret] = self::resolveMessagingCredentials(); if ($corpId === '' || $agentId <= 0 || $secret === '') { Log::warning('企业微信应用消息未配置完整(corp_id/agent_id/secret),跳过发消息'); return [ 'ok' => false, 'message' => '服务端未配置企业微信应用消息(需 corp_id、agent_id 及该应用的 Secret,建议配置 WECHAT_WORK_AGENT_SECRET)', ]; } $token = self::getAccessToken($corpId, $secret); if (!$token) { return ['ok' => false, 'message' => '获取企业微信 access_token 失败,请查看服务端日志']; } $url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' . urlencode($token); $body = [ 'touser' => $toUser, 'msgtype' => 'text', 'agentid' => $agentId, 'text' => ['content' => $content], 'safe' => 0, ]; $res = self::httpPostJson($url, $body); if (!$res) { Log::error('企业微信发消息无响应'); return ['ok' => false, 'message' => '企业微信接口无响应']; } $err = (int) ($res['errcode'] ?? -1); if ($err !== 0) { $errmsg = (string) ($res['errmsg'] ?? ''); Log::error('企业微信发消息失败: ' . $errmsg . ' errcode=' . $err); $hint = ''; if ($err === 81013 || $err === 60111) { $hint = '请核对开方人绑定的 userid 与企业微信成员账号一致,且成员已安装/可见该应用。'; } elseif ($err === 60020) { $hint = '请将服务器出口 IP 加入企业微信应用可信 IP。'; } elseif ($err === 60011) { $hint = '请检查应用是否具备发消息能力及可见范围是否包含该成员。'; } return [ 'ok' => false, 'message' => '企业微信返回:' . $errmsg . '(errcode=' . $err . ')' . ($hint !== '' ? ' ' . $hint : ''), 'errcode' => $err, ]; } return ['ok' => true]; } }