Skip to content

Instantly share code, notes, and snippets.

@haolian9
Last active October 15, 2015 03:53
Show Gist options
  • Save haolian9/31350695ff5d68addf36 to your computer and use it in GitHub Desktop.
Save haolian9/31350695ff5d68addf36 to your computer and use it in GitHub Desktop.
Yii 2 authclient for wechat-public-account
<?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