个人博客添加微信登录

个人博客添加微信登录

个人小程序实现扫码登录,30元/年搞定


开发背景

自己博客很早就接入了QQ登录、新浪博客登录、GitHub登录,唯独缺少微信登录。以前实现微信登录需要企业认证(300元/年),最近研究小程序时发现:个人认证的小程序也可以实现扫码登录,只需30元/年,值得研究!

博客不希望游客随意评论,但也要方便用户登录,提升体验——微信扫码是目前最方便的方式。


三种微信登录方案对比

方案一:微信开放平台扫码登录    企业专属

PC网页端扫码,官方体验最佳,需企业认证

✅ 官方支持,体验好

✅ 用户信任度高

✅ 可获取昵称、头像、性别

❌ 需企业认证(300元/年)

❌ 需要域名备案

❌ 审核较严格

方案二:微信公众号扫码登录    不推荐

同样需要企业认证,300元/年,个人开发者不划算。

方案三:微信小程序扫码登录    ✅ 推荐个人

个人认证即可,30元/年,实现简单,扫码即登录

✅ 个人即可使用

✅ 费用低(30元/年)

✅ 实现简单

✅ 扫码即登录

❌ 小程序

❌ 部分接口需认证小程序

❌ 仅能获取 openid

说明

小程序方案的缺点是不支持获取昵称、头像、性别等详细信息,但对于博客评论登录场景来说,openid 已经完全够用。


整体流程设计

整个扫码登录涉及三端协作:Web前端、后端API、微信小程序,流程如下:

┌─────────┐      ┌─────────┐      ┌─────────┐
│  Web端  │      │ 后端API │      │  小程序  │
│         │      │         │      │         │
│ 生成scene├─────>│ 创建记录│      │         │
│         │      │         │      │         │
│ 显示二维码│<─────│ 返回scene│      │         │
│         │      │         │      │         │
│ 轮询状态 ├─────>│ 查询状态│      │         │
│         │      │         │<─────│  扫码   │
│         │      │         │─────>│ 登录处理 │
│         │      │ 更新状态 │<─────│ 返回成功 │
│ 登录成功 │<─────│ 返回用户│      │         │
└─────────┘      └─────────┘      └─────────┘

开发前置条件

微信小程序账号(mp.weixin.qq.com),个人认证即可

已备案域名

小程序完成备案


开发流程

一、Web 端

1  获取小程序码,直接显示在前端页面(不保存到服务器)

小风博客


单独做一个登录界面,显示登录二维码

小风博客

前端的主要代码

            <div class="container">
                    <div class="card border-0 shadow-sm mb-3">
                    <div class="card-body">
                                            <div class="login-body">
                        <div data-bs-target="wechat">
                            <h4 class="text-center">微信登录</h4>
                            <div class="qr-image" data-bs-qrcode="">
                                <img style="width: 100%;" src="{$image}">
                            </div>
                           <!--  <h5>请使用微信,扫码登录</h5> -->
                            <h5><p class="status-text waiting" id="statusText">请使用微信扫描二维码</p></h5><br>
                                            <div id="refreshBtn" style="text-align: center;display: none;"><button class="refresh-btn"  onclick="location.reload()">
                                            刷新二维码
                                            </button></div>
                        </div>
                        
                        <h6>其他登录方式</h6>
                        <div class="social-links">
                           <!--  <a data-bs-selector="wechat" href="#wechat" hidden="hidden">
                                <i class="iconfont icon-weixin "></i>
                            </a> -->
                            <a data-bs-selector="mobile" title='QQ登录' href="https://hotxf.com/Home/User/oauth_login/type/qq">
                                <i class="iconfont icon-mobile "></i>
                            </a>
                            <a  title='Sina登录' href="https://hotxf.com/Home/User/oauth_login/type/sina">
                                <i class="iconfont icon-qq "></i>
                            </a>
                            <a title='GitHub登录' href="https://hotxf.com/Home/User/oauth_login/type/github">
                                <i class="iconfont icon-github "></i>
                            </a>
                        </div>
                    </div>
                        </div>
    </div>
            </div>

2  实现轮询机制,定时查询扫码状态

轮询代码

       <script type="text/javascript">
     // 配置
        const API_BASE = 'https://hotxf.com/api/wx/';  // 改成你的域名
        
        let scene = '{$token}';
        let pollTimer = null;
        let pollCount = 0;
    // 开始轮询
    startPoll();
 // 轮询检查状态
        function startPoll() {
            pollTimer = setInterval(async () => {
                pollCount++;
                
                // 5分钟后自动停止
                if (pollCount > 150) {
                    stopPoll();
                    updateStatus('error', '二维码已过期,请刷新重试');
                    document.getElementById('refreshBtn').classList.add('show');
                    return;
                }
                
                try {
                    const response = await fetch(API_BASE + 'check?token=' + scene);
                    const result = await response.json();
                    
                    if (result.code === 0) {
                        const status = result.status;
                        
                        switch (status) {
                            case 0:
                                updateStatus('waiting', '等待扫码...');
                                console.log('轮询错误:0');
                                break;
                            case 1:
                                console.log('轮询错误:1');
                                updateStatus('scanned', '已扫码,请在手机确认');
                                break;
                            case 2:
                                console.log('轮询错误:2');
                                updateStatus('success', '登录成功!正在跳转...');
                                document.location = '/';
                                
                                // 授权成功
                                stopPoll();
                              
                                break;
                            case 3:
                                console.log('轮询错误:3');
                                stopPoll();
                                updateStatus('error', '二维码已过期');
                                document.getElementById('refreshBtn').classList.add('show');
                                break;
                        }
                    } else {
                        stopPoll();
                        updateStatus('error', result.msg || '查询失败');
                    }
                } catch (error) {
                    console.error('轮询错误:', error);
                }
            }, 2000);
        }
        
        // 停止轮询
        function stopPoll() {
            if (pollTimer) {
                clearInterval(pollTimer);
                pollTimer = null;
            }
        }
        // 更新状态
        function updateStatus(status, text) {
            const statusText = document.getElementById('statusText');
            statusText.className = 'status-text ' + status;
            statusText.textContent = text;
        }
        

        // 页面离开时停止轮询
        window.addEventListener('beforeunload', stopPoll);
</script>



3  扫码成功后自动完成登录跳转

小风博客

二、后端 API

1  创建生成二维码记录,返回 scene 和二维码信息

后端生成二维码信息(由于我有两个后端,博客后端用的Thinkphp3.2一直没有升级,小程序用的Thinkphp8,代码写法不一样,仅供参考)

//微信小程序登录二维码
    public function generate(){
        
        $scene = I('scene/s', 's'. substr(md5(uniqid(mt_rand(), true)), 0, 16));
        $page = I('page/s', 'pages/scan-login/index');
         // 从缓存获取(快速)
        $cacheScan = S('scan');
        // 检查是否过期
        if ($cacheScan['status']===0) {
            return json_encode(['image'=>$cacheScan['image'],'scene'=>$cacheScan['scene']],JSON_UNESCAPED_SLASHES);
        }
        // 获取 AccessToken
        $accessToken = $this->access_token;
        
        if (!$accessToken) {
            echo json_encode(['code' => 500, 'msg' => '获取token失败'], JSON_UNESCAPED_SLASHES);exit;
        }
        
        // 调用微信接口生成小程序码
        $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
        
        $postData = [
            'scene' => $scene,           // 场景值,最大32个可见字符
            'page' => $page,              // 页面路径
            'env_version' => 'release',   // release-正式版 trial-体验版 develop-开发版
            'width' => 430,               // 二维码宽度
            'auto_color' => false,        // 自动配置线条颜色
            'line_color' => [             // 线条颜色
                'r' => 0,
                'g' => 0,
                'b' => 0
            ],
            'is_hyaline' => false,       // 是否透明
        ];
        
        $result = $this->httpPost($url, json_encode($postData));
        
        if (isset($result['errcode']) && $result['errcode'] != 0) {
            echo json_encode(['code' => 500, 'msg' => $result['errmsg']], JSON_UNESCAPED_SLASHES);exit;
        }
        //创建扫码记录,写入到数据库
        $db = M('wx_scan_login', 'xf_', 'DB_CONFIG_XF');
        $expireTime = date('Y-m-d H:i:s', time() + 300);//有效时间5分钟        
        $data = [
            'scene_str' => $scene,
            'status' => 0,// 未扫码
            'expires_time' => $expireTime,
            'create_time' => time(),
            'update_time' => time(),
        ];
        $a = $db->add($data);
        if($a>0){
            // 1. 缓存记录(用于快速查询)
            S('scan_' . $scene, array(
                'status' => 0,
                'openid' => '',
                'user_id' => 0,
            ), 300);
            // 2. 缓存二维码(用于快速查询)
            S('scan', array(
                'scene' => $scene,
                'image' => 'data:image/png;base64,' . base64_encode($result),
                'status' => 0,
            ), 300);
        }
        return json_encode(['image'=>'data:image/png;base64,' . base64_encode($result),'scene'=>$scene],JSON_UNESCAPED_SLASHES);
       
        
    }


2  提供状态查询接口,供前端轮询

前端轮询代码

    /**
     * 查询扫码状态(前端轮询)
     * GET /api/wx/check
     */
    public function check()
    {
        $scene = I('token/s', '');
        
        if (empty($scene)) {
            echo json_encode(['code' => 400, 'msg' => '缺少参数'], JSON_UNESCAPED_SLASHES);exit;
        }
         // 从缓存获取(快速)
        $cacheData = S('scan_' . $scene);
        
        if ($cacheData['status']===0 ) {
            echo json_encode(['code'=>0,'msg'=>'success','status'=>$cacheData['status'],'user_id'=>$cacheData['user_id'] ?? 0], JSON_UNESCAPED_SLASHES);exit;
        }               
        // 从数据库获取
        $record = M('wx_scan_login', 'xf_', 'DB_CONFIG_XF')->where(array('scene_str'=>$scene))->find();
        
        if (!$record) {
            echo json_encode(['code' => 404, 'msg' => '二维码不存在或已过期'], JSON_UNESCAPED_SLASHES);exit;
           
        }        
        // 检查是否过期
        if ($record['expires_time'] < date('Y-m-d H:i:s')) {
            $record->status = 3;//3已过期
            M('wx_scan_login', 'xf_', 'DB_CONFIG_XF')->where(array('scene_str'=>$scene))->save(array('status'=>3));
            echo json_encode(['code' => 404, 'msg' => '二维码不存在或已过期', 'status' => intval(3)], JSON_UNESCAPED_SLASHES);exit;
        }  
        if($record['status']==2){
            // 查找或创建用户
            $data = M('OauthUser')->where(array('openid'=>$record['openid']))->find();
            // 组合存session的数据
            $login_info=array(
                'id'=>$data['id'],
                'head_img'=>$data['head_img'],
                'nickname'=>$data['nickname'],
                );            
            if($data['id']>0){
                session('user',$login_info);
                //写入cookie可保持1年登录状态,但是要把ID加密下,+777,等到用的时候-777
                $login_info['id']=$id+777;
                cookie('sjd',$login_info,60*60*24*365);
            }    
            
            // 跳转到登录前的页面
            $this_url=empty($_COOKIE['this_url']) ? '/' : cookie('this_url');
        }
        //登录成功处理       
        echo json_encode(['code' => 0, 'msg' => 'success', 'status' => intval($record['status']),'url'=>$this_url], JSON_UNESCAPED_SLASHES);exit;
      
    }



3  接收小程序回调,更新登录状态并返回用户信息

小程序扫码后,只需要回调服务器接口,告诉服务器openid和scene就可以了,进行登录成功操作

三、小程序端

1  添加扫码状态显示页面

生成二维码时要写扫码状态显示页面的路径:

pages/scan-login/index

2  用户扫码后展示确认界面

扫码成功,WEB端网站就自动登录成功了

3  回调后端 API,通知服务器登录成功

回调服务器接口,告诉服务器openid和scene就可以了


如有问题欢迎留言交流

打 赏

小风博客
请点评论按钮,登录后发表评论
  • 最新评论
  • 总共0条评论