generateAuthUrl($redirectUrl); * // 重定向到 $authUrl 让用户登录授权 * * 2. 在回调页面(redirectUrl)获取 AccessToken: * $auth = new Auth(); * $code = Yii::$app->request->get('code'); * $result = $auth->getAccessToken($code); * * if ($result['success']) { * // 保存 access_token 和 refresh_token * // $result['access_token'] - 访问令牌 * // $result['refresh_token'] - 刷新令牌 * // $result['expires_in'] - 过期秒数(3个月) * } * * 3. 刷新过期的 AccessToken: * $auth = new Auth(); * $result = $auth->refreshAccessToken($refreshToken); * * if ($result['success']) { * // 使用新的 access_token * // $result['access_token'] * // $result['refresh_token'] * // $result['expires_in'] * } * * ============ 授权流程 ============ * * 授权流程: * 1. 用户点击「货拉拉授权」按钮 -> generateAuthUrl() 获取授权URL * 2. 用户被重定向到货拉拉授权页面 * 3. 用户填写货拉拉账号进行授权 * 4. 货拉拉重定向回 redirectUrl,并携带 code * 5. 在回调页面用 code 调用 getAccessToken() 获取 token * 6. 保存 access_token 和 refresh_token 到数据库 * * ============ 注意事项 ============ * * 1. AccessToken 有效期为 3 个月(7776000秒) * 2. 授权码(code)有效期为 1 分钟,且只能使用 1 次 * 3. 刷新令牌(refresh_token)需要妥善保管,长期有效 * 4. 建议在 Access Token 过期前主动刷新 * 5. 环境配置通过 YII_ENV 环境变量自动区分(production 或其他) * 6. 所有请求都是通过 GET 方式进行 * * @package common\components\delivery\platform\huolala */ class Auth { // 固定参数 const RESPONSE_TYPE = 'code'; const GRANT_TYPE_AUTH_CODE = 'authorization_code'; const GRANT_TYPE_REFRESH = 'refresh_token'; // 授权端点 const AUTHORIZE_URL = 'https://open.huolala.cn/#/oauth/authorize'; const TOKEN_URL = 'https://open.huolala.cn/oauth/token'; protected $appKey; protected $appSecret; protected $redirectUri; protected $isSandbox; /** * 初始化授权类 * 根据环境获取配置信息 */ public function __construct() { $isProduction = getenv('YII_ENV') == 'production'; if($isProduction){ // 货拉拉配置 $this->appKey = 'gBhmCdWtX7hi7qnljduk0yK21XgQg8w3'; $this->appSecret = 'bARxI68FFHpEXAcCQ23fxVQyxtJ9RONU'; }else{ // 货拉拉配置 $this->appKey = '8S2uZQrWwM1mkG2Cj576PsMiNIe25UNT'; $this->appSecret = 'olzor07UgmUWaagahNGQAKQKXRtvhK8j'; } // 根据环境设置沙箱标志 $this->isSandbox = !$isProduction; } /** * 设置重定向URI * 需要进行URLEncode编码 * * @param string $redirectUri 重定向地址 */ public function setRedirectUri($redirectUri) { $this->redirectUri = $redirectUri; return $this; } /** * 生成授权URL * 用户需要通过浏览器访问此URL进行授权 * * @param string $redirectUri 重定向地址(回调地址) * @return string 授权URL */ public function generateAuthUrl($redirectUri) { $this->setRedirectUri($redirectUri); $params = [ 'response_type' => self::RESPONSE_TYPE, 'client_id' => $this->appKey, 'redirect_uri' => $this->redirectUri, 'isSandbox' => $this->isSandbox ? 'true' : 'false', ]; // 构建URL $queryString = http_build_query($params); return self::AUTHORIZE_URL . '?' . $queryString; } /** * 获取AccessToken * 使用授权码换取令牌 * * 文档:/oauth/token (GET请求) * 入参:grant_type, client_id, code, isSandbox * 出参:access_token, refresh_token, expires_in * * @param string $code 授权码(来自授权页面重定向) * @return array 返回格式:['success' => true/false, 'access_token' => '', 'refresh_token' => '', 'expires_in' => 0, 'error' => ''] */ public function getAccessToken($code) { $params = [ 'grant_type' => self::GRANT_TYPE_AUTH_CODE, 'client_id' => $this->appKey, 'code' => $code, 'isSandbox' => $this->isSandbox ? 'true' : 'false', ]; $response = HttpClient::get(self::TOKEN_URL, $params); Yii::info("[HuoLalaAuthGetToken] Response: " . json_encode($response)); return $this->parseResponse($response); } /** * 刷新AccessToken * 使用刷新令牌获取新的AccessToken * * 文档:/oauth/token (GET请求) * 入参:grant_type, client_id, refresh_token, isSandbox * 出参:access_token, refresh_token, expires_in * * @param string $refreshToken 刷新令牌 * @return array 返回格式:['success' => true/false, 'access_token' => '', 'refresh_token' => '', 'expires_in' => 0, 'error' => ''] */ public function refreshAccessToken($refreshToken) { $params = [ 'grant_type' => self::GRANT_TYPE_REFRESH, 'client_id' => $this->appKey, 'refresh_token' => $refreshToken, 'isSandbox' => $this->isSandbox ? 'true' : 'false', ]; $response = HttpClient::get(self::TOKEN_URL, $params); Yii::info("[HuoLalaAuthRefreshToken] Response: " . json_encode($response)); return $this->parseResponse($response); } /** * 解析API响应 * * 货拉拉 API 返回格式: * { * "ret": 0, * "msg": "success", * "data": { * "auth_mobile": "17168665598", * "access_token": "a3207b6b5ad04249ad1dbf6a9824dbea", * "refresh_token": "4ccbbab0e9e443159c518d1d10741ad", * "auth_end_time": "2020-06-07 15:35:08" * } * } * * @param array $response HTTP响应 * @return array 标准化的响应格式 */ protected function parseResponse($response) { // 检查 ret 字段(0 表示成功) if (!isset($response['ret'])) { return [ 'success' => false, 'error' => $response['msg'] ?? '响应格式错误,缺少 ret 字段', ]; } // 如果 ret 不等于 0,表示请求失败 if ($response['ret'] != 0) { return [ 'success' => false, 'error' => $response['msg'] ?? 'API返回异常', 'ret' => $response['ret'], ]; } // 提取 data 字段中的数据 $data = $response['data'] ?? []; // 检查是否包含必需的token信息 if (empty($data['access_token'])) { return [ 'success' => false, 'error' => $response['msg'] ?? '未获取到 access_token', ]; } // 成功响应 return [ 'success' => true, 'access_token' => $data['access_token'] ?? '', 'refresh_token' => $data['refresh_token'] ?? '', 'auth_mobile' => $data['auth_mobile'] ?? '', 'auth_end_time' => $data['auth_end_time'] ?? '', ]; } /** * 获取AppKey * * @return string */ public function getAppKey() { return $this->appKey; } /** * 获取AppSecret * * @return string */ public function getAppSecret() { return $this->appSecret; } /** * 获取是否为沙箱环境 * * @return bool */ public function isSandbox() { return $this->isSandbox; } }