Last active
October 15, 2015 03:53
-
-
Save haolian9/31350695ff5d68addf36 to your computer and use it in GitHub Desktop.
Yii 2 authclient for wechat-public-account
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace app\modules\stuff\oauthclients; | |
use CURLFile; | |
use yii\authclient\OAuthToken; | |
use yii\base\DynamicModel; | |
use yii\web\HttpException; | |
use yii\helpers\Json; | |
use yii\helpers\ArrayHelper; | |
/** | |
* WeChat class file. | |
* @Author haoliang | |
* @Date 13.10.2015 10:20 | |
*/ | |
class WeChat extends \yii\authclient\OAuth2 | |
{ | |
const TYPE_IMAGE = 'image', | |
TYPE_VIDEO = 'video', | |
TYPE_VOICE = 'voice', | |
TYPE_ARTICLE = 'news', | |
TYPE_CONTENT_IMAGE = 'content_image'; | |
public $apiBaseUrl = 'https://api.weixin.qq.com/cgi-bin'; | |
public $tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token'; | |
/** | |
* @brief fetchAccessToken | |
* | |
* http://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html | |
* | |
* @param $params | |
* | |
* @return OAuthToken | |
*/ | |
public function fetchAccessToken() | |
{/*{{{*/ | |
$defaultParams = [ | |
'appid' => $this->clientId, | |
'secret' => $this->clientSecret, | |
'grant_type' => 'client_credential' | |
]; | |
$response = $this->sendRequest('GET', $this->tokenUrl, $defaultParams); | |
$token = $this->createToken(['params' => $response]); | |
$this->setAccessToken($token); | |
return $token; | |
}/*}}}*/ | |
/** | |
* @brief getTotal | |
* | |
* 获取素材总数 | |
* | |
* http://mp.weixin.qq.com/wiki/16/8cc64f8c189674b421bee3ed403993b8.html | |
* | |
* @return mixed | |
*/ | |
public function getTotal() | |
{/*{{{*/ | |
return $this->api('material/get_materialcount'); | |
}/*}}}*/ | |
/** | |
* @brief postArticle | |
* | |
* 新增文章素材: 文章数量不定 | |
* http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html | |
* todo | |
* | |
* @return mixed | |
*/ | |
public function postArticle(array $articles) | |
{/*{{{*/ | |
$params = [ | |
'articles' => $articles, | |
]; | |
return $this->api('material/add_news', 'POST', $params); | |
}/*}}}*/ | |
/** | |
* @brief updateArticle | |
* | |
* 修改文章素材 | |
* http://mp.weixin.qq.com/wiki/4/19a59cba020d506e767360ca1be29450.html | |
* | |
* @param $media_id | |
* @param $articles 名为articles, 实则只代表一个素材里的一篇文章 | |
* @param $index | |
* | |
* @return mixed | |
*/ | |
public function updateArticle($media_id, $articles, $index = 0) | |
{/*{{{*/ | |
$model = DynamicModel::validateData(compact('media_id', 'articles', 'index'), [ | |
[['media_id', 'articles', 'index'], 'required'], | |
['articles', 'safe'], | |
['media_id', 'string'], | |
['index', 'integer', 'min' => 0], | |
]); | |
if ($model->hasErrors()) | |
throw HttpException(400, implode(' ', $model->getErrors()) ); | |
$params = $model->getAttributes(); | |
return $this->api('material/update_news', 'POST', $params); | |
}/*}}}*/ | |
/** | |
* @brief deleteStuff | |
* | |
* 删除素材 | |
* http://mp.weixin.qq.com/wiki/5/e66f61c303db51a6c0f90f46b15af5f5.html | |
* | |
* @param $media_id | |
* | |
* @return mixed | |
*/ | |
public function deleteStuff($media_id) | |
{/*{{{*/ | |
if (empty($media_id)) | |
throw new HttpException('media_id must be given.'); | |
return $this->api('material/del_material', 'POST', ['media_id' =>$media_id]); | |
}/*}}}*/ | |
/** | |
* @brief getBatch Image | |
* | |
* 批量获取图片素材 | |
* | |
* @param $offset integer | |
* @param $count integer | |
* | |
* @return mixed | |
*/ | |
public function getBatchArticles($offset, $count) | |
{/*{{{*/ | |
return $this->getBatch(self::TYPE_ARTICLE, $offset, $count); | |
}/*}}}*/ | |
/** | |
* @brief getBatchImages | |
* | |
* 批量获取图文素材 | |
* | |
* @param $offset | |
* @param $count | |
* | |
* @return mixed | |
*/ | |
public function getBatchImages($offset, $count) | |
{/*{{{*/ | |
return $this->getBatch(self::TYPE_IMAGE, $offset, $count); | |
}/*}}}*/ | |
/** | |
* @brief getBatch | |
* | |
* 批量获取素材, 素材类型: self::TYPE_* | |
* http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html | |
* | |
* @param $type | |
* @param $offset | |
* @param $count | |
* | |
* @return | |
*/ | |
public function getBatch($type, $offset, $count) | |
{/*{{{*/ | |
$model = DynamicModel::validateData(compact('type', 'offset', 'count'), [ | |
[['offset', 'count', 'type'], 'required'], | |
['type', 'string'], | |
['offset', 'integer', 'min' => 0], | |
['count', 'integer', 'min' => 0, 'max' => 20], | |
]); | |
if ($model->hasErrors()) { | |
throw HttpException(400, implode(' ', $model->getErrors()) ); | |
} | |
$params = $model->getAttributes(); | |
return $this->api('material/batchget_material', 'POST', $params); | |
}/*}}}*/ | |
/** | |
* @brief uploadImage | |
* | |
* 上传图片素材 | |
* | |
* @return | |
*/ | |
public function uploadImage($fileName) | |
{/*{{{*/ | |
$params = ['type' => self::TYPE_IMAGE]; | |
return $this->uploadMedia('material/add_material', $fileName, self::TYPE_IMAGE, $params); | |
}/*}}}*/ | |
/** | |
* @brief uploadContentImage | |
* | |
* 上传图片(非图片素材) | |
* | |
* @return mixed | |
* @throws HttpException | |
*/ | |
public function uploadContentImage($fileName) | |
{/*{{{*/ | |
return $this->uploadMedia('media/uploadimg', $fileName, self::TYPE_CONTENT_IMAGE); | |
}/*}}}*/ | |
/** | |
* @brief uploadMedia | |
* | |
* 上传媒体 | |
* http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html | |
* | |
* @param $fileName | |
* @param $type 仅支持 self::TYPE_IMAGE, self::TYPE_CONTENT_IMAGE | |
* @param $params | |
* | |
* @return mixed | |
*/ | |
public function uploadMedia($url, $fileName, $type, array $params = []) | |
{/*{{{*/ | |
if (empty($fileName) || ! file_exists($fileName)) { | |
throw new HttpException('invalid file'); | |
} | |
/* 类型约束 */ | |
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $fileName); | |
if (empty($mime) || ! in_array($mime, $this->supportFormats($type))) { | |
throw new HttpException('invalid file mime, can only `png` and `jpeg`, given ' . $mime); | |
} | |
# todo 使用 CURLFile 方式实现 | |
$params = array_merge([ | |
#'media' => new CURLFile($fileName, $mime, 'sb'), | |
'media' => "@{$fileName}", | |
], $params); | |
return $this->requestWithUpload($url, $params); | |
}/*}}}*/ | |
/** | |
* @brief refreshAccessToken | |
* | |
* 微信并未提供刷新accessToken 接口 | |
* | |
* @param $token | |
* | |
* @return | |
*/ | |
public function refreshAccessToken(OAuthToken $token) | |
{/*{{{*/ | |
return $this->fetchAccessToken(); | |
}/*}}}*/ | |
/** | |
* @brief composeRequestCurlOptions | |
* | |
* @param $method | |
* @param $url | |
* @param $params | |
* | |
* @return dict | |
*/ | |
protected function composeRequestCurlOptions($method, $url, array $params) | |
{/*{{{*/ | |
$curlOptions = []; | |
switch ($method) { | |
case 'GET': { | |
$curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params); | |
break; | |
} | |
case 'POST': { | |
$curlOptions[CURLOPT_POST] = true; | |
$curlOptions[CURLOPT_HTTPHEADER] = ['Content-type: application/x-www-form-urlencoded']; | |
/* | |
* 微信的 access_token 采用 url 方式传递, | |
* 而其他参数需要打包成 json 再 post 传递 | |
*/ | |
$curlOptions[CURLOPT_URL] = $this->composeUrl($url, [ 'access_token' => $params['access_token'] ]); | |
unset($params['access_token']); | |
$curlOptions[CURLOPT_POSTFIELDS] = Json::encode($params); | |
break; | |
} | |
case 'HEAD': { | |
$curlOptions[CURLOPT_CUSTOMREQUEST] = $method; | |
if (!empty($params)) { | |
$curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params); | |
} | |
break; | |
} | |
default: { | |
$curlOptions[CURLOPT_CUSTOMREQUEST] = $method; | |
if (!empty($params)) { | |
$curlOptions[CURLOPT_POSTFIELDS] = $params; | |
} | |
} | |
} | |
return $curlOptions; | |
}/*}}}*/ | |
/** | |
* @brief requestWithUpload | |
* | |
* post-only 上传文件 | |
* | |
* @param $apiSubUrl | |
* @param $params | |
* @param $headers | |
* | |
* @return mixed | |
* @throws HttpException | |
*/ | |
protected function requestWithUpload($apiSubUrl, array $params = [], array $headers = []) | |
{/*{{{*/ | |
if (preg_match('/^https?:\\/\\//is', $apiSubUrl)) { | |
$url = $apiSubUrl; | |
} else { | |
$url = $this->apiBaseUrl . '/' . $apiSubUrl; | |
} | |
$accessToken = $this->getAccessToken(); | |
if (!is_object($accessToken) || !$accessToken->getIsValid()) { | |
throw new HttpException(400, 'Invalid access token.'); | |
} | |
$curlOptions = $this->mergeCurlOptions( | |
$this->defaultCurlOptions(), | |
$this->getCurlOptions(), | |
[ | |
# default method: post | |
CURLOPT_POST => true, | |
CURLOPT_HTTPHEADER => ['Content-type: multipart/form-data'], | |
CURLOPT_RETURNTRANSFER => true, | |
], | |
[ | |
CURLOPT_HTTPHEADER => $headers, | |
CURLOPT_POSTFIELDS => $params, | |
CURLOPT_URL => $this->composeUrl($url, ['access_token' => $accessToken->getToken()]), | |
] | |
); | |
$curlResource = curl_init(); | |
foreach ($curlOptions as $option => $value) { | |
curl_setopt($curlResource, $option, $value); | |
} | |
$response = curl_exec($curlResource); | |
$responseHeaders = curl_getinfo($curlResource); | |
// check cURL error | |
$errorNumber = curl_errno($curlResource); | |
$errorMessage = curl_error($curlResource); | |
curl_close($curlResource); | |
if ($errorNumber > 0) { | |
throw new Exception('Curl error requesting "' . $url . '": #' . $errorNumber . ' - ' . $errorMessage); | |
} | |
if (strncmp($responseHeaders['http_code'], '20', 2) !== 0) { | |
throw new InvalidResponseException($responseHeaders, $response, 'Request failed with code: ' . $responseHeaders['http_code'] . ', message: ' . $response); | |
} | |
return $this->processResponse($response, $this->determineContentTypeByHeaders($responseHeaders)); | |
}/*}}}*/ | |
/** | |
* @brief supportFormats | |
* | |
* 不同类型有不同的支持类型 | |
* | |
* @return list | |
*/ | |
protected function supportFormats($type) | |
{/*{{{*/ | |
$formats = [ | |
self::TYPE_IMAGE => ['image/bmp', 'image/png', 'image/jpeg', 'image/jpg', 'image/gif'], | |
self::TYPE_CONTENT_IMAGE => ['image/jpg', 'image/png'], | |
]; | |
return ArrayHelper::getValue($formats, $type, []); | |
}/*}}}*/ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment