微信扫码 - 关注公众号后网站自动注册并登录的实现

微信扫码 - 关注公众号后网站自动注册并登录的实现

需求描述
在自己网站上点击微信登录,网站自己弹出一个二维码、扫描二维码后弹出公众号的关注界面、只要一关注公众号网站自动登录、第二次扫描登录的时候网站直接登录。
大家可以体验一下 「随便找的一个网站」

前期准备
一个公众号(必须认证,配置服务器)
微信开发文档

实现原理
公众平台提供了生成带参二维码的接口。使用该接口可以生成带不同场景值的二维码,用户扫描后,公众号可以接收到扫码/关注事件推送,在细分如下:

  • 扫描二维码,如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值(自定义值)关注事件推送给开发者。
  • 扫描二维码,如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值(自定义值)扫码事件推送给开发者。

设计如下流程:

  1. 生成二维码的时候你自定义一个参数到二维码中,顺便把这个参数传到前端页面中。
  2. 用户扫码关注后微信服务器发送一个关注事件或扫码事件消息到自己服务端,消息参数中包括了自定义参数和扫码用户openid等参数。
  3. 根据openid用微信公众号接口去获取用户信息,拿到用户信息之后就是实现注册逻辑,用自定义参数标记作为缓存key标记可以登录。
  4. 前端轮询查询定义参数为key的缓存是否标记可登录时,就开始实现登录逻辑,重载页面,流程完毕。

实战
1.微信公众号部署服务器和Token认证
由于我们自己服务,需要接管微信服务器的推送,所以需要在微信公众号后台配置服务器通知地址,公众号测试号的配置大同小异这里不做累述,如图所示:
在这里插入图片描述

注:这个配置启用后,微信服务器会把相关的事件推送都转发到用户服务器上,同时微信后台的自定义菜单和回复也失效,需要用户在自己服务器上通过微信接口来接管

Token验证示例代码:

<?php
// 微信token认证
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$echostr = $_GET["echostr"];
// 你在微信公众号后台的设置的Token
$token = "";
 
// 1)将token、timestamp、nonce三个参数进行字典序排序
$tmpArr = array($nonce,$token,$timestamp);
sort($tmpArr,SORT_STRING);
 
// 2)将三个参数字符串拼接成一个字符串进行sha1加密
$str = implode($tmpArr);
$sign = sha1($str);
 
// 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if ($sign == $signature) {
	echo $echostr;
}

2.服务端生成带参二维码
先了解下,公众号的带参二维码生成 接口文档 ,我们选择临时二维码,步骤如下:

第一步:通过AppID和AppSecret获取access_token
这里的access_token和上边的token不是一回事,不要混淆。获取access_token接口文档 ,这一步建议抽离封装,以便其他接口复用。

第二步:通过access_token创建二维码ticket
每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id),分别介绍临时二维码和永久二维码的创建二维码ticket过程。

第三步:通过ticket换取二维码
这一步其实就是拼接成一个二维码图片地址:https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET 提醒:TICKET记得进行UrlEncode,然后把这个二维码图片地址,返回给前端直接显示

示例代码:

    /**
     * 获取微信场景二维码
     * @param $sceneValue 场景值
     * @param int $type 1:临时二维码  2:永久二维码
     * @return string
     */
    public static function getWeChatUrl($sceneValue,$type = 1){
        $accessToken = WxToken::getToken();
        $url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token='.$accessToken;
        $actionName = 'QR_STR_SCENE';
        if($type==2){
            $actionName = 'QR_LIMIT_STR_SCENE';
        }
 
        $res = self::post($url,json_encode([
            'expire_seconds'=>604800,
            'action_name'=>$actionName,
            'action_info'=>[
                'scene'=>[
                    'scene_id'=>$sceneValue,
                    'scene_str'=>$sceneValue
                ]
            ]
        ]));
        if(isset($res['errcode']) && $res['errcode']){
            echo $res['errmsg'];die();
        }
        return 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='.$res['ticket'];
    }

3.前端请求二维码,轮询状态(也可使用websocket)

    // 方便清除轮询
    var timer = null;
    var flag = '';
 
    $(document).on('click', '.wechat-login', function () {
        // 请求登录二维码
        $.get('/admin/index/weChatQrCode', function (data, status) {
            $('.wechat-url').attr('src', data.data.qr_code_url);
 
            // 显示二维码
            flag = data.data.scene_value;
 
            // 轮询登录状态
            timer = setInterval(function () {
                // 对话框关闭时,清除轮询
                if ($('#wx-login-pan').attr('class').indexOf('open') == -1) {
                    clearInterval(timer);
                    return;
                }
                // 请求参数是二维码中的场景值
 
                $.get('/admin/index/loginCheck?wechat_flag='+flag, function (data, status) {
                    if(data.code==0){
                       window.location.href = '/home/serve/apply';
                    }
                });
            }, 2000);
        });
    });
 
    // 返回时清除轮询
    $('.wechat-back').click(function () {
        clearInterval(timer)
    });
 
    function closeLoginPan() {
        clearInterval(timer);
    }

4.后端扫码事件接受处理

// 微信事件接收类
class WxPush
{
    /**
     * 获得请求时POST:XML字符串
     * 不能用$_POST获取,因为没有key
     */
    public static function getResponse()
    {
        $xmlStr = $GLOBALS['HTTP_RAW_POST_DATA'];
        Log::record($xmlStr, 'wx_push');
        if (empty($xmlStr)) {
            return false;
        }
        // 解析该xml字符串,利用simpleXML
        libxml_disable_entity_loader(true);
        //禁止xml实体解析,防止xml注入
        $xml = simplexml_load_string($xmlStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        $data = json_decode(json_encode($xml),true);
        //判断该消息的类型,通过元素MsgType
        Log::write($data, 'wx_push');
        switch ($data['MsgType']) {
            case 'event': // 事件处理
                self::handleEvent($data);
                break;
            case 'text'://文本消息
 
                break;
            case 'image'://图片消息
 
                break;
            case 'voice'://语音消息
 
                break;
            case 'video'://视频消息
 
                break;
            case 'shortvideo'://短视频消息
 
                break;
            case 'location'://位置消息
 
                break;
            case 'link'://链接消息
 
                break;
        }
    }
 
    /**
     * 事件引导处理方法(事件有许多,拆分处理)
     *
     * @param $data
     * @return mixed
     * @internal param $request
     * @internal param $event
     */
    static function handleEvent($data)
    {
        $method = strtolower($data['Event']);
        $event = new Event();
        if (method_exists($event, $method)) {
            return call_user_func_array([$event, $method], [$data]);
        }
    }
 
}
// 事件类
class Event
{
    /**
     * 扫描带参二维码事件
     */
    public function scan($data)
    {
        // 标记前端可登陆
        Admin::markLogin($data['EventKey'],$data['FromUserName']);
    }
 
    /**
     * 关注订阅
     */
    public function subscribe($data)
    {
        // 关注事件的场景值会带一个前缀需要去掉
        $data['EventKey'] = str_replace('qrscene_','',$data['EventKey']);
 
        // 标记前端可登陆
        Admin::markLogin($data['EventKey'],$data['FromUserName']);
    }
 
    /**
     * 取消订阅
     */
    public function unsubscribe($data)
    {
        $adminName = $data['FromUserName'];
        $admin = Admin::get(['wx_openid'=>$data['FromUserName']]);
        if($admin && $admin['name']){
            $adminName = $admin['name'];
        }
    }
 
    /**
     *  菜单点击事件
     */
    public function click(){
 
    }
 
    /**
     * 连接跳转事件
     */
    public function view(){
 
    }
}
    /**
     * 标记可登录
     */
    public static function markLogin($key,$val)
    {
        $admin = self::get(['wx_openid'=>$val]);
        $time = time();
        if(empty($admin)){
            $wxUser = WxUser::getCgiUserInfo($val);
            $admin = self::create([
                'wx_openid'=>$val,
                'name'=>Emoji::encode($wxUser['nickname']),
                'head_img_url'=>$wxUser['headimgurl'],
                'sex'=>$wxUser['sex'],
                'province'=>$wxUser['province'],
                'city'=>$wxUser['city'],
                'type'=>3,// 客户
                'create_time'=>$time,
                'update_time'=>$time
            ]);
        }
        // 写入缓存
        Cache::set($key, $val,60*60);
    }

5.登录检查
在前端轮询请求检查登录状态接口时,把自定义参数传给后端,后端以该自定义参数为key去缓存查询可登录状态,查到可以登录了则去授权登录,返回登录成功。

    /**
     * 微信用户登录检查
     *
     */
    public function loginCheck()
    {
        // 判断请求是否有微信登录标识
        if (!$flag = $this->request->get('wechat_flag')) {
            $this->apiError(-1,'标识不能为空');
        }
 
        // 根据微信标识在缓存中获取需要登录用户的 UID
        $uid  = Cache::get($flag);
        if(empty($uid)){
            $this->apiError(-1,'void');
        }
        $admin = Admin::get(['wx_openid'=>$uid]);
        if(empty($admin)){
            $this->apiError(-1,'用户未注册');
        }
        Admin::doLogin($admin);
        $this->apiSuccess($flag);
 
    }
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程概述: 本课程是一个小型的vue周边技术+以php为基础的+微信接口开发的小型项目。本项目以微信扫码关注公众号实现网站自动登陆这一功能为载体,将会讲解如下主要核心知识点:前端你将学习到: 一、vue-cli4进行前端项目的创建 二、如何使用vuex进行登陆信息的管理与同步 三、如何使用axios进行接口请求的封装与拦截 四、在脚手架里使用Element-UI 五、学习组件化编程的思想 六、如何解决接口前后端分离引起的跨域问题以及在跨域下使用cookie凭证进行会话维护 后端你讲学习到: 一、如何申请微信公众号测试账号,如何进行相关参数的配置,如何进行微信相关接口的开发 二、如何使用微信接口开发,如获取临时二维码,获取用户基础信息,监听公众号关注以及扫描事件 三、如何使用redis对数据进行缓存 四、如何使用php原生代码进行接口的开发 五、如何使用laralvel 7.x 框架进行接口的开发 六、学习到laravel 中核心概念的使用方法,如什么是依赖注入,如何使用服务容器解决依赖注入、服务提供者、中间件的使用,如何处理请求数据,如何进行用户认证,以及如何使用Eloquent ORM进行数据库操作 七、开发过程中遇到的问题,如何进行排查 八、git远端仓库的建立与配置,如何在配置多仓库下的公钥,服务器端部署公钥的配置,如何进行代码部署,本地与服务器代码的开发实时同步 九、如何使用composer帮助我们进行第三方依赖包的安装 本课程的设计思路随笔: 从业务层面上来讲,扫码关注公众号实现网站自动登陆是一个非常实用的功能,可以为微信公众号引流。 技术层面上来说,使用前后端分离进行制作,可以将前端以及后端的知识都涵盖到。对于前端的路由,信息维护,脚手架的搭建,ui组件的使用,接口的请求与封装都能够讲解到。 对于后端,本课程对php原生代码以及工作中使用频率比较高的同时也很优雅的laravel框架都会进行讲解,分别予以代码的实现。让同学们能够看到原生开发与框架开发的区别,原生开发使得基础比较弱的同学能够慢慢上手,也知道此功能实现的核心要点,在进行原生代码开发后,再进行框架开发,会有个循序渐进的过程,同时在框架里面我们会降到主流框架都会用到的一些核心思想,比如依赖注入,服务容器,中间件等等,同时对于想学习laravel框架的同学,学习过这个案例后,再去看文档就知道该如何去看,如何去学了。 对于整个代码的管理与部署,我们也会引入git仓库进行项目代码管理,如何在服务器进行网站环境的搭建与代码部署等等实用技巧。 相信本课程会给大家带来十足的收获,大家加油。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值