实现用户关注公众号登录,流程如下:
重点在于如何将唯一标识和用户微信唯一openid联系起来。
$router->get('wechat/server', 'Admin\WeChatController@check');//微信服务器验证
$router->post('wechat/server', 'Admin\WeChatController@receiver');
- 创建带参数的临时二维码
官方文档地址: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
easywechat文档地址:
https://www.easywechat.com/docs/4.1/basic-services/qrcode
use EasyWeChat\Factory as Wechat;
public function qrcode()
{
$cache_key = 'wx_login_' . time();
$wechat_app = $this->getWechatApp();
$result = $wechat_app->qrcode->temporary($cache_key, 7 * 24 * 3600);
$url = $wechat_app->qrcode->url($result['ticket']);
return $this->response->array(['data' => [
'url' => $url,
'appid' => $this->getConfig()['app_id'],
'cache_key' => $cache_key,
]]);
}
private function getWechatApp()
{
if ($this->wechat_app) {
return $this->wechat_app;
}
$app = Wechat::officialAccount($this->getConfig());
$this->wechat_app = $app;
return $this->wechat_app;
}
返回前端一个二维码链接,一个appid,一个最重要的二维码参数cache_key,也就是用户的唯一标识
-
监听微信关注/取关事件消息
前端将二维码展示后,用户会扫描二维码,点击关注后,微信会推送消息到你服务器
① 监听
官方文档地址: https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
easywechat文档地址:
https://www.easywechat.com/docs/4.1/official-account/server
/**
* 微信回调监听
*
* @return string
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
*/
public function receiver()
{
$message = $this->client->server->getMessage();
if(empty($message)){
return '';
}
switch ($message['MsgType']) {
case 'event':
$func = strtolower($message['Event']);
return $this->$func($message);
break;
case 'text':
return '收到文字消息';
break;
case 'image':
return '收到图片消息';
break;
case 'voice':
return '收到语音消息';
break;
case 'video':
return '收到视频消息';
break;
case 'location':
return '收到坐标消息';
break;
case 'link':
return '收到链接消息';
break;
case 'file':
return '收到文件消息';
default:
return '收到其它消息';
break;
}
}
$message就是微信post请求发送的xml信息转换后的array,xml大体结构如下:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
我们这里暂时只处理事件消息event
case 'event':
$func = strtolower($message['Event']);
return $this->$func($message);
break;
关注事件
①获取到openid,拿到用户信息
②数据库对比用户关注状态(可以用字段区分,我这边直接软删除的),操作用户
③将二维码唯一标识和用户唯一openid对应,存入cache,时间自己定(时间为轮询时长)
private function subscribe($message)
{
$open_id = $message['FromUserName'];
$user = $this->getWechatApp->user->get($open_id);
$wx_user = WxUser::query()->withTrashed()->where('open_id', $user['openid'])->first();
if($wx_user && $wx_user->deleted_at){
$wx_user->restore();
}else{
WxUser::create(
[
'open_id' => $user['openid'],
'app_id' => $this->config['app_id'],
'type' => 2,//1小程序 2公众号
'union_id' => $user['unionid'] ?? '',
'avatar' => $user['headimgurl'] ?? '',
'nickname' => $user['nickname'] ?? '',
'language' => $user['language'] ?? '',
'city' => $user['city'] ?? '',
'province' => $user['province'] ?? '',
'country' => $user['country'] ?? '',
'gender' => $user['sex'] ?? '',
]
);
}
$event_key = $message['EventKey'];
$key = substr($event_key, stripos($event_key, '_') + 1);
Cache::put($key, $open_id, 4);
return '';
}
已关注事件
用户登录token过期,但是仍然关注了公众号,扫码就进入了该事件
private function scan($message)
{
$event_key = $message['EventKey'];
$open_id = $message['FromUserName'];
if(!Cache::get($event_key)){
Cache::put($event_key, $open_id, 4);
}
return '';
}
取关事件
你可以改变数据库数据状态,我这边直接软删除
private function unsubscribe($message)
{
$open_id = $message['FromUserName'];
WxUser::query()->where('open_id', $open_id)->delete();
return '';
}
②前端轮询
根据cache判断用户是否已关注,所以需要前端拿cache_key(也就是我们一开始二维码的参数)来轮询。
服务器查询cache,查询数据库用户是否关注,关注表示登录成功
public function login(Request $request)
{
$cache_key = $request->input('cache_key');
$open_id = Cache::get($cache_key, '');
if (!$open_id) {
return $this->response->errorUnauthorized();
}
$wx_user = WxUser::query()->where('open_id', $open_id)->first();
if (!$wx_user) {
return $this->response->errorUnauthorized();
}
$token = auth('wx')->login($wx_user);
Cache::forget($cache_key);
return $this->response->array([
'access_token' => (string)$token,
'user' => array_only($wx_user->toArray(), [
'gender', 'nickname', 'avatar', 'country', 'province', 'city', 'language',
]),
]);
}