| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- <?php
- namespace common\components\delivery\platform\huolala;
- use common\components\delivery\helpers\HttpClient;
- use Yii;
- /**
- * 货拉拉商户授权
- *
- * 支持 OAuth2.0 Authorization Code 模式,遵循货拉拉开放平台授权规范。
- * Access Token 有效期为 3 个月,需要定期通过 Refresh Token 进行更新。
- *
- * ============ 使用示例 ============
- *
- * 1. 生成授权URL:
- * $auth = new Auth();
- * $redirectUrl = 'http://your-domain.com/callback';
- * $authUrl = $auth->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;
- }
- }
|