config = Config::get('third_party.alibaba_agent', []); $this->appKey = $this->config['appKey'] ?? ''; $this->appSecret = $this->config['appSecret'] ?? ''; $this->accessToken = 'ce15fc70-b462-43bf-acc3-f87e43c858ba'; $this->gatewayUrl = $this->config['gatewayUrl'] ?? 'https://gw.open.1688.com/openapi/'; } /** * 设置访问令牌 * * @param string $accessToken * @return $this */ public function setAccessToken(string $accessToken): self { $this->accessToken = $accessToken; return $this; } /** * 设置刷新令牌 * * @param string $refreshToken * @return $this */ public function setRefreshToken(string $refreshToken): self { $this->refreshToken = $refreshToken; return $this; } /** * 获取访问令牌 * * @return string */ public function getAccessToken(): string { return $this->accessToken; } /** * 获取OAuth 2.0授权URL * * @param string $state 防CSRF状态码 * @return string */ public function getAuthUrl(string $state = ''): string { $params = [ 'client_id' => $this->appKey, 'redirect_uri' => $this->config['redirectUri'] ?? '', 'response_type' => 'code', 'state' => $state, ]; return 'https://auth.1688.com/oauth/authorize?' . http_build_query($params); } /** * 通过授权码获取访问令牌 * * @param string $code 授权码 * @return array|false */ public function getAccessTokenByCode(string $code) { $url = 'https://gw.open.1688.com/openapi/http/1/system.oauth2/getAccessTokenByCode/' . $this->appKey; $params = [ 'code' => $code, 'appKey' => $this->appKey, 'appSecret' => $this->appSecret, 'redirect_uri' => $this->config['redirectUri'] ?? '', 'grant_type' => 'authorization_code', ]; return $this->parseResponse( HttpService::postRequest($url, $params) ); } /** * 刷新访问令牌 * * @param string $refreshToken * @return array|false */ public function refreshAccessToken(string $refreshToken) { $url = 'https://gw.open.1688.com/openapi/http/1/system.oauth2/getAccessTokenByCode/' . $this->appKey; $params = [ 'refreshToken' => $refreshToken, 'appKey' => $this->appKey, 'appSecret' => $this->appSecret, 'grant_type' => 'refresh_token', ]; $result = $this->parseResponse( HttpService::postRequest($url, $params) ); if ($result && isset($result['access_token'])) { $this->setAccessToken($result['access_token']); if (isset($result['refresh_token'])) { $this->setRefreshToken($result['refresh_token']); } } return $result; } /** * 生成1688 API签名(param2方式) * * 参考可调通的代码: * $aliParams = array(); * foreach ($code_arr as $key => $val) { * if (is_array($val)) { $val = json_encode($val); } * $aliParams[] = $key . $val; * } * sort($aliParams); * $sign_str = join('', $aliParams); * $sign_str = $param . $apiInfo . $sign_str; * return strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true))); * * @param string $param 协议前缀,如 "param2/1/" * @param string $apiInfo API路径(含appKey),如 "com.alibaba.fenxiao/jxhy.product.getPageList/6512262" * @param array $params 所有请求参数(含access_token,不含_aop_signature) * @return string */ protected function generateSignature(string $param, string $apiInfo, array $params): string { $aliParams = array(); foreach ($params as $key => $val) { // 数组参数需要json_encode if (is_array($val)) { $val = json_encode($val); } $aliParams[] = $key . $val; } sort($aliParams); $sign_str = join('', $aliParams); // 注意顺序: param + apiInfo + 参数拼装 $sign_str = $param . $apiInfo . $sign_str; return strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $this->appSecret, true))); } /** * 执行API请求(param2方式 - RESTful风格) * * @param string $namespace API命名空间 * @param string $name API名称 * @param int $version API版本号 * @param array $businessParams 业务参数 * @return array|false */ protected function executeParam2( string $namespace, string $name, int $version = 1, array $businessParams = [] ) { // param2方式URL格式: https://gw.open.1688.com/openapi/param2/{version}/{namespace}/{name}/{appKey} // 注意: URL路径最后一部分是 appKey,不是API版本号 $appKey = $this->getConfig('appKey', ''); // 协议前缀: param2/{version}/ $param = 'param2/' . $version . '/'; // API路径(含appKey): {namespace}/{name}/{appKey} $apiInfo = $namespace . '/' . $name . '/' . $appKey; // 完整URL(不含query参数,参数通过POST body发送) $url = rtrim($this->gatewayUrl, '/') . '/' . $param . $apiInfo; // 所有参数(包括access_token和业务参数) $allParams = array_merge( ['access_token' => $this->accessToken], $businessParams ); // 生成签名 // 注意: generateSignature($param, $apiInfo, $params) // sign_str = $param . $apiInfo . 参数拼装 $signature = $this->generateSignature($param, $apiInfo, $allParams); $allParams['_aop_signature'] = $signature; $this->logRequest($url, $allParams); try { // 使用POST方式发送请求,参数放在POST body中(参考可调通的代码) $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($allParams)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 15); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FAILONERROR, false); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($response === false) { $this->logError('HTTP请求失败', $curlError ?: '未知curl错误'); return false; } $this->logResponse($url, $response); return $this->parseResponse($response, $httpCode); } catch (\Exception $e) { $this->logError('API请求异常', $e->getMessage()); return false; } } /** * 执行API请求(http方式 - 传统POST方式) * * @param string $namespace API命名空间 * @param string $name API名称 * @param int $version API版本号 * @param array $businessParams 业务参数 * @return array|false */ protected function executeHttp( string $namespace, string $name, int $version = 1, array $businessParams = [] ) { // 构建URL: https://gw.open.1688.com/openapi/http/1/cn.alibaba.open/offer.get/1 $url = rtrim($this->gatewayUrl, '/') . '/http/' . $version . '/' . $namespace . '/' . $name . '/' . $version; // 构建请求参数 $params = [ 'access_token' => $this->accessToken, 'appKey' => $this->appKey, 'appSecret' => $this->appSecret, ]; // 业务参数 if (!empty($businessParams)) { $params = array_merge($params, $businessParams); } $this->logRequest($url, $params); try { $response = HttpService::postRequest($url, $params); $this->logResponse($url, $response); return $this->parseResponse($response); } catch (\Exception $e) { $this->logError('API请求异常', $e->getMessage()); return false; } } /** * 解析响应 * * @param string|false $response 响应体 * @param int $httpCode HTTP状态码 * @return array|false */ protected function parseResponse($response, int $httpCode = 200) { if ($response === false || empty($response)) { $this->logError('HTTP请求失败', '响应为空或请求失败'); return false; } $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { $this->logError('JSON解析失败', json_last_error_msg() . ' | 原始响应: ' . substr($response, 0, 500)); return false; } // HTTP状态码非200,记录错误并返回false if ($httpCode !== 200) { $errorMsg = json_encode($result, JSON_UNESCAPED_UNICODE); $this->logError("HTTP请求失败(status:{$httpCode})", $errorMsg); return false; } // 检查1688 API标准错误格式 if (isset($result['error_code']) || isset($result['errorMessage'])) { $errorMsg = $result['errorMessage'] ?? ($result['error_message'] ?? '未知错误'); $errorCode = $result['error_code'] ?? ($result['code'] ?? 0); $this->logError('1688 API错误', "[{$errorCode}] {$errorMsg}"); return false; } // 检查result中的错误 if (isset($result['result']) && isset($result['result']['errorCode'])) { $errorMsg = $result['result']['errorMessage'] ?? '未知错误'; $errorCode = $result['result']['errorCode']; $this->logError('1688 API业务错误', "[{$errorCode}] {$errorMsg}"); return false; } // 检查alibaba标准错误响应格式 if (isset($result['errorResponse']) && isset($result['errorResponse']['code'])) { $errorMsg = $result['errorResponse']['message'] ?? '未知错误'; $errorCode = $result['errorResponse']['code']; $this->logError('1688 API错误响应', "[{$errorCode}] {$errorMsg}"); return false; } return $result; } /** * 记录请求日志 * * @param string $url * @param array $params */ protected function logRequest(string $url, array $params): void { if (!($this->config['logEnabled'] ?? false)) { return; } $logData = [ 'url' => $url, 'params' => $this->maskSensitiveData($params), ]; Log::channel('alibaba')->info('[1688分销采购] 请求: ' . json_encode($logData, JSON_UNESCAPED_UNICODE)); } /** * 记录响应日志 * * @param string $url * @param string|false $response */ protected function logResponse(string $url, $response): void { if (!($this->config['logEnabled'] ?? false)) { return; } Log::channel('alibaba')->info('[1688分销采购] 响应: ' . substr((string)$response, 0, 2000)); } /** * 记录错误日志 * * @param string $title * @param string $message */ protected function logError(string $title, string $message): void { Log::channel('alibaba')->error("[1688分销采购] {$title}: {$message}"); // 同时写入文件日志(兼容旧的文件日志方式) $logPath = $this->config['logPath'] ?? runtime_path() . 'alibaba/'; $logFile = $logPath . 'error_' . date('Y-m-d') . '.log'; $logDir = dirname($logFile); if (!is_dir($logDir)) { @mkdir($logDir, 0755, true); } @file_put_contents( $logFile, '[' . date('Y-m-d H:i:s') . "] {$title}: {$message}\n", FILE_APPEND ); } /** * 脱敏敏感数据 * * @param array $data * @return array */ protected function maskSensitiveData(array $data): array { $sensitiveKeys = ['appSecret', 'access_token', 'refreshToken']; foreach ($data as $key => $value) { if (in_array($key, $sensitiveKeys) && is_string($value)) { $data[$key] = substr($value, 0, 4) . '****' . substr($value, -4); } } return $data; } /** * 获取配置项 * * @param string $key * @param mixed $default * @return mixed */ protected function getConfig(string $key, $default = null) { return $this->config[$key] ?? $default; } }