| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- <?php
- /**
- * 1688分销严选采购解决方案(分销买家版) - 基础服务类
- *
- * 提供OAuth 2.0认证、API请求签名、HTTP请求等基础能力
- *
- * @author: yourname
- * @day: 2026/04/27
- */
- namespace app\services\ThirdParty\AlibabaAgent;
- use crmeb\services\HttpService;
- use think\facade\Config;
- use think\facade\Log;
- class AlibabaAgentBaseService
- {
- /** @var array 配置 */
- protected $config;
- /** @var string App Key */
- protected $appKey;
- /** @var string App Secret */
- protected $appSecret;
- /** @var string 网关地址 */
- protected $gatewayUrl;
- /** @var string 访问令牌 */
- protected $accessToken = '';
- /** @var string 刷新令牌 */
- protected $refreshToken = '';
- /**
- * 构造函数
- */
- public function __construct()
- {
- $this->config = Config::get('third_party.alibaba_agent', []);
- $this->appKey = $this->config['appKey'] ?? '';
- $this->appSecret = $this->config['appSecret'] ?? '';
- $this->accessToken = $this->config['accessToken'] ?? '';
- $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;
- }
- }
|