2019独角兽企业重金招聘Python工程师标准>>>
鉴于美国国防部网站 http://sxsxssx.com 是用yii开发的,自然的我新建了一个wechat模块,域名 http://sxsxssx.com/wechat 是微信端部分。
对于在微信端登陆的每个会员,都自动生成一个新会员,下次登陆直接使用。
开始改造
鉴于这样一个小站,我决定直接改造他的user表。老的是
大家知道微信有个 open_id 代表每个公众号的会员,每个人针对于每个公众号都是不一样的,于是我为user表增加了一个 open_id 字段,用来标记会员。对于 open_id 为 NULL 时,代表不来自于微信端。
关于 open_id 长度问题,迄今为止微信也没有给出此字段应该多长,经多方查证和统计,一般在28、29、30之间徘徊,所以针对此字段,设置为32位的 varchar 应该是没啥问题的,事实上北哥也一直这样干的。
@@nai8@@
微信网页授权
接下来我们要研究当一个人通过微信浏览器打开客户网站的时候,如何识别身份那?看微信网页授权文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141217&token=&lang=zh_CN
先读一遍文档再往下看。
**简单的说:**如果用户在微信浏览器中访问第三方网页,可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
换句话说,就是我们有机会获得当前正在浏览网页的会员基本信息,那么思路就来了,草草的画个流程图。
大体内容是这样的,我将微信授权后的open_id存到session中,下次访问页面时,如果session中有open_id则根据它从数据库中去对应会员信息然后继续访问页面,如果session中open_id不存在,则进入微信授权获得open_id,然后看看数据库中是否有对应会员,如果有则拿出来然后将open_id再存到session,如果没有则新建一个含open_id的会员,然后存session。
所以其实重点就落在了获取open_id上,通过微信的web授权文档,我知道它是一定拿的到的,我先把上面流程图的代码实现下。
考虑到对于wechat模块,所有页面我都需要在web授权下去访问,因此对于wechat/controllers 我抽象出一个父类,父类完成微信的授权和获取当前访问的微信会员信息等职责。
构造WxBase
我决定为WxBase增加一个叫做$wxLogin的变量,存放当前获取的微信会员。代码如下
namespace app\modules\wechat\controllers;use Yii;
use yii\web\Controller;
class WxBase extends Controller {public $wxLogin = null;public function init(){parent::init();$this->initWxAuth(); // 获取登陆会员信息}protected function initWxAuth(){$session = Yii::$app->session;$wxOpenId = $session["wx_open_id"];if(empty($wxOpenId)){// todo 去做授权机制die("我要去授权,别拦我。");}$user = User::find()->where(['open_id'=>$wxOpenId])->one();$this->wxLogin = $user;}
}
上面的逻辑其实是模拟了用户 登陆 / 注册 的思路,只不过这种 登陆 / 注册 是微信web授权授权而已。
好,接下来我们主攻如何通过微信web授权完成 open_id 的获取以及会员初始化的问题。
真正核心的东西来了
通过对文档的阅读,我明白需要做几项配置。
- 先到公众平台官网修改授权回调域名。
- 我们需要知道公众号的 AppID 和 AppSecret
好在我们的上一篇让大家准备了 微信公众平台接口测试帐号 ,足以满足。
存放 AppID 和 AppSecret
考虑到 AppID 和 AppSecret 属于配置项,且将来可能具有更多的配置,因此我在程序的 config/params.php 中 新建了一个数组key用于存放所有的微信相关参数。
return ['adminEmail' => 'admin@example.com','wechat'=>['appId'=>'appId-Key','appSecret'=>'appSecret-Key',]
];
配置回调域名
对于测试号 我们可以通过 体验接口权限表 的 网页授权获取用户基本信息 找到它
对于正式环境 需要在“开发 -接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中。
正式服务器需要验证服务器可靠性,下载一个txt文件到你服务器根目录然后检查通过才能提交。有时候微信服务器可能抽风,即使你按步骤做了,通过浏览器也能访问上传的txt文件内容,但是仍然不通过,一般过会再试就没事了。
natapp映射本地
然后我通过natapp映射远程域名到我本地目录,一切就绪了。
我映射的域名为 http://abei.natapp2.cc 到 本地。
好,现在我用微信开发者工具 访问 http://abei.natapp2.cc/index.php?r=wechat
我要去授权,别拦我。,哈哈,要的就是它。好,我开始写授权。
先看下微信给我们的步骤
- 第一步:用户同意授权,获取code
- 第二步:通过code换取网页授权access_token
- 第三步:刷新access_token(如果需要)
- 第四步:拉取用户信息(需scope为 snsapi_userinfo)
- 附:检验授权凭证(access_token)是否有效
用户同意授权,获取code
namespace app\modules\wechat\controllers;use Yii;
use yii\web\Controller;
class WxBase extends Controller {public $wxLogin = null;......protected function initWxAuth(){$session = Yii::$app->session;$wxOpenId = $session["wx_open_id"];if(empty($wxOpenId)){// todo 去做授权机制$this->wxRedirectOauth2();}$user = User::find()->where(['open_id'=>$wxOpenId])->one();$this->wxLogin = $user;}protected function wxRedirectOauth2(){$url = Yii::$app->request->getUrl();$conf = Yii::$app->params['wechat'];$redirect_url = Url::to(['/wechat/default/oauth2','url'=>$url],true);$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=".$conf['appId']."&redirect_uri=".urlencode($redirect_url)."&response_type=code&scope=snsapi_userinfo&state=#wechat_redirect";header('Location:'.$url);exit();}
}
上面的代码我是想获得用户授权,并且得到code。我们使用index.php?r=wechat/default/oauth2 作为授权后重定向的回调链接地址。为了教学方便,我采用了snsapi_userinfo作用域,它会弹出一个授权对话框。
少言,看效果。
在逻辑上看,用户点击确认登陆后,微信会引导访问一个叫做 http://abei.natapp2.cc/index.php?r=wechat/default/oauth2&url=xxxx 的页面。
但是这样存在一个问题,原则上这个地址是用来获取code的,但是因为我们做了一个父类WxBase,并且针对每一个 wechat 模块下的action,我们都做了 initWxAuth 工作,那就会导致当yii访问 wechat/default/oauth2 时候发现 Session 中不存在 wx_open_id 不存在,然后继续访问 我们刚定义的 wxRedirectOauth2函数,形成死循环,页面无限刷新。
因此我们需要将 wechat/default/oauth2 这个action排除在 initWxAuth 之外,所以我们在授权页面 确认登陆 前再对代码进行一点小修改。
namespace app\modules\wechat\controllers;use Yii;
use yii\web\Controller;
class WxBase extends Controller {public $wxLogin = null;public $notAuths = ['wechat/default/oauth2'];public function init(){parent::init();if(in_array(Yii::$app->requestedRoute,$this->notAuths) == false){$this->initWxAuth();}}......
}
考虑程序扩展性,我将不需要授权的路由放到了一个数组中(你甚至可将其作为参数配置,那样更好。)
顺便我们写一下 wechat/default/oauth2
namespace app\modules\wechat\controllers;class DefaultController extends WxBase {/*** Renders the index view for the module* @return string*/public function actionIndex(){return $this->render('index');}public function actionOauth2(){echo "我是oauth2";}
}
好,再一次看效果。
很好,一起都在控制中,的确微信引导我们来到了 wechat/default/oauth2 ,并且我们来看看 这个url
http://abei.natapp2.cc/index.php?r=wechat%2Fdefault%2Foauth2&url=%2Findex.php%3Fr%3Dwechat&code=051af2HX0141A02cTLJX02hJGX0af2Hw&state=
你是否发现,多了url、code、state,那么他们是什么那?
- url 是我们的 oauth2 处理完逻辑后进入的页面,也就是我本次要访问的目标页面。
- code 这个好,就是我要的,通过它我能得到access token,进而获取open_id等。
- state 一个可以传递信息的字段,这里没有使用。
通过code换取网页授权access_token
接下来的好戏都有oauth2 这个action来主导,我们先去获得access_token。
namespace app\modules\wechat\controllers;class DefaultController extends WxBase {....public function actionOauth2(){// 得到Get传递过来的code 和 url$code = Yii::$app->request->get('code');$url = Yii::$app->request->get('url');$conf = Yii::$app->params['wechat'];$accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";$result = $this->HttpRequest($accessTokenUrl);echo $result;}/*** 使用curl获取url返回结果。* @param $url* @return mixed*/public function HttpRequest($url) {$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);curl_setopt($ch, CURLOPT_TIMEOUT, 2);$result = curl_exec($ch);return $result;}
}
根据文档我将appId、appSecret和这次的code传递给微信服务器以请求授权access_token,为了看的方便,我将微信返回的结果直接打印下来。
这里的 HttpRequest 是通过curl在php发起的GET 请求,它当前可以使用,但是如果用于生产环境,请再优化。
看下微信返回的。
结果让人欣慰,微信给我返回了 access_token、openid、expires_in 等等。参数的解释如下图
对于unionid,必须将公众号绑定到开放平台才会有,具体作用以后会有说,这里先过。
看看我们现在
我们已经得到了 openid 不是么?马上去完善代码。
namespace app\modules\wechat\controllers;class DefaultController extends WxBase {....public function actionOauth2(){// 得到Get传递过来的code 和 url$code = Yii::$app->request->get('code');$url = Yii::$app->request->get('url');$conf = Yii::$app->params['wechat'];$accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";$result = $this->HttpRequest($accessTokenUrl);$data = Json::decode($result);if($data && isset($data['errcode']) === false){$model = User::find()->where(['open_id' => $data['openid']])->one();if($model === false){$model = new User();$model->username = "wx-".rand(100000,999999);$model->password = md5('123456');$model->create_time = time();$model->open_id = $data['openid'];$model->save();}$session = \Yii::$app->session;$session['wx_open_id'] = $data['openid'];return $this->redirect($url);}}...
}
看下效果吧。
页面经过授权后进入了我们访问的目标页面,再看看数据库。
成功了~~~~
完事了么?
从代码上说,我们已经完成了上面流程图的内容,登陆页面可以对新会员进行初始化,并且可以在每个页面获得当前登陆的会员信息($this->wxLogin中),但是你也注意到了,在会员初始化的时候,username等等我们是用了随机值,而微信在授权的最后一步是允许我们通过access_token拉取用户信息的,我们实现一下。
拉取用户信息(需scope为 snsapi_userinfo)
在我的数据库设计中,username是登陆用户名,必须唯一,因此放微信信息的昵称是不合理的,下面代码只将解信息拉取过程,具体你要讲拉取的信息放到何处具体程序具体看要。
namespace app\modules\wechat\controllers;class DefaultController extends WxBase {....public function actionOauth2(){// 得到Get传递过来的code 和 url$code = Yii::$app->request->get('code');$url = Yii::$app->request->get('url');$conf = Yii::$app->params['wechat'];$accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";$result = $this->HttpRequest($accessTokenUrl);$data = Json::decode($result);if($data && isset($data['errcode']) === false){$model = User::find()->where(['open_id' => $data['openid']])->one();if($model === false){$model = new User();$model->username = "wx-".rand(100000,999999);$model->password = md5('123456');$model->create_time = time();$model->open_id = $data['openid'];$model->save();// 拉取用户信息$userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token={$data['access_token']}&openid={$data['openid']}&lang=zh_CN";$info = $this->HttpRequest($userInfoUrl);$userInfo = Json::decode($info);// todo $userInfo 是一个数组,具体放哪具体决定,可以一个表单独存放会员微信信息,也可以对user表进行扩展。}$session = \Yii::$app->session;$session['wx_open_id'] = $data['openid'];return $this->redirect($url);}}...
}
如上代码,你可以轻松获得用户信息。
最后要说的
上面的代码经阿北测试可以正常运行,但是如果生产环境还请对每个部分做下异常处理,在平时开发中,这种方式并不是我们推荐的,用一个开源库也许是我们最好的选择,本专题也会讲到用开源库easywechat实现微信端授权登陆的实现。
如果你是兄弟连成员,务必关注“开发知乎”这套视频课程,这是一个用easywechat完整且彻底实现微信接口开发的好案例。
另外你可能对本文的code、access_token等比较迷糊还是,无需忧虑,这些都是oauth2授权机制的一部分,我们会单独去讲解 oauth2,这个强大的通用于所有开放平台的授权机制。
而本篇,你能做出微信端授权就已成功。