url = rtrim($url, '/'); $this->ak = $ak; $this->sk = $sk; $this->userAgent = $userAgent; } /** * @param array $payload 已含 package、class 及业务字段 * @return array{state:int,msg:string,body?:array,response?:string} */ public function post(array $payload): array { // 与甘草网关规范一致:签名/头里的时间戳须与 body 内业务字段 timestamp(若有)一致,避免跨秒不一致导致签名校验失败 $sigTs = time(); if (isset($payload['timestamp']) && is_numeric($payload['timestamp'])) { $sigTs = (int) $payload['timestamp']; } if ($sigTs <= 0) { $sigTs = time(); } $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false) { return ['state' => 0, 'msg' => 'json_encode 失败']; } $noise = self::randStr(8); $signature = sha1($json . $sigTs . $noise . $this->sk); $cipher = self::encrypt($json, $this->sk); if ($cipher === '') { return ['state' => 0, 'msg' => 'AES 加密失败']; } $headers = [ 'Connection: close', 'Content-Type: application/json; charset=utf-8', 'Content-length: ' . strlen($cipher), 'Cache-Control: no-cache', 'AK: ' . $this->ak, 'Signature: ' . $signature, 'UTC-Timestamp: ' . $sigTs, 'NOISE: ' . $noise, 'Expect:', ]; $ch = curl_init($this->url); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); // 改用 HTTP/1.1 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 增加连接超时 curl_setopt($ch, CURLOPT_TIMEOUT, 120); curl_setopt($ch, CURLOPT_POSTFIELDS, $cipher); curl_setopt($ch, CURLOPT_HEADER, true); if (str_starts_with($this->url, 'https:')) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 改为 0,完全禁用主机验证 // 添加 SSL 相关选项 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); // 强制使用 TLS 1.2 curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'DEFAULT@SECLEVEL=1'); // 降低安全级别 } // 添加 TCP keepalive curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1); curl_setopt($ch, CURLOPT_TCP_KEEPIDLE, 120); curl_setopt($ch, CURLOPT_TCP_KEEPINTVL, 60); $raw = curl_exec($ch); $info = curl_getinfo($ch); $curlErr = curl_errno($ch); $curlMsg = curl_error($ch); curl_close($ch); if ($raw === false || (int) ($info['http_code'] ?? 0) !== 200) { $http = (int) ($info['http_code'] ?? 0); $err = is_string($raw) ? $raw : ''; return [ 'state' => 0, 'msg' => '通信失败:HTTP ' . $http . ($curlErr !== 0 ? ' curl#' . $curlErr . ' ' . $curlMsg : ''), 'response' => $err, ]; } $bodyRaw = substr($raw, strpos($raw, "\r\n\r\n") + 4); $plain = self::decrypt($bodyRaw, $this->sk); if ($plain === '') { $maybeJson = json_decode($bodyRaw, true); if (is_array($maybeJson) && isset($maybeJson['status'])) { return ['state' => 1, 'msg' => '成功(明文)', 'body' => $maybeJson]; } return ['state' => -1, 'msg' => '解密失败(请核对网关 SK 是否为 16 位且与 AK 匹配)', 'response' => mb_substr($bodyRaw, 0, 500)]; } $decoded = json_decode($plain, true); return ['state' => 1, 'msg' => '成功', 'body' => is_array($decoded) ? $decoded : []]; } private static function encrypt(string $string, string $key): string { $out = openssl_encrypt($string, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); return $out !== false ? base64_encode($out) : ''; } private static function decrypt(string $string, string $key): string { $bin = base64_decode($string, true); if ($bin === false) { return ''; } $out = openssl_decrypt($bin, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); return $out !== false ? $out : ''; } private static function randStr(int $length = 8): string { $chars = 'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'; $chars = str_shuffle($chars); $end = strlen($chars) - 1; $buf = []; while (true) { $c = $chars[random_int(0, $end)]; if ($c !== '0') { $buf[] = $c; break; } } $n = 1; while ($n < $length) { $r = $chars[random_int(0, $end)]; if ($r !== $buf[count($buf) - 1]) { $buf[] = $r; ++$n; } } return implode('', $buf); } }