记一次小需求:
一个投票活动,在原有的投票接口里(很老的项目。。)增加判断用户是否在微信内登陆,且是否关注公众号,如果用户未关注,则引导用户关注公众号。
一、需求要点
- 只对指定对部分活动有效
- 区分微信浏览器访问和外部浏览器访问
- 区分用户是否关注我们的公众号
- 关注可投,不关注引导关注
二、大体实现
- 指定部分活动有效
最简单对方式,设置一个数组,对id在数组内的逻辑活动才执行新增的逻辑,这样主流程里只插入几行代码,整个逻辑在另一个函数内实现。
...前置逻辑
$wechatAuthList = [];
if (in_array($article_id, $wechatAuthList)){
if($this->wechatAuth()){
return ['code' => 403, 'message' => '引导关注']
}
}
...后面的逻辑
- 区分微信浏览器访问和外部浏览器访问
通过user-agent判断,没什么好说了。
// 判断是否是微信浏览器
public function isWeixin()
{
if ( strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false ) {
return true;
}
return false;
}
- 区分用户是否关注我们的公众号
通过微信授权接口,获取用户信息,通过返回结果的subscribe
字段判断。
‘subscribe’ = 1 (已关注)
‘subscribe’ = 0 (未关注)
这一步我们要依次用到四个接口:
//获取code
"https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appId}&redirect_uri=$redirect&response_type=code&scope={$scope}&state=1#wechat_redirec";
//获取opendId
"https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appId}&secret={$this->secret}&code={$code}&grant_type=authorization_code";
//获取access_token(基础token)
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->secret}";
//获取用户信息
"https://api.weixin.qq.com/cgi-bin/user/info?access_token={$this->accessToken}&openid={$this->openId}&lang=zh_CN"
下文会描述具体步骤。
- 关注可投,不关注引导关注
基本是前端工作了,对后端来说就是根据不同情况返回不同结果,前端根据返回值确定是否弹出浮层显示二维码。
伪代码:
$.ajax({
type: '...',
url: '...',
data: '...',
success: function(data) {
if (data.code === 403){
$("#tab").alert();
}
}
三、详细步骤
下面来说下第3部分,如何区分用户是否关注我们的公众号
首先明确需要用到哪些:
- 上边列出的四个接口
- 公众号 appId 和 secret
- 微信公众平台设置授权回调地址和redirect_uri 一致
需要注意的:
- 别忘了到微信公众平台设置授权回调地址和redirect_uri 一致,不然会报 redirect_uri参数错误
- 重定向地址redirect_uri,如果有参数的话,别忘了urlencode(),不然微信重定向回来原本的参数会没有
- 获取code时需要在浏览器重定向,不能用ajax这种异步请求!
- scope 有两种值,不需要用户感知的用静默授权(snsapi_base):
snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
- 请求到的 code 只能使用一次
- access_token 在微信有两种,一种是授权用的,与openid同时返回,只用于授权,没有请求次数限制;另一个种是基础token,每天最多请求2000次,请求subscribe字段要用这个access_token,得到他时只获得两个数据,access_token 和 expired,注意区分
具体代码:
整体流程:
//代码精简处理了,只写写大体,细节不介绍了,比如异常处理、日志记录之类的
public function wechatAuth()
{
$wechat = new Wechat();
// 是否微信内置浏览器登陆判断
if ($wechat->isWeixin()){
// 获取openId(这一步会先获取code,再用得到的code获取openid,同时会得到)
$openId = $wechat->getOpenId();
// 获取 client 的 access_token
$accessToken = $wechat->getAccessToken();
// 获取用户信息
$info = $wechat->getUserInfo();
// 未关注返回true,即引到认证
if (isset($info->subscribe) && $info->subscribe == 0){
return true;
}
}
return false;
}
获取openid:
public function getOpenId($redirect = null, $scope = 'snsapi_base')
{
if (isset($this->openId)){
return $this->openId;
}
// 如果之前session中存入了openId,则直接返回
if (isset($_SESSION['openId'])){
$this->openId = $_SESSION['openId'];
return $_SESSION['openId'];
}
// 如果没有code,则先请求code,再从微信自动跳转回来
// 这部分注释掉了,因为这个接口是ajax请求,被微信限制,所以code获取部分后来翻到了前段js里,如果在微信内,先通过window.location.href = ''去获取code,然后请求接口时再携带code来请求
// if (!isset($_GET['code'])){
// $redirect = empty($redirect)?'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] : $redirect;
// $redirect = urlencode($redirect);
// $url="https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appId}&redirect_uri=$redirect&response_type=code&scope=$scope&state=1#wechat_redirec";
// header("Location:".$url);exit();
// }
$code = $_GET['code'];
// 获取 openid 及 授权用的access_token
$get_openid_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.$this->appId.'&secret='.$this->secret.'&code='.$code.'&grant_type=authorization_code';
$response = file_get_contents($get_openid_url);
$userInfo = json_decode($response, true);
// openid如有则存入session
if (array_key_exists('openid', $userInfo)){
$_SESSION['openId'] = $userInfo;
$this->openId = $userInfo['openid'];
}
return $this->openId;
}
获取access_token:
//这里只看原生的几句就行。因为微信授权域名只能设置两个,而有多个系统需要使用,所以做了一层代理,通过代理平台的接口获取access_token。
//而这个项目比较老,零几年的了,所以redis、memcache之类的都没有可用的,就直接写文件记录了,具体的失效处理就不写了。
public function getAccessToken($refresh = false)
{
// 微信原生方法
$get_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->secret";
$response = file_get_contents($get_token_url);
$tokenInfo = json_decode($response, true);
$accessToken = isset($tokenInfo['access_token'])? $tokenInfo['access_token'] : '';
// 平台接口获取
//$accessTokenFile = '/tmp/access_token';
// 如果需要刷新token 或 token文件不存在,则重新请求token并写入文件
//if ($refresh || !file_exists($accessTokenFile)){
// $accessTokenApi = '......';
// $res = file_get_contents($accessTokenApi);
// $accessToken = json_decode($res)->token;
// if (!is_dir('/tmp')){
// mkdir('/tmp', 777);
// }
// file_put_contents($accessTokenFile, $accessToken);
//}else{// 否则直接取文件
// $accessToken = file_get_contents($accessTokenFile);
//}
//$this->accessToken = $accessToken;
return $accessToken;
}
获取用户信息:
public function getUserInfo()
{
$get_user_info_url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=$this->accessToken&openid=$this->openId&lang=zh_CN";
$response = file_get_contents($get_user_info_url);
$info = json_decode($response);
return $info;
}
正常情况下,微信会返回下述JSON数据包:
{
"subscribe": 1,
"openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
"nickname": "Band",
"sex": 1,
"language": "zh_CN",
"city": "广州",
"province": "广东",
"country": "中国",
"headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"subscribe_time": 1382694957,
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
"remark": "",
"groupid": 0,
"tagid_list":[128,2],
"subscribe_scene": "ADD_SCENE_QR_CODE",
"qr_scene": 98765,
"qr_scene_str": ""
}
其他
通过 js 获取 code 的代码:
<script>
var oCode = '';
function isWeixin() { //判断是否是微信
var ua = navigator.userAgent.toLowerCase();
return ua.match(/MicroMessenger/i) == "micromessenger";
}
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
function getWxCode() {
var appId = 'wx133b*****3db0a';
var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + location.href.split('#')[0] + "&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect";
var code = getUrlParam("code");
if (!code) {
window.location = url;
} else {
return code;
}
}
// 如果是微信内登陆则重定向获取code
if (isWeixin()){
console.log('is weixin!');
oCode = getWxCode();
console.log(oCode);
}
</script>