最近学习了关于微信公众号开发的相关知识,为了帮助自己更好的理解,在此重新再梳理一遍
更多关于微信公众号开发的功能可以参考微信公众平台的开发技术文档
完成开发者配置
第一步,需要在微信公众平台配置我们的服务器
在接口的文件需要写入以下代码以完成验证:
class Wxapi{ public function __construct() { $this->index(); } public function index() { $echostr = isset($_GET['echostr']) ? $_GET['echostr'] : ''; if($$this->checkSignature && $echostr){ echo $echostr; } else { $this->responseMsg(); } } public function checkSignature() { $token = "weixin"; $timestamp() = $_GET['timestamp']; $nonce = $_GET['nonce']; $signature = $_GET['signature']; $tmpArr = array($token, $nonce, $timestamp); sort($tmpArr); if($tmpArr == $signature){ return true; } else { return false; } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
curl方法
在微信公众号开发过程中,我们的第三方服务器(公众号中配置的接口服务器)与微信之间的通信都是用curl的方式来完成的,下面写一下我开发过程中用到的curl实例方法
class Wxapi{ public function httpCurl($url, $type='get', $postData='') { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); if($type == 'post'){ curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); } $res = curl_exec($ch); if(curl_errno($ch)){ return curl_error($ch); } curl_close($ch); return $res; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
获取access_token
这里的access_token是指的全局access_token 在调用很多微信后台的接口时都需要用access_token验证,通过后才可以调用相关接口实现相应的功能,我们可以通过appid和appsecret获取到我们的access_token
class Wxapi{ public function getWxAccessToken() { if($_SESSION['access_token'] && $_SESSION['expire_time'] > time()){ return $_SESSION['access_token']; } else { $appid = ''; $appsecret = ''; $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$appsecret; $res = $this->httpCurl($url); $res = json_decode($res, true); $access_token = $res['access_token']; $_SESSION['access_token'] = $access_token; $_SESSION['expire_time'] = time()+7000; return $access_token(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
获取微信服务器的IP地址
很多时候我们会收到微信传过来的信息,但是我们并不清楚这些信息是不是真的是由微信服务器传过来的,因此微信也提供了一个接口,返回微信服务器的地址以便我们验证信息来源;
class Wxapi{ public function getWxServerIp() { $access_token = $this->getWxAccessToken(); $url = 'https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token='.$access_token; $res = $this->httpCurl($url); $arr = json_decode($res,true); return $arr; }}
自定义菜单
自定义菜单是公众号常用的一个功能,每个公众号有自己的定位,菜单的内容当然也不同,自定义菜单也需要使用全局access_token来完成。下面用实例写两种常用的菜单类型,更多内容请参考开发文档
public function defineMenu(){ $access_token = $this->getWxAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=".$access_token; $postArr = array( 'button' => array( array( 'name' => '菜单名', 'type' => 'click', 'key' => 'EventKey' ), array( 'name' => '', 'sub_button' => array( array( 'name' => '', 'type' => 'view', 'url' => '' ) ) ) ) ); $postJson = json_encode($postArr); $res = $this->httpCurl($url, 'post', $postJson);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
事件回复及关键字回复
验证通过后,可以开始配置我们公众号常用的一些功能,这部分主要是事件回复以及关键回复
事件回复我这里主要指的用户关注的事件,具体消息的模版参考微信公众平台开发模版的消息管理
class Wxapi{ public function responseMsg() { $postArr = $GLOBALS['HTTP_RAW_POST_DATA']; if(!empty($postArr)){ $postObj = simplexml_load_string($postArr); if(strtolower($postObj->MsgType) == 'event'){ if(strtolower($postObj->Event) == 'subcribe'){ $content = ''; $info = $this->responseText($postObj, $content); echo $info; } elseif(strtolower($postObj->Event) == 'click'){ switch($postObj->EventKey){ case '': $content = ''; $info = $this->responseText($postObj, $content); echo $info; break; case '': $arr = array( array( 'title' => '潮图|你的姆爷Slim Shady', 'description' => '高能预警', 'picUrl' => '', 'url' => '', ) ); $info = $this->responseNews($postObj, $arr); echo $info; break; } } elseif(strtolower($postObj->Event) == 'scan') { switch ($postObj->EventKey){ case 2000: $content = ""; $info = $this->responseText($postObj, $content); echo $info; break; } } } if(strtolower($postObj->MsgType) == 'text'){ $keyword = trim($postObj->Content); if(!empty($keyword)) { if($keyword == 'slimshady'){ $arr = array( array( 'title' => '', 'description' => '', 'picUrl' => '', 'url' => '', ), ); $info = $this->responseNews($postObj, $arr); echo $info; } switch ($keyword) { case 'shadypop': $content = "<a href='http://jason9351.get.vip'>欢迎访问我的主页</a>"; break; default: $content = "表示看不懂你说的"; break; } $info = $indexModel->responseText($postObj, $content); echo $info; } } } } public function responseText($postObj, $content) { $toUser = $postObj->FromUserName; $fromUser = $postObj->ToUserName; $time = time(); $textTpl = "<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[%s]]></Content> </xml>"; $info = sprintf($textTpl, $toUser, $fromUser, $time, $content); return $info; } public function responseNews($postObj, $arr) { $toUser = $postObj->FromUserName; $fromUser = $postObj->ToUserName; $time = time(); $newsTpl = "<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>".count($arr)."</ArticleCount> <Articles>"; foreach ($arr as $k => $v) { $newsTpl .= "<item> <Title><![CDATA[".$v['title']."]]></Title> <Description><![CDATA[".$v['description']."]]></Description> <PicUrl><![CDATA[".$v['picUrl']."]]></PicUrl> <Url><![CDATA[".$v['url']."]]></Url> </item>"; } $newsTpl .= "</Articles> </xml>"; $info = sprintf($newsTpl, $toUser, $fromUser, $time); return $info; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
生成带参数的二维码
目前微信支持生成临时二维码和永久二维码两种。临时二维码有效期为30天,主要用于帐号绑定等不要求二维码永久保存的业务场景。永久二维码没有过期时间,但是数量有限,为10万个,主要用于适用于帐号绑定、用户来源统计等场景。
用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
生成二维码分为两步:首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。
创建二维码ticket
每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id);
临时二维码:
public function getTmpQRCode(){ $access_token = $this->getWxAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=".$access_token; $postArr = array( 'expire_seconds' => 604800, 'action_name' => 'QR_SCENE', 'action_info' => array( 'scene' => array('scene_id' => '自行设定') ) ); $postJson = json_encode($postArr); $res = $this->httpCurl($url, 'post', $postJson); $res = json_decode($res, true); $ticket = urlencode($res['ticket']); $url = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='.$ticket; echo "<img src='{$url}' />";}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
永久二维码:
永久二维码与临时二维码不同就在于action_name不同,并且没有expire_time:
public function getInsQRCode(){ $access_token = $this->getWxAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=".$access_token; $postArr = array( 'action_name' => 'QR_LIMIT_SCENE', 'action_info' => array( 'scene' => array('scene_id' => '') ) ); $postJson = json_encode($postArr); $res = $this->httpCurl($url, 'post', $postJson); $res = json_decode($res, true); $ticket = urlencode($res['ticket']); $url = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='.$ticket; echo "<img src='{$url}' />";}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
获取网页授权以及用户的基本信息
在用户同意授权的情况下,我们可以获取到用户的一些基本信息,来实现我们的业务逻辑
关于授权回调域名的两点说明:
1.在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
2.授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权
另外, 网页授权也分为两种类型,分别是scope_base和scope_userinfo,两者有以下区别:
1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)//即无需用户点击同意
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
具体而言,网页授权流程分为四步:
1、引导用户进入授权页面同意授权,获取code
2、通过code换取网页授权access_token(与基础支持中的access_token不同)
3、如果需要,开发者可以刷新网页授权access_token,避免过期
4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
以下为实例:
只获取用户openid,scope_base类型
public function getBaseInfo(){ $appid = ''; //上文提及的公众号自己的appid $redirectUri = urlencode('调用getUserOpenId的uri'); $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='.$appId.'&redirect_uri='.$redirectUri.'&response_type=code&scope=snsapi_base&state=123#wechat_redirect'; header('location:'.$url); //此步仅做演示,实际业务中引导用户访问调用此方法的链接来跳转至redirectUrl}public function getUserOpenId(){ $appId = 'wx693062b792d58274'; $appSecret = '885cab15b3d47e0ccddfb065da734eff'; $code = $_GET['code']; //getBaseInfo 方法传来的code $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.$appId.'&secret='.$appSecret.'&code='.$code.'&grant_type=authorization_code'; $res = $this->httpCurl($url); //返回值的格式:{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
至此,我们就获取到了我们需要的openid值。
获取用户的详细信息
public function getBaseInfo(){ $appid = ''; //上文提及的公众号自己的appid $redirectUri = urlencode('调用getUserInfo的uri'); $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='.$appId.'&redirect_uri='.$redirectUri.'&response_type=code&scope=snsapi_base&state=123#wechat_redirect'; header('location:'.$url); //此步仅做演示,实际业务中引导用户访问调用此方法的链接来跳转至redirectUrl}public function getUserInfo(){ //获取openid和网页授权access_token $appId = 'wx693062b792d58274'; $appSecret = '885cab15b3d47e0ccddfb065da734eff'; $code = $_GET['code']; //getBaseInfo 方法传来的code $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.$appId.'&secret='.$appSecret.'&code='.$code.'&grant_type=authorization_code'; $res = $this->httpCurl($url); //返回值的格式:{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" } $res = json_decode($res, true); $access_token = $res['access_token']; $openid = $res['openid']; //获取用户的详细信息 $url = ' https://api.weixin.qq.com/sns/userinfo?access_token='.$access_token.'&openid='.$openid.'&lang=zh_CN'; $arr = $this->httpCurl($url);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
此处返回以下格式的内容:
正确时返回的JSON数据包如下:{"openid":" OPENID", "nickname": NICKNAME, "sex":"1", "province":"PROVINCE" "city":"CITY", "country":"COUNTRY", "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", "privilege":[ "PRIVILEGE1" "PRIVILEGE2" ], "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" }