微信公众号开发—扫描二维码实现登录方案

news2025/1/25 8:59:29
😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 微信公众号开发—扫描二维码实现登录方案
⏱️ @ 创作时间: 2022年12月21日

目录

  • 1、准备工作
  • 2、二维码扫码登录说明
  • 3、yaml配置
  • 4、定义工具类
  • 5、扫描带参数二维码事件
  • 6、扫码登录流程示例代码

1、准备工作

1、调用微信公众号接口,需要实现获取AccessToken,参考《获取AccessToken接口调用凭据》
2、在本地进行联调时,为让微信端能够访问到本地服务,需要进行内网穿透,参考《本地服务器内网穿透实现(NATAPP)》
3、配置微信接口配置信息,用于告诉微信接收消息的回调地址
在这里插入图片描述

2、二维码扫码登录说明

  • 前端调用接口,后端生成二维码以及一个loginId参数(就是一个微信扫码场景值 sceneStr,loginId存入redis中,时效为5分钟);
  • 同时前端通过loginId进行轮询访问判断是否成功登录
    • 如果扫码后,用户没有进行任何绑定,则直接回复用户消息并且附带上进行绑定的引导链接(绑定操作就是一个前端页面,进入页面后用户需要同意微信网页授权、并且输入手机号、短信验证码,后端将授权后得到的code查询到openId,最后将手机号与openId进行绑定),绑定操作参考《微信网页授权自动登录业务系统》;
    • 如果扫码后,loginId已过期则要求用户重新刷新二维码,并且只要是授权不成功都需要重新刷新二维码再次执行授权流程;
    • 如果扫码后 发现以及已经绑定了openid则授权登录成功,将用户信息及token信息通过轮询接口返回到前端,并且删除loginId值;

3、yaml配置

wx:
  # 来源于测试平台
  appid: wx79ec4331f29311b9
  secret: 1c79a199560f94096f26b8caa2a73a08
  apiUrl: https://api.weixin.qq.com/
  openApiUrl: https://open.weixin.qq.com/
  authRedirectUri: http://6uks3d.natappfree.cc/wechat/auth

4、定义工具类

MapUtils:

public class MapUtils {

    /**
     * Map转换为 Entity
     *
     * @param params 包含参数的Map
     * @param t      需要赋值的实体
     * @param <T>    类型
     */
    public static <T> T mapToEntity(Map<String, Object> params, T t) {
        if (null == params) {
            return t;
        }
        Class<?> clazz = t.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        try {
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                String name = declaredField.getName();
                if (null != params.get(name)) {
                    declaredField.set(t, params.get(name));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("属性设置失败!");
        }
        return t;
    }

    /**
     * 将对象转换为HashMap
     *
     * @param t   转换为Map的对象
     * @param <T> 转换为Map的类
     * @return Map
     */
    public static <T> Map<String, Object> entityToMap(T t) {
        Class<?> clazz = t.getClass();
        List<Field> allField = getAllField(clazz);
        Map<String, Object> hashMap = new LinkedHashMap<>(allField.size());
        try {
            for (Field declaredField : allField) {
                declaredField.setAccessible(true);
                Object o = declaredField.get(t);
                if (null != o) {
                    hashMap.put(declaredField.getName(), o);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("属性获取失败!");
        }
        return hashMap;
    }

    /**
     * 获取所有属性
     *
     * @param clazz class
     * @param <T>   泛型
     * @return List<Field>
     */
    public static <T> List<Field> getAllField(Class<T> clazz) {
        List<Field> fields = new ArrayList<>();
        Class<?> superClazz = clazz;
        while (null != superClazz) {
            fields.addAll(Arrays.asList(superClazz.getDeclaredFields()));
            superClazz = superClazz.getSuperclass();
        }
        return fields;
    }

    /**
     * 将Map参数转换为字符串
     *
     * @param map
     * @return
     */
    public static String mapToString(Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        map.forEach((key, value) -> {
            sb.append(key).append("=").append(value.toString()).append("&");
        });
        String str = sb.toString();
        str = str.substring(0, str.length() - 1);
        return str;
    }

    /**
     * 将Bean对象转换Url请求的字符串
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> String getUrlByBean(T t) {
        String pre = "?";
        Map<String, Object> map = entityToMap(t);
        return pre + mapToString(map);
    }

}

5、扫描带参数二维码事件

@Service
@Slf4j
public class MessageService {
    @Resource
    private ScanAuthService scanAuthService;
    
    public String receiveAndResponseMessage(HttpServletRequest request) {
    	log.info("------------微信消息开始处理-------------");
        try {
            // 调用消息工具类MessageUtil解析微信发来的xml格式的消息,解析的结果放在HashMap里;
            Map<String, String> map = WeixinMessageUtil.xmlToMap(request);
            String jsonString = JSON.toJSONString(map);

            // 发送方账号(用户方)
            String fromUserName = map.get("FromUserName");
            // 接受方账号(公众号)	
            String toUserName = map.get("ToUserName");
            // 消息类型
            String msgType = map.get("MsgType");
            log.info("fromUserName is:" + fromUserName + " toUserName is:" + toUserName + " msgType is:" + msgType);
			// 事件类型
            String eventType = map.get("Event");
			if (eventType.equals(MessageType.EVENT_TYPE_SCAN)) {
                 BaseEvent message = JSON.parseObject(jsonString, BaseEvent.class);
                 log.info("扫码成功 {}", message);

                 // 事件 KEY 值,qrscene_为前缀,后面为二维码的参数值
                 String eventKey = map.get("EventKey");
                 String openId = message.getFromUserName();
                 // 进行后续的授权处理
                 if (!StringUtils.isEmpty(eventKey)) {
                    scanAuthService.getAuth(openId, eventKey);
                 }
             }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("系统出错");
            return null;
        }
    }
}

6、扫码登录流程示例代码

扫码请求实体类定义:

@Data
public class QrCodeRep {
    /**
     * 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为60秒
     */
    private Integer expire_seconds;

    /**
     * 二维码类型
     * QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值
     * QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
     */
    private String action_name;

    /**
     * 二维码详细信息
     */
    private ActionInfo action_info;

    @Data
    public static class ActionInfo {
        private Scene scene;
    }

    @Data
    public static class Scene {
        /**
         * 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
         */
        private String scene_str;
    }
}

扫码请求响应类定义:

@Data
public class QrCodeRes {
    /**
     * 获取的二维码ticket,凭借此 ticket 可以在有效时间内换取二维码。
     */
    private String ticket;

    /**
     * 通过ticket换取的二维码
     */
    private String ticketUrl;

    /**
     * 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)
     */
    private Integer expire_seconds;

    /**
     * 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片
     */
    private String url;

    /**
     * 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
     */
    private String sceneStr;
}

Service层:

@Slf4j
@Service
public class ScanAuthService {

    @Resource
    private WeChantService weChantService;

    @Resource
    private RestHttpRequest restHttpRequest;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private WxBean wxBean;

    public String getAuth(String openId, String loginId) {
        String msg = "扫码登录成功!";

        // 如果扫码后,loginId已过期则要求用户重新刷新二维码,并且只要授权不成功都需要重新刷新二维码再次执行授权流程
        if (!RedisUtils.hasKey(loginId)) {
            msg = "扫码登录已过期,请重新刷新二维码!";
        }

        Object o = RedisUtils.get(loginId);
        if (o == null) {
            msg = "扫码登录已过期,请重新刷新二维码!";
        } else {
            ScanStatusAuthRes authRes = JSON.parseObject(o.toString(), ScanStatusAuthRes.class);

            UserInfo userInfo = userInfoMapper.selectByOpenId(openId);
            if (userInfo == null) {
                // 如果扫码后,如果没有进行任何绑定,则直接回复用户消息并且附带上进行绑定的链接
                msg = "<a href =\"https://www.baidu.com/\">请前往绑定</a>";
                RedisUtils.del(loginId);
            }

            // 如果扫码后 发现以及已经绑定了openid则授权登录成功,将用户信息及token信息通过轮询接口返回到前端,并且删除loginId值
            authRes.setStatus(2);
            authRes.setUserInfo(userInfo);
            long expire = RedisUtils.getExpire(loginId);
            RedisUtils.setEx(loginId, JSON.toJSONString(authRes), expire, TimeUnit.SECONDS);
        }
        return msg;
    }

    public PageResult qrcodeCreate() {
        String url = wxBean.getApiUrl() + InterfaceConstant.QR_CODE_CREATE;
        BaseRep rep = new BaseRep();
        rep.setAccess_token(weChantService.getAccessToken());
        url = url + MapUtils.getUrlByBean(rep);

        QrCodeRep codeRep = new QrCodeRep();
        codeRep.setAction_name("QR_STR_SCENE");
        codeRep.setExpire_seconds(5 * 60);

        QrCodeRep.ActionInfo actionInfo = new QrCodeRep.ActionInfo();
        QrCodeRep.Scene scene = new QrCodeRep.Scene();
        // 设置场景值
        String loginId = UUIDUtils.getUuId();
        scene.setScene_str(loginId);
        actionInfo.setScene(scene);
        codeRep.setAction_info(actionInfo);
        Map map = restHttpRequest.doHttp(url, HttpMethod.POST, codeRep);

        QrCodeRes res = new QrCodeRes();
        MapUtils.mapToEntity(map, res);
        res.setSceneStr(scene.getScene_str());
        try {
            // 通过ticket获取二维码
            String ticket = res.getTicket();
            String ticketUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket, "UTF-8");
            res.setTicketUrl(ticketUrl);
        } catch (
                UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        // 将状态写入redis
        ScanStatusAuthRes authRes = new ScanStatusAuthRes();
        authRes.setStatus(1);
        RedisUtils.setEx(loginId, JSON.toJSONString(authRes), res.getExpire_seconds(), TimeUnit.SECONDS);
        return ResultUtils.success(res);
    }

    public PageResult scanStatus(String loginId) {
        // 通过redis查询是否存在
        if (!RedisUtils.hasKey(loginId)) {
            return ResultUtils.fail("扫码登录已过期,请重新刷新二维码!");
        }
        Object o = RedisUtils.get(loginId);
        ScanStatusAuthRes authRes = JSON.parseObject(o.toString(), ScanStatusAuthRes.class);

        if (authRes.getStatus() == 2) {
            // 删除key
            RedisUtils.del(loginId);
        }
        return ResultUtils.success(authRes);
    }
}

Controller层:

@Slf4j
@RestController
public class ScanAuthController {

    @Resource
    private ScanAuthService scanAuthService;


    /**
     * 获取扫码登录二维码
     *
     * @return
     */
    @ApiOperation(value = "获取扫码登录二维码", notes = "获取扫码登录二维码")
    @GetMapping("/scanLogin")
    public PageResult qrcodeCreate() {
        return ResultUtils.success(scanAuthService.qrcodeCreate());
    }


    /**
     * 轮询获取登录状态
     *
     * @param loginId
     * @return
     */
    @ApiOperation(value = "轮询获取登录状态", notes = "轮询获取登录状态")
    @GetMapping("/scanStatus")
    public PageResult scanStatus(@RequestParam("loginId") String loginId) {
        return ResultUtils.success(scanAuthService.scanStatus(loginId));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非零基础自学Golang 第13章 并发与通道 13.1 概述

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.1 概述13.1.1 并行与并发13.1.2 Go并发优势第13章 并发与通道 并发是指在同一段时间内&#xff0c;程序可以执行多个任务。 随着社会需求的发展&#xff0c;光靠硬件的提升是无法满足高并发的需求的&#…

[前端攻坚]:数组去重的几种方法

总结一些日常需要用到的一些api&#xff0c;也是在一些面试中会经常出现的题目&#xff0c;今天分享的是数组去重的几个不同的方法&#xff0c; 同时文章也被收录到我的《JS基础》专栏中&#xff0c;欢迎大家点击收藏加关注。 数组去重的方法 1.set去重 2.map去重 3.for循环in…

Python安装Pycrypto

前言 安装 使用以下命令安装 pip install pycrypto2.6.1报错 如果在安装过程中出现如下错误 则说明系统缺乏相应python开发包&#xff0c;需要进行安装对应的python开发包 解决 在CentOS下&#xff0c;如果是python2.7则使用如下命令安装 yum install python-devel是pyt…

Pytest用例运行及规范

温馨提示 本篇约1600字&#xff0c;看完需3-5分钟&#xff0c;学习学半小时&#xff0c;加油&#xff01; 先看普通函数运行顺序 import pytestdef test_one():print("我是清安")def test_02():print("--02--")def test_a():print("--a--")de…

BP神经网络的最简Python实现

文章目录神经元BP原理及实现测试BP&#xff0c;就是后向传播(back propagation)&#xff0c;说明BP网络要向后传递一个什么东西&#xff0c;这个东西就是误差。 而神经网络&#xff0c;就是由神经元组成的网络&#xff0c;所以在考虑BP之前&#xff0c;还不得不弄清楚神经元是…

endata 电影票房响应数据破解

本文仅供参考学习&#xff0c;如有侵权可联系本人 目标网站 aHR0cHM6Ly93d3cuZW5kYXRhLmNvbS5jbi9Cb3hPZmZpY2UvQk8vWWVhci9pbmRleC5odG1s加密入口分析 在异步请求那里可以看到请求接口&#xff0c;请求参数并未加密只是响应内容进行了加密&#xff0c;暂时也无法判断加密方…

JavaWeb的Servlet学习之Request03

目录 1.Request 1.1Request执行流程 1.2request对象和response对象的原理 1.3 request对象继承体系结构 1.4request功能&#xff1a; 1.3.1获取请求消息数据 1.获取请求行数据 2.获取请求头 3.获取请求体数据 4.其他功能 4.1获取请求参数通用方式&#xff1a;不论get…

开源CA搭建-基于openssl实现数字证书的生成与分发

目录 一、前言 二、openssl介绍 三、openssl的常用用法 &#xff08;一&#xff09;单向加密 &#xff08;二&#xff09;生成随机数 &#xff08;三&#xff09;生成公钥&#xff0c;私钥 1.生成私钥 2.提取公钥 四、搭建CA &#xff08;一&#xff09;创建根CA私钥…

Linux的camera驱动 摄像头调试方法

CameraInfo类用来描述相机信息&#xff0c;通过Camera类中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法获得&#xff0c; 主要包括以下两个成员变量facing&#xff0c;facing 代表相机的方向&#xff0c; 它的值只能是CAMERA_FACING_BACK&#xff08;后置摄像头&am…

Golang 【basic_leaming】1 基本语法

阅读目录Go 语言变量Go 语言 - 声明变量1. 标准格式2. 批量格式Go 语言 - 初始化与定义变量1. 标准格式2. 编译器推导类型格式3. 短变量声明与初始化Go语言 - 多变量同时赋值Go 语言 - 匿名变量参考资料Go 语言整型&#xff08;整数类型&#xff09;1 自动匹配平台的 int 和 un…

新项目为什么决定用 JDK 17了

大家好&#xff0c;我是风筝。公众号「古时的风筝」&#xff0c;专注于后端技术&#xff0c;尤其是 Java 及周边生态。文章会收录在 JavaNewBee 中&#xff0c;更有 Java 后端知识图谱&#xff0c;从小白到大牛要走的路都在里面。 最近在调研 JDK 17&#xff0c;并且试着将之前…

阴差阳错,阴阳之变

北京的第一批“杨康”们已经返回到工作岗位&#xff0c;这其中就包括我。简单总结一下我的感染和康复过程&#xff0c;给大家做个样本吧。我属于北京放开的第一波感染者&#xff0c;12.9日当天感觉嗓子干&#xff0c;毫不犹豫&#xff0c;果然是中招了&#xff1b;周末开始发烧…

特朗普发行NFT惹群嘲,上线售罄现“真香定律”

文/章鱼哥出品/陀螺财经特朗普14日在其创建的社交平台truth social上发帖称&#xff0c;“美国需要一个超级英雄”。他还预告自己将于当地时间15日宣布“重大消息”。据《新闻周刊》报道&#xff0c;特朗普当日在其社交平台上发了一段十几秒的视频&#xff0c;里面有一个他站在…

Windows实时运动控制软核(三):LOCAL高速接口测试之C++

今天&#xff0c;正运动小助手给大家分享一下MotionRT7的安装和使用&#xff0c;以及使用C对MotionRT7开发的前期准备。 01 MotionRT7简介 MotionRT7是深圳市正运动技术推出的跨平台运动控制实时内核&#xff0c;也是国内首家完全自主自研&#xff0c;自主可控的Windows运动控…

Linux搭建测试环境详细步骤

本文讲解如何在Linux CentOS下部署Java Web项目的步骤 环境准备 &#xff08;1&#xff09;Linux系统&#xff08;2&#xff09;JDK&#xff08;3&#xff09;Tomcat &#xff08;4&#xff09;MySQL工具下载 一、Linux系统 本文主要是Linux CentOS7为例 自己在家练习小项…

[拆轮子] PaddleDetection 中的 COCODataSet 是怎么写的

今日&#xff0c;辗转反侧&#xff0c;该&#x1f4a9;的代码就是跑不成功&#xff0c;来看看 COCODataSet 到底是怎么写的&#xff0c;本文只参考当前版本的代码&#xff0c;当前版本 PaddleDetection2.5 COCODataSet 源码见附录 COCODataSet 类内部就三个函数&#xff1a; …

词义和词义消歧

Synsets(“synonym sets”, effectively senses) are the basic unit of organization in WordNet.同义词集 对于许多应用程序&#xff0c;我们希望消除歧义 • 我们可能只对一种含义感兴趣 • 在网络上搜索chemical plant 化工厂&#xff0c;我们不想搜到香蕉中的化学物质 所以…

【SpringBoot扩展点】 容器刷新前回调ApplicationContextInitializer

本文将作为Spring系列教程中源码版块的第一篇&#xff0c;整个源码系列将分为两部分进行介绍&#xff1b;单纯的源码解析&#xff0c;大概率是个吃力没人看的事情&#xff0c;因此我们将结合源码解析&#xff0c;一个是学习下别人的优秀设计&#xff0c;一个是站在源码的角度看…

【MySQL】索引和事务重点知识汇总

目录1.索引:1.1 索引的使用:1.2 索引背后的核心数据结构:1.2.1 先认识 B 树(N叉搜索树):1.2.2 再认识 B 树(N叉搜索树):2.事务:2.1 隔离性:2.1.1 脏读问题:2.1.2 不可重复读问题:2.1.3 幻读问题:2.1.4 总结:2.1.5 隔离级别:1.索引: 索引存在的意义就是为了提高查询到效率.索引…

【AI理论学习】Python机器学习中的特征选择

Python机器学习中的特征选择特征选择方法特征选择的Python库使用Scikit-learn实现特征选择方差卡方检验ANOVALasso正则化递归特征消除使用Feature-engine进行特征选择单变量特征选择相关性Python 中的更多特性选择方法参考资料任何数据科学项目的一个重要步骤是选择最具预测性的…