| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <?php
- namespace common\components\delivery\platform\fengniao;
- use common\components\delivery\helpers\HttpClient;
- use Yii;
- class Auth
- {
- // 固定参数
- const GRANT_TYPE_AUTH_CODE = 'authorization_code';
- const GRANT_TYPE_REFRESH = 'refresh_token';
- // 授权端点 - 正式环境
- const TOKEN_URL_PRODUCTION = 'https://open-anubis.ele.me/anubis-webapi/openapi/token';
- const REFRESH_TOKEN_URL_PRODUCTION = 'https://open-anubis.ele.me/anubis-webapi/openapi/refreshToken';
- // 授权端点 - 沙箱环境
- const TOKEN_URL_SANDBOX = 'https://exam-anubis.ele.me/anubis-webapi/openapi/token';
- const REFRESH_TOKEN_URL_SANDBOX = 'https://exam-anubis.ele.me/anubis-webapi/openapi/refreshToken';
- protected $appId;
- protected $appSecret;
- protected $merchantId;
- protected $isSandbox;
- /**
- * 初始化授权类
- * 根据环境获取配置信息
- */
- public function __construct()
- {
- $isProduction = getenv('YII_ENV') == 'production';
- // 配置(需要替换为真实的appId和appSecret)
- $this->appId = '6587209115185920913';// 3659064244254722812 -- 6587209115185920913
- $this->appSecret = '3f935d5f-bf65-467e-a61c-72cfb1d53960'; // dce31e3b-32da-45ed-8353-f2f041b5288f -- 3f935d5f-bf65-467e-a61c-72cfb1d53960
- $this->merchantId = ''; // 需要从配置中获取或外部设置
- // 根据环境设置沙箱标志
- $this->isSandbox = !$isProduction;
- }
- /**
- * 生成授权URL
- * 用户需要通过浏览器访问此URL进行授权
- *
- * @param string $redirectUri 重定向地址(回调地址)
- * @return string 授权URL
- */
- public function generateAuthUrl($redirectUri)
- {
- $params = [
- 'appId' => $this->appId,
- 'devId' => 133146508, // 蜂鸟开放平台 【开发者中心】->【应用管理】->【发起商户授权】获得的
- 'authCallbackUrl' => $redirectUri,
- ];
- // 构建URL
- $queryString = http_build_query($params);
- return 'https://open.ele.me/app-auth?' . $queryString;
- }
- /**
- * 设置商户ID
- *
- * @param string $merchantId 商户ID
- */
- public function setMerchantId($merchantId)
- {
- $this->merchantId = $merchantId;
- return $this;
- }
- /**
- * 生成签名
- * 根据峰鸟开放平台API文档,签名算法采用SHA-256
- *
- * @param array $params 参数数组
- * @return string 签名结果
- */
- protected function generateSignature(array $params)
- {
- // Step 2: 按字典序排序
- ksort($params);
- // Step 3: 拼接成字符串
- $paramStr = '';
- $first = true;
- foreach ($params as $key => $value) {
- if ($first) {
- $paramStr = "{$key}={$value}";
- $first = false;
- } else {
- $paramStr .= "&{$key}={$value}";
- }
- }
- // Step 4: 拼接 appSecret
- $signBefore = $this->appSecret . $paramStr;
- // Step 5: 使用SHA-256加密
- $signature = hash('sha256', $signBefore);
- Yii::info("[FengniaAuth] Sign Before: {$signBefore}, Signature: {$signature}");
- return $signature;
- }
- /**
- * 获取 Token URL
- *
- * @return string
- */
- protected function getTokenUrl()
- {
- return $this->isSandbox ? self::TOKEN_URL_SANDBOX : self::TOKEN_URL_PRODUCTION;
- }
- /**
- * 获取刷新 Token URL
- *
- * @return string
- */
- protected function getRefreshTokenUrl()
- {
- return $this->isSandbox ? self::REFRESH_TOKEN_URL_SANDBOX : self::REFRESH_TOKEN_URL_PRODUCTION;
- }
- /**
- * 获取AccessToken
- * 使用授权码换取令牌
- *
- * 文档:获取token接口
- * 入参:grant_type, code, app_id, merchant_id, timestamp, signature
- * 出参:access_token, refresh_token, expire_in
- *
- * @param string $code 授权码(来自授权回调)
- * @param string $merchantId 商户ID(可选,如果已设置则使用已设置的)
- * @return array 返回格式:['success' => true/false, 'data' => [...], 'error' => '']
- */
- public function getAccessToken($code, $merchantId = null)
- {
- if ($merchantId) {
- $this->setMerchantId($merchantId);
- }
- if (empty($this->merchantId)) {
- return [
- 'success' => false,
- 'error' => '商户ID未设置',
- ];
- }
- $timestamp = (int)(microtime(true) * 1000); // 毫秒级时间戳
- $params = [
- 'grant_type' => self::GRANT_TYPE_AUTH_CODE,
- 'code' => $code,
- 'app_id' => $this->appId,
- 'merchant_id' => $this->merchantId,
- 'timestamp' => $timestamp,
- ];
- // 沙箱环境不传code
- if (getenv('YII_ENV') !== 'production') {
- //unset($params['code']);
- //$params['code'] = "";
- }
- // 生成签名
- $signature = $this->generateSignature($params);
- $params['signature'] = $signature;
- $url = $this->getTokenUrl();
- // 发送POST请求
- $response = HttpClient::post($url, $params, [
- 'Content-Type' => 'application/json',
- ]);
- Yii::info("[FengniaAuth] GetAccessToken Response: " . json_encode($response));
- return $this->parseResponse($response);
- }
- /**
- * 刷新AccessToken
- * 使用刷新令牌获取新的AccessToken
- *
- * 文档:刷新token接口
- * 入参:grant_type, app_id, merchant_id, timestamp, refresh_token, signature
- * 出参:access_token, refresh_token, expire_in
- *
- * @param string $refreshToken 刷新令牌
- * @param string $merchantId 商户ID(可选,如果已设置则使用已设置的)
- * @return array 返回格式:['success' => true/false, 'data' => [...], 'error' => '']
- */
- public function refreshAccessToken($refreshToken, $merchantId = null)
- {
- if ($merchantId) {
- $this->setMerchantId($merchantId);
- }
- if (empty($this->merchantId)) {
- return [
- 'success' => false,
- 'error' => '商户ID未设置',
- ];
- }
- $timestamp = (string)(time() * 1000); // 毫秒级时间戳
- $params = [
- 'grant_type' => self::GRANT_TYPE_REFRESH,
- 'app_id' => $this->appId,
- 'merchant_id' => $this->merchantId,
- 'timestamp' => $timestamp,
- 'refresh_token' => $refreshToken,
- ];
- // 生成签名
- $signature = $this->generateSignature($params);
- $params['signature'] = $signature;
- $url = $this->getRefreshTokenUrl();
- // 发送POST请求
- $response = HttpClient::post($url, $params, [
- 'Content-Type' => 'application/json',
- ]);
- Yii::info("[FengniaAuth] RefreshAccessToken Response: " . json_encode($response));
- return $this->parseResponse($response);
- }
- /**
- * 解析API响应
- *
- * 峰鸟开放平台 API 返回格式:
- * {
- * "sign": "返回值签名",
- * "code": "200|错误码",
- * "msg": "错误信息",
- * "business_data": {
- * "app_id": "应用id",
- * "merchant_id": "商户id",
- * "access_token": "凭证token",
- * "refresh_token": "刷新token",
- * "expire_in": "access_token剩余有效时间,单位:秒",
- * "re_expire_in": "refresh_token剩余有效时间"
- * }
- * }
- *
- * @param array $response HTTP响应
- * @return array 标准化的响应格式
- */
- protected function parseResponse($response)
- {
- // 检查 code 字段
- if (!isset($response['code'])) {
- return [
- 'success' => false,
- 'error' => $response['msg'] ?? '响应格式错误,缺少 code 字段',
- ];
- }
- // 如果 code 不等于 '200' 或 200,表示请求失败
- if ((string)$response['code'] !== '200') {
- return [
- 'success' => false,
- 'error' => $response['msg'] ?? 'API返回异常',
- 'code' => $response['code'],
- ];
- }
- // 提取 business_data 字段中的数据
- $businessData = $response['business_data'] ?? [];
- if (is_string($businessData)) {
- $businessData = json_decode($businessData, true);
- }
- // 检查是否包含必需的token信息
- if (empty($businessData['access_token'])) {
- return [
- 'success' => false,
- 'error' => $response['msg'] ?? '未获取到 access_token',
- ];
- }
- // 成功响应
- return [
- 'success' => true,
- 'data' => [
- 'access_token' => $businessData['access_token'] ?? '',
- 'refresh_token' => $businessData['refresh_token'] ?? '',
- 'app_id' => $businessData['app_id'] ?? '',
- 'merchant_id' => $businessData['merchant_id'] ?? '',
- 'expire_in' => $businessData['expire_in'] ?? 0,
- 're_expire_in' => $businessData['re_expire_in'] ?? 0,
- ],
- ];
- }
- /**
- * 获取AppId
- *
- * @return string
- */
- public function getAppId()
- {
- return $this->appId;
- }
- /**
- * 获取AppSecret
- *
- * @return string
- */
- public function getAppSecret()
- {
- return $this->appSecret;
- }
- /**
- * 获取商户ID
- *
- * @return string
- */
- public function getMerchantId()
- {
- return $this->merchantId;
- }
- /**
- * 获取是否为沙箱环境
- *
- * @return bool
- */
- public function isSandbox()
- {
- return $this->isSandbox;
- }
- }
|