Commit bdd6d174 authored by 刘小敏's avatar 刘小敏

fix(xhs): 改用静态凭证回传转化事件

parent dd5ce668
...@@ -5,13 +5,18 @@ namespace addons\shopro\service\xhs; ...@@ -5,13 +5,18 @@ namespace addons\shopro\service\xhs;
use app\admin\model\shopro\xhs\ConversionLog as ConversionLogModel; use app\admin\model\shopro\xhs\ConversionLog as ConversionLogModel;
/** /**
* 小红书聚光 Marketing API 转化回传服务 * 小红书聚光「种草直达」转化回传服务
* *
* 负责调用小红书开放平台接口回传转化事件 * 负责调用小红书转化回传 API 上报转化事件
* 包含 Token 管理、API 调用、重试逻辑
* *
* ⚠️ 使用前需在聚光开放平台 (ad-market.xiaohongshu.com) 申请应用, * ⚠️ 凭证说明:
* 并在环境变量中配置 PHP_XHS_APP_KEY / PHP_XHS_APP_SECRET * app_id + access_token 由小红书运营/销售侧提供(静态凭证),非 OAuth 动态获取。
* 配置方式:
* - 环境变量: PHP_XHS_APP_ID / PHP_XHS_ACCESS_TOKEN
* - 或 shopro 配置表 code=xhs_ad 组中配置 APP_ID / ACCESS_TOKEN
*
* 注意:OAuth 接口(app_key + app_secret → /oauth/token)用于管理类 API,
* 转化回传 API 不使用 OAuth token,此处保留 getAccessToken() 仅供其他场景参考。
*/ */
class ConversionService class ConversionService
{ {
...@@ -38,23 +43,23 @@ class ConversionService ...@@ -38,23 +43,23 @@ class ConversionService
* @return string|null * @return string|null
* @throws \Exception * @throws \Exception
*/ */
public function getAccessToken() // public function getAccessToken()
{ // {
// 检查缓存 // // 检查缓存
$cached = redis_cache('?' . self::TOKEN_CACHE_KEY); // $cached = redis_cache('?' . self::TOKEN_CACHE_KEY);
if ($cached) { // if ($cached) {
$tokenData = json_decode($cached, true); // $tokenData = json_decode($cached, true);
if ($tokenData && isset($tokenData['token'])) { // if ($tokenData && isset($tokenData['token'])) {
$now = time(); // $now = time();
if ($tokenData['expires_at'] > ($now + self::TOKEN_REFRESH_AHEAD)) { // if ($tokenData['expires_at'] > ($now + self::TOKEN_REFRESH_AHEAD)) {
return $tokenData['token']; // return $tokenData['token'];
} // }
} // }
} // }
// 获取新 Token // // 获取新 Token
return $this->refreshAccessToken(); // return $this->refreshAccessToken();
} // }
/** /**
* 强制刷新 Access Token * 强制刷新 Access Token
...@@ -62,40 +67,40 @@ class ConversionService ...@@ -62,40 +67,40 @@ class ConversionService
* @return string|null * @return string|null
* @throws \Exception * @throws \Exception
*/ */
private function refreshAccessToken() // private function refreshAccessToken()
{ // {
$appKey = $this->getConfig('APP_KEY', ''); // $appKey = $this->getConfig('APP_KEY', '');
$appSecret = $this->getConfig('APP_SECRET', ''); // $appSecret = $this->getConfig('APP_SECRET', '');
if (empty($appKey) || empty($appSecret)) { // if (empty($appKey) || empty($appSecret)) {
throw new \Exception('小红书聚光 API 凭证未配置(PHP_XHS_APP_KEY / PHP_XHS_APP_SECRET)'); // throw new \Exception('小红书聚光 API 凭证未配置(PHP_XHS_APP_KEY / PHP_XHS_APP_SECRET)');
} // }
$url = self::API_BASE . self::TOKEN_URL; // $url = self::API_BASE . self::TOKEN_URL;
$params = [ // $params = [
'app_key' => $appKey, // 'app_key' => $appKey,
'app_secret' => $appSecret, // 'app_secret' => $appSecret,
'grant_type' => 'client_credentials', // 'grant_type' => 'client_credentials',
]; // ];
$response = $this->httpPost($url, $params); // $response = $this->httpPost($url, $params);
if (empty($response['access_token'])) { // if (empty($response['access_token'])) {
throw new \Exception('获取 access_token 失败: ' . json_encode($response)); // throw new \Exception('获取 access_token 失败: ' . json_encode($response));
} // }
$token = $response['access_token']; // $token = $response['access_token'];
$expiresIn = intval($response['expires_in'] ?? 7200); // $expiresIn = intval($response['expires_in'] ?? 7200);
// 缓存 Token // // 缓存 Token
$tokenData = [ // $tokenData = [
'token' => $token, // 'token' => $token,
'expires_at' => time() + $expiresIn, // 'expires_at' => time() + $expiresIn,
]; // ];
redis_cache(self::TOKEN_CACHE_KEY, json_encode($tokenData), $expiresIn); // redis_cache(self::TOKEN_CACHE_KEY, json_encode($tokenData), $expiresIn);
return $token; // return $token;
} // }
/** /**
* 回传转化事件到小红书(对齐官方「种草直达」API 文档) * 回传转化事件到小红书(对齐官方「种草直达」API 文档)
...@@ -120,15 +125,19 @@ class ConversionService ...@@ -120,15 +125,19 @@ class ConversionService
} }
// 获取 app_id 和 access_token // 获取 app_id 和 access_token
$appId = $this->getConfig('APP_ID', ''); // 注意:转化回传 API 的 access_token 不同于管理 API 的 OAuth token
try { // 官方文档说明:"app_id和access_token需要联系您的产品获取",即由小红书运营/销售侧提供的静态凭证
$accessToken = $this->getAccessToken(); $appId = config('shopro.xhs_app_id');
} catch (\Exception $e) { $accessToken = config('shopro.xhs_access_token');
return ['success' => false, 'response' => '获取 Token 失败: ' . $e->getMessage()];
} custom_log('[xhs.context] 配置调试:app_id:'.$appId.'accessToken:'.$accessToken, 0, 'xhs_ad', 'info');
if (empty($appId)) { if (empty($appId)) {
return ['success' => false, 'response' => 'app_id 未配置']; return ['success' => false, 'response' => 'app_id 未配置(需联系小红书运营获取)'];
}
if (empty($accessToken)) {
return ['success' => false, 'response' => 'access_token 未配置(需联系小红书运营获取)'];
} }
// 转换事件类型为小红书平台编码 // 转换事件类型为小红书平台编码
...@@ -365,30 +374,30 @@ class ConversionService ...@@ -365,30 +374,30 @@ class ConversionService
return $map[$type] ?? 0; return $map[$type] ?? 0;
} }
/** // /**
* 获取配置(优先环境变量,其次 shopro 配置组) // * 获取配置(优先环境变量,其次 shopro 配置组)
* // *
* @param string $key // * @param string $key
* @param mixed $default // * @param mixed $default
* @return mixed // * @return mixed
*/ // */
private function getConfig($key, $default = null) // private function getConfig($key, $default = null)
{ // {
// 1. 优先环境变量(如 PHP_XHS_APP_KEY / PHP_XHS_APP_SECRET // 1. 优先环境变量(如 PHP_XHS_APP_ID / PHP_XHS_ACCESS_TOKEN
$envKey = 'PHP_XHS_' . strtoupper($key); // $envKey = 'PHP_XHS_' . strtoupper($key);
$envValue = getenv($envKey); // $envValue = getenv($envKey);
if ($envValue !== false && $envValue !== '') { // if ($envValue !== false && $envValue !== '') {
return $envValue; // return $envValue;
} // }
// 2. 从 shopro 配置组中获取(需先在 fa_shopro_config 表添加 code=xhs_ad 的配置组) // // 2. 从 shopro 配置组中获取(需先在 fa_shopro_config 表添加 code=xhs_ad 的配置组)
$config = sheep_config('xhs_ad'); // $config = sheep_config('xhs_ad');
if ($config && isset($config[$key])) { // if ($config && isset($config[$key])) {
return $config[$key]; // return $config[$key];
} // }
return $default; // return $default;
} // }
/** /**
* HTTP POST (form-urlencoded) * HTTP POST (form-urlencoded)
......
...@@ -335,7 +335,12 @@ return [ ...@@ -335,7 +335,12 @@ return [
'obs_access_key' => 'HPUAWU3GPS8HEWX9JFLG', 'obs_access_key' => 'HPUAWU3GPS8HEWX9JFLG',
'obs_secret_key' => 'Jj7Vt1WnU69pWG6QplBmHBkaw6WM4oLU1AZa9Z9n', 'obs_secret_key' => 'Jj7Vt1WnU69pWG6QplBmHBkaw6WM4oLU1AZa9Z9n',
'obs_endpoint' => 'obs.cn-east-3.myhuaweicloud.com', // 根据区域修改 'obs_endpoint' => 'obs.cn-east-3.myhuaweicloud.com', // 根据区域修改
'obs_bucket' => 'beevera' 'obs_bucket' => 'beevera',
// 小红书配置
'xhs_app_id' => Env::get('xhs.app_id', ''),
'xhs_access_token' => Env::get('xhs.access_token', ''),
], ],
]; ];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment