You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

649 lines
20 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi.cn@gmail.com> <http://www.zjzit.cn>
// +----------------------------------------------------------------------
namespace Com;
class WechatAuth {
/* 消息类型常量 */
const MSG_TYPE_TEXT = 'text';
const MSG_TYPE_IMAGE = 'image';
const MSG_TYPE_VOICE = 'voice';
const MSG_TYPE_VIDEO = 'video';
const MSG_TYPE_SHORTVIDEO = 'shortvideo';
const MSG_TYPE_LOCATION = 'location';
const MSG_TYPE_LINK = 'link';
const MSG_TYPE_MUSIC = 'music';
const MSG_TYPE_NEWS = 'news';
const MSG_TYPE_EVENT = 'event';
/* 二维码类型常量 */
const QR_SCENE = 'QR_SCENE';
const QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
/**
* 微信开发者申请的appID
* @var string
*/
private $appId = '';
/**
* 微信开发者申请的appSecret
* @var string
*/
private $appSecret = '';
/**
* 获取到的access_token
* @var string
*/
private $accessToken = '';
/**
* 微信api根路径
* @var string
*/
private $apiURL = 'https://api.weixin.qq.com/cgi-bin';
/**
* 微信二维码根路径
* @var string
*/
private $qrcodeURL = 'https://mp.weixin.qq.com/cgi-bin';
private $requestCodeURL = 'https://open.weixin.qq.com/connect/oauth2/authorize';
private $qrconnectCodeURL = 'https://open.weixin.qq.com/connect/qrconnect';
private $oauthApiURL = 'https://api.weixin.qq.com/sns';
/**
* 构造方法调用微信高级接口时实例化SDK
* @param string $appid 微信appid
* @param string $secret 微信appsecret
* @param string $token 获取到的access_token
*/
public function __construct($appid, $secret, $token = null){
if($appid && $secret){
$this->appId = $appid;
$this->appSecret = $secret;
if(!empty($token)){
$this->accessToken = $token;
}
} else {
throw new \Exception('缺少参数 APP_ID 和 APP_SECRET!');
}
}
public function getRequestCodeURL($redirect_uri, $state = null,$scope = 'snsapi_userinfo'){
$query = array(
'appid' => $this->appId,
'redirect_uri' => $redirect_uri,
'response_type' => 'code',
'scope' => $scope,
);
if(!is_null($state) && preg_match('/[a-zA-Z0-9]+/', $state)){
$query['state'] = $state;
}
$query = http_build_query($query);
return "{$this->requestCodeURL}?{$query}#wechat_redirect";
}
public function getQrconnectURL($redirect_uri, $state = null,$scope = 'snsapi_login'){
$query = array(
'appid' => $this->appId,
'redirect_uri' => $redirect_uri,
'response_type' => 'code',
'scope' => $scope,
);
if(!is_null($state) && preg_match('/[a-zA-Z0-9]+/', $state)){
$query['state'] = $state;
}
$query = http_build_query($query);
return "{$this->qrconnectCodeURL}?{$query}#wechat_redirect";
}
/**
* 获取access_token用于后续接口访问
* @return array access_token信息包含 token 和有效期
*/
public function getAccessToken($type = 'client', $code = null){
$param = array(
'appid' => $this->appId,
'secret' => $this->appSecret
);
switch ($type) {
case 'client':
$param['grant_type'] = 'client_credential';
$url = "{$this->apiURL}/token";
break;
case 'code':
$param['code'] = $code;
$param['grant_type'] = 'authorization_code';
$url = "{$this->oauthApiURL}/oauth2/access_token";
break;
default:
throw new \Exception('不支持的grant_type类型');
break;
}
$token = self::http($url, $param);
$token = json_decode($token, true);
if(is_array($token)){
if(isset($token['errcode'])){
throw new \Exception($token['errmsg']);
} else {
$this->accessToken = $token['access_token'];
return $token;
}
} else {
throw new \Exception('获取微信access_token失败');
}
}
/**
* 获取授权用户信息
* @param string $openid 用户的OpenID
* @param string $lang 指定的语言
* @return array 用户信息数据,具体参见微信文档
*/
public function getUserInfo($openid, $lang = 'zh_CN'){
$query = array(
'access_token' => $this->accessToken,
'openid' => $openid,
'lang' => $lang,
);
$info = self::http("{$this->oauthApiURL}/userinfo", $query);
return json_decode($info, true);
}
/**
* 上传零时媒体资源
* @param string $filename 媒体资源本地路径
* @param string $type 媒体资源类型,具体请参考微信开发手册
*/
public function mediaUpload($filename, $type){
$filename = realpath($filename);
if(!$filename) throw new \Exception('资源路径错误!');
$data = array(
'type' => $type,
'media' => "@{$filename}"
);
return $this->api('media/upload', $data, 'POST', '', false);
}
/**
* 上传永久媒体资源
* @param string $filename 媒体资源本地路径
* @param string $type 媒体资源类型,具体请参考微信开发手册
* @param string $description 资源描述,仅资源类型为 video 时有效
*/
public function materialAddMaterial($filename, $type, $description = ''){
$filename = realpath($filename);
if(!$filename) throw new \Exception('资源路径错误!');
$data = array(
'type' => $type,
'media' => "@{$filename}",
);
if($type == 'video'){
if(is_array($description)){
//保护中文微信api不支持中文转义的json结构
array_walk_recursive($description, function(&$value){
$value = urlencode($value);
});
$description = urldecode(json_encode($description));
}
$data['description'] = $description;
}
return $this->api('material/add_material', $data, 'POST', '', false);
}
/**
* 获取媒体资源下载地址
* 注意:视频资源不允许下载
* @param string $media_id 媒体资源id
* @return string 媒体资源下载地址
*/
public function mediaGet($media_id){
$param = array(
'access_token' => $this->accessToken,
'media_id' => $media_id
);
$url = "{$this->apiURL}/media/get?";
return $url . http_build_query($param);
}
/**
* 给指定用户推送信息
* 注意微信规则只允许给在48小时内给公众平台发送过消息的用户推送信息
* @param string $openid 用户的openid
* @param array $content 发送的数据,不同类型的数据结构可能不同
* @param string $type 推送消息类型
*/
public function messageCustomSend($openid, $content, $type = self::MSG_TYPE_TEXT){
//基础数据
$data = array(
'touser'=>$openid,
'msgtype'=>$type,
);
//根据类型附加额外数据
$data[$type] = call_user_func(array(self, $type), $content);
return $this->api('message/custom/send', $data);
}
/**
* 发送文本消息
* @param string $openid 用户的openid
* @param string $text 发送的文字
*/
public function sendText($openid, $text){
return $this->messageCustomSend($openid, $text, self::MSG_TYPE_TEXT);
}
/**
* 发送图片消息
* @param string $openid 用户的openid
* @param string $media 图片ID
*/
public function sendImage($openid, $media){
return $this->messageCustomSend($openid, $media, self::MSG_TYPE_IMAGE);
}
/**
* 发送语音消息
* @param string $openid 用户的openid
* @param string $media 音频ID
*/
public function sendVoice($openid, $media){
return $this->messageCustomSend($openid, $media, self::MSG_TYPE_VOICE);
}
/**
* 发送视频消息
* @param string $openid 用户的openid
* @param string $media_id 视频ID
* @param string $title 视频标题
* @param string $discription 视频描述
*/
public function sendVideo(){
$video = func_get_args();
$openid = array_shift($video);
return $this->messageCustomSend($openid, $video, self::MSG_TYPE_VIDEO);
}
/**
* 发送音乐消息
* @param string $openid 用户的openid
* @param string $title 音乐标题
* @param string $discription 音乐描述
* @param string $musicurl 音乐链接
* @param string $hqmusicurl 高品质音乐链接
* @param string $thumb_media_id 缩略图ID
*/
public function sendMusic(){
$music = func_get_args();
$openid = array_shift($music);
return $this->messageCustomSend($openid, $music, self::MSG_TYPE_MUSIC);
}
/**
* 发送图文消息
* @param string $openid 用户的openid
* @param array $news 图文内容 [标题描述URL缩略图]
* @param array $news1 图文内容 [标题描述URL缩略图]
* @param array $news2 图文内容 [标题描述URL缩略图]
* ... ...
* @param array $news9 图文内容 [标题描述URL缩略图]
*/
public function sendNews(){
$news = func_get_args();
$openid = array_shift($news);
return $this->messageCustomSend($openid, $news, self::MSG_TYPE_NEWS);
}
/**
* 发送一条图文消息
* @param string $openid 用户的openid
* @param string $title 文章标题
* @param string $discription 文章简介
* @param string $url 文章连接
* @param string $picurl 文章缩略图
*/
public function sendNewsOnce(){
$news = func_get_args();
$openid = array_shift($news);
$news = array($news);
return $this->messageCustomSend($openid, $news, self::MSG_TYPE_NEWS);
}
/**
* 创建用户组
* @param string $name 组名称
*/
public function groupsCreate($name){
$data = array('group' => array('name' => $name));
return $this->api('groups/create', $data);
}
/**
* 查询所有分组
* @return array 分组列表
*/
public function groupsGet(){
return $this->api('groups/get', '', 'GET');
}
/**
* 查询用户所在的分组
* @param string $openid 用户的OpenID
* @return number 分组ID
*/
public function groupsGetid($openid){
$data = array('openid' => $openid);
return $this->api('groups/getid', $data);
}
/**
* 修改分组
* @param number $id 分组ID
* @param string $name 分组名称
* @return array 修改成功或失败信息
*/
public function groupsUpdate($id, $name){
$data = array('id' => $id, 'name' => $name);
return $this->api('groups/update', $data);
}
/**
* 移动用户分组
* @param string $openid 用户的OpenID
* @param number $to_groupid 要移动到的分组ID
* @return array 移动成功或失败信息
*/
public function groupsMemberUpdate($openid, $to_groupid){
$data = array('openid' => $openid, 'to_groupid' => $to_groupid);
return $this->api('groups/member/update', $data);
}
/**
* 用户设备注名
* @param string $openid 用户的OpenID
* @param string $remark 设备注名
* @return array 执行成功失败信息
*/
public function userInfoUpdateremark($openid, $remark){
$data = array('openid' => $openid, 'remark' => $remark);
return $this->api('user/info/updateremark', $data);
}
/**
* 获取指定用户的详细信息
* @param string $openid 用户的openid
* @param string $lang 需要获取数据的语言
*/
public function userInfo($openid, $lang = 'zh_CN'){
$param = array('openid' => $openid, 'lang' => $lang);
return $this->api('user/info', '', 'GET', $param);
}
/**
* 获取关注者列表
* @param string $next_openid 下一个openid在用户数大于10000时有效
* @return array 用户列表
*/
public function userGet($next_openid = ''){
$param = array('next_openid' => $next_openid);
return $this->api('user/get', '', 'GET', $param);
}
/**
* 创建自定义菜单
* @param array $button 符合规则的菜单数组,规则参见微信手册
*/
public function menuCreate($button){
$data = array('button' => $button);
return $this->api('menu/create', $data);
}
/**
* 获取所有的自定义菜单
* @return array 自定义菜单数组
*/
public function menuGet(){
return $this->api('menu/get', '', 'GET');
}
/**
* 删除自定义菜单
*/
public function menuDelete(){
return $this->api('menu/delete', '', 'GET');
}
/**
* 创建二维码,可创建指定有效期的二维码和永久二维码
* @param integer $scene_id 二维码参数
* @param integer $expire_seconds 二维码有效期0-永久有效
*/
public function qrcodeCreate($scene_id, $expire_seconds = 0){
$data = array();
if(is_numeric($expire_seconds) && $expire_seconds > 0){
$data['expire_seconds'] = $expire_seconds;
$data['action_name'] = self::QR_SCENE;
} else {
$data['action_name'] = self::QR_LIMIT_SCENE;
}
$data['action_info']['scene']['scene_id'] = $scene_id;
return $this->api('qrcode/create', $data);
}
/**
* 根据ticket获取二维码URL
* @param string $ticket 通过 qrcodeCreate接口获取到的ticket
* @return string 二维码URL
*/
public function showqrcode($ticket){
return "{$this->qrcodeURL}/showqrcode?ticket={$ticket}";
}
/**
* 长链接转短链接
* @param string $long_url 长链接
* @return string 短链接
*/
public function shorturl($long_url){
$data = array(
'action' => 'long2short',
'long_url' => $long_url
);
return $this->api('shorturl', $data);
}
/**
* 调用微信api获取响应数据
* @param string $name API名称
* @param string $data POST请求数据
* @param string $method 请求方式
* @param string $param GET请求参数
* @return array api返回结果
*/
protected function api($name, $data = '', $method = 'POST', $param = '', $json = true){
$params = array('access_token' => $this->accessToken);
if(!empty($param) && is_array($param)){
$params = array_merge($params, $param);
}
$url = "{$this->apiURL}/{$name}";
if($json && !empty($data)){
//保护中文微信api不支持中文转义的json结构
array_walk_recursive($data, function(&$value){
$value = urlencode($value);
});
$data = urldecode(json_encode($data));
}
$data = self::http($url, $params, $data, $method);
return json_decode($data, true);
}
/**
* 发送HTTP请求方法目前只支持CURL发送请求
* @param string $url 请求URL
* @param array $param GET参数数组
* @param array $data POST的数据GET请求时该参数无效
* @param string $method 请求方法GET/POST
* @return array 响应数据
*/
protected static function http($url, $param, $data = '', $method = 'GET'){
$opts = array(
CURLOPT_TIMEOUT => 30,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
);
/* 根据请求类型设置特定参数 */
$opts[CURLOPT_URL] = $url . '?' . http_build_query($param);
if(strtoupper($method) == 'POST'){
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $data;
if(is_string($data)){ //发送JSON数据
$opts[CURLOPT_HTTPHEADER] = array(
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($data),
);
}
}
/* 初始化并执行curl请求 */
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
//发生错误,抛出异常
if($error) throw new \Exception('请求发生错误:' . $error);
return $data;
}
/**
* 构造文本信息
* @param string $content 要回复的文本
*/
private static function text($content){
$data['content'] = $content;
return $data;
}
/**
* 构造图片信息
* @param integer $media 图片ID
*/
private static function image($media){
$data['media_id'] = $media;
return $data;
}
/**
* 构造音频信息
* @param integer $media 语音ID
*/
private static function voice($media){
$data['media_id'] = $media;
return $data;
}
/**
* 构造视频信息
* @param array $video 要回复的视频 [视频ID标题说明]
*/
private static function video($video){
$data = array();
list(
$data['media_id'],
$data['title'],
$data['description'],
) = $video;
return $data;
}
/**
* 构造音乐信息
* @param array $music 要回复的音乐[标题说明链接高品质链接缩略图ID]
*/
private static function music($music){
$data = array();
list(
$data['title'],
$data['description'],
$data['musicurl'],
$data['hqmusicurl'],
$data['thumb_media_id'],
) = $music;
return $data;
}
/**
* 构造图文信息
* @param array $news 要回复的图文内容
* [
* 0 => 第一条图文信息[标题,说明,图片链接,全文连接]
* 1 => 第二条图文信息[标题,说明,图片链接,全文连接]
* 2 => 第三条图文信息[标题,说明,图片链接,全文连接]
* ]
*/
private static function news($news){
$articles = array();
foreach ($news as $key => $value) {
list(
$articles[$key]['title'],
$articles[$key]['description'],
$articles[$key]['url'],
$articles[$key]['picurl']
) = $value;
if($key >= 9) break; //最多只允许10条图文信息
}
$data['articles'] = $articles;
return $data;
}
}