基于微信公众号(服务号)实现扫码自动登录系统功能

news2025/2/28 5:21:55

微信提供了两种方法都可以实现扫描登录。

一种是基于微信公众平台的扫码登录,另一种是基于微信开放平台的扫码登录。

两者的区别:

微信开放平台需要企业认证才能注册(认证费用300元,只需要认证1次,后续不再需要进行缴费年审)。

微信公众平台需要认证微信服务号(认证费用300元/年),才能进行扫码登录的开发。

本文主要介绍的是微信公众平台中的扫码登录。

微信公众号在线开发文档:微信公众平台开发概述 | 微信开放文档

目录

一、应用场景

二、实现原理

 三、需要用到的接口

(1)获取Access token

(2)生成带参数的二维码

(3)接收事件推送

四、核心部分代码

(1)获取access_token

(2)获取临时二维码

(3)微信推送事件方法

(4)request请求转换为Map方法

(5)检查微信登录状态

五、你可能会遇到的问题

(1)填写服务配置可能会遇到的问题

(2)本地正常,服务器上无法调用

六、结语


一、应用场景

我在我的网站中实现了这个功能,只需要使用微信扫描并关注公众号,即可实现自动登录。

在线体验地址:https://www.ewbang.com/community/login

首次扫码登录需要先进行关注公众号,后续只需要使用微信扫码即可完成自动登录。

注:你需要准备一个已经认证过的公众号或者测试号

二、实现原理

大致流程

1、用户访问微信快速登录页面,生成带参数的二维码。

2、监听微信推送事件(订阅或者扫码)

3、用户扫码会有两种情况:

① 用户初次关注公众号(订阅事件subscribe),根据时间返回的OpenID进行查询,如果系统中存在此用户,直接调用系统登录方法,否则创建默认账号并绑定OpenID,再调用系统登录方法。

② 用户之前关注过工作号(扫码事件SCAN),根据时间返回的OpenID进行查询,如果系统中存在此用户,直接调用系统登录方法,否则创建默认账号并绑定OpenID,再调用系统登录方法。

4、根据参数查询用户登录状态(轮询),登录成功之后,重定向到指定页面。

运行流程图(参考)

 三、需要用到的接口

(1)获取Access token

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

(2)生成带参数的二维码

https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html

(3)接收事件推送

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

这里只处理订阅事件和扫码事件即可。

四、核心部分代码

用到的部分依赖jar包

  <!-- hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>

        <!--生成二维码依赖包-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>

        <!--xml解析-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

(1)获取access_token

    /**
     * 获取access_token
     *
     * @return
     */
    public String createAccessToken() {
        String url = "https://api.weixin.qq.com/cgi-bin/token";
        Map<String, Object> data = new HashMap<>();
        data.put("grant_type", "client_credential");
        data.put("appid", wxConfig.getAppid());
        data.put("secret", wxConfig.getSecret());
        log.info("createAccessToken data:{}", data);
        String json = HttpUtil.createGet(url).form(data).execute().body();
        String access_token = (String) JSONUtil.getByPath(JSONUtil.parse(json), "access_token");
        return access_token;
    }

(2)获取临时二维码

这里需要注意的是:

action_name:QR_SCENE(场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000))

action_name:QR_STR_SCENE(场景值ID(字符串形式的ID),字符串类型,长度限制为1到64)

  /**
     * 创建临时二维码
     *
     * @return
     */
    public String createTempQrCode(String access_token, String scene_str) {
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
        Map<String, Object> data = new HashMap<>();
        data.put("expire_seconds", 604800);
        data.put("action_name", "QR_STR_SCENE");
        Map<String, Object> action_info = new HashMap<>();
        Map<String, Object> scene = new HashMap<>();
        scene.put("scene_str", scene_str);
        action_info.put("scene", scene);
        data.put("action_info", action_info);
        String json = HttpUtil.createPost(url)
                .header("Content-Type", "application/json")
                .body(JSONUtil.toJsonStr(data))
                .execute().body();
        System.out.println("json = " + json);
        String qrcode = (String) JSONUtil.getByPath(JSONUtil.parse(json), "url");
        return qrcode;
    }

(3)微信推送事件方法

get请求接口主要用于签名验证。(在配置开发者服务器配置(已启用)时会用到)

post请求接口主要用于事件接收逻辑处理

/*
     * @param signature 微信加密签名,signature结合了开发者填写的 token 参数和请求中的 timestamp 参数、nonce参数。
     * @param timestamp 时间戳
     * @param nonce     这是个随机数
     * @param echostr   随机字符串,验证成功后原样返回
     */
    @GetMapping("/wx/event")
    public void get(@RequestParam(required = false) String signature,
                    @RequestParam(required = false) String timestamp,
                    @RequestParam(required = false) String nonce,
                    @RequestParam(required = false) String echostr,
                    HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(echostr);
        response.getWriter().flush();
        response.getWriter().close();
    }


    //处理微信推送事件
    @PostMapping("/wx/event")
    public void post(final HttpServletRequest request, HttpServletResponse response) {
        try {
            // 微信加密签名
            final String signature = request.getParameter("signature");
            // 时间戳
            final String timestamp = request.getParameter("timestamp");
            // 随机数
            final String nonce = request.getParameter("nonce");
            // 随机字符串
            final String echostr = request.getParameter("echostr");

            //将xml文件转成易处理的map(下方贴出)
            final Map<String, String> map = parseXml(request);
            //开发者微信号
            final String toUserName = map.get("ToUserName");
            //OpenId
            final String fromUserName = map.get("FromUserName");
            //消息创建时间 (整型)
            final String createTime = map.get("CreateTime");
            //消息类型,event
            final String msgType = map.get("MsgType");
            //事件类型
            final String event = map.get("Event");

            String scene_str = "";
            if ("event".equals(msgType)) {
                if (event.equals("subscribe")) {
                    final String ticket = map.get("Ticket");
                    if (ticket != null) {
                        scene_str = map.get("EventKey").replace("qrscene_", "");
                    }
                }
                //注:事件类型为SCAN即已关注
                else if (event.equals("SCAN")) {
                    final String ticket = map.get("Ticket");
                    if (ticket != null) {
                        scene_str = map.get("EventKey");
                    }
                }
            }

            // 如果scene_str 不为空代表用户已经扫描并且关注了公众号
            if (StringUtils.isNotEmpty(scene_str)) {
                // 将微信公众号用户ID缓存到redis中,标记用户已经扫码完成,执行登录逻辑。
                redisCache.setCacheObject(scene_str, fromUserName, LoginStatus.SEESIOIN_TIME, TimeUnit.SECONDS);
            }
            response.getWriter().write("");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

(4)request请求转换为Map方法

    //将xml文件转成易处理的map
    public static Map<String, String> parseXml(final HttpServletRequest request) throws Exception {
        final Map<String, String> map = new HashMap<String, String>();
        InputStream inputStream = request.getInputStream();
        final SAXReader reader = new SAXReader();
        final Document document = reader.read(inputStream);
        final Element root = document.getRootElement();
        final List<Element> elementList = (List<Element>) root.elements();
        for (final Element e : elementList) {
            map.put(e.getName(), e.getText());
        }
        inputStream.close();
        inputStream = null;
        return map;
    }

(5)检查微信登录状态

    /**
     * 检查微信登录状态
     *
     * @return
     */
    @PostMapping("/wxLogin")
    @ResponseBody
    public AjaxResult checkWxLoginStatus(HttpSession session) {
        String scene_str = (String) session.getAttribute("scene_str");
        if (StringUtils.isEmpty(scene_str)) {
            return AjaxResult.custom("二维码已过期", 1001);
        }
        String loginStatus = redisCache.getCacheObject(scene_str);
        if (LoginStatus.NOT_LOGIN.equals(loginStatus)) {
            return AjaxResult.custom("未登录", 1002);
        }
        //执行登录方法
        try {
            autoWxLogin(loginStatus);
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
        return AjaxResult.success("登录成功");
    }

五、你可能会遇到的问题

(1)填写服务配置可能会遇到的问题

 如果配置的url不能访问,会报token验证失败。在本地开发测试,可以借助于网穿透工具:Sunny-Ngrok内网转发内网穿透 - 国内内网映射服务器

(2)本地正常,服务器上无法调用

有可能你在本地开发过程中是没有问题,但是你将代码发布到服务器上的时候,发现接口调用失败。有可能是你没有将公网服务器IP添加到白名单导致的。

多个白名单配置方法:每行配置一个IP地址即可。

六、结语

本章教程到这里就结束了,以上内容仅来自于博主的自我总结,也许其中有总结不到为的地方,希望能您够多多理解,感谢你的阅读,希望对你有一点点的帮助。

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

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

相关文章

Python制作9行最简单音乐播放器?不,我不满足

嗨害大家好鸭~我是小熊猫 好久不见啦~这次就来给大家整个大福利 ~ 源码资料电子书:点击此处跳转文末名片获取 最简单的9行代码音乐播放器如下&#xff1a; import time import pygamefile r歌曲路径 pygame.mixer.init() print(正在播放,file) track pygame.mixer.music.lo…

遗传算法及Python实现

0 建议学时 4学时 1 人工智能概述 2020中国人工智能产业年会在苏州召开&#xff0c;会上发布的《中国人工智能发展报告2020》显示&#xff0c;过去十年(2011-2020) &#xff0c;中国人工智能专利申请量达389571件&#xff0c;占全球总量的74.7%&#xff0c;位居世界第一。 报…

Power BI 数据处理介绍(数据初始调整、合并列及查看数据结构)

本系列的文章&#xff1a; 安装流程和示例介绍&#xff1a; 《Power BI windows下载安装流程&#xff09;》《Power BI 11个必学官方示例数据案例&#xff08;附下载链接&#xff09;》 数据导入阶段介绍&#xff1a; 《Power BI 数据导入&#xff08;SQL Server、MySQL、网页…

1802908-00-4,Dde Biotin-PEG4-alkyne,炔烃PEG4生物素Dde

Dde Biotin-PEG4-alkyne&#xff0c;Alkyne-PEG4-Biotin Dde&#xff0c;Dde 生物素-PEG4-炔烃&#xff0c;Dde 生物素PEG4炔基&#xff0c;炔烃PEG4生物素DdeProduct structure&#xff1a;Product specifications&#xff1a;1.CAS No&#xff1a;1802908-00-42.Molecular fo…

leetcode 540. Single Element in a Sorted Array(排序数组中的单个元素)

给一个已经排好序的升序数组&#xff0c;其中每个元素都会重复2次&#xff0c;只有一个元素只有一个&#xff0c; 找出这个只有一个的元素。 要求时间复杂度在O(logn), 空间复杂度在O(1). 思路&#xff1a; 时间复杂度为O(logn), 让人想到了binary search. 因为时间复杂度为…

keil5安装了pack包但是还是不能选择device

一开始&#xff0c;我以为是keil5无法安装 STM32 芯片包&#xff0c;打开device倒是可以看到stm公司的芯片包&#xff0c;但是没有我想要的stm32f1。 我按照网上的一些说法&#xff0c;找到了这个STM32F1 的pack芯片包&#xff0c;但是我双击安装的时候&#xff0c;它的安装位…

(HP)新手引导使用react-shepherd

1&#xff0c;官方参数文档&#xff1a;https://shepherdjs.dev/docs/tutorial-02-usage.html 2&#xff0c;基本代码 import { ShepherdTour } from react-shepherd; import ./index.less; // 自己的样式文件&#xff0c;用来修改样式 import ./shepherd.less; // 将shephe…

嵌入式C语言自我修养:从芯片、编译器到操作系统-习题、笔记

前沿 C语言测试(1):基本概念考查 什么是标识符、关键字和预定义标识符? 三者有何区别? 标识符&#xff08;Identifier&#xff09;:由程序员定义&#xff0c;用来表示变量&#xff0c;包括了变量名、函数名、宏名、结构体名等。 标识符的命名规范&#xff1a;C语言规定&…

Android架构设计——【 APT技术实现butterknife框架 】

APT简介 APT英文全称&#xff1a;Android annotation process tool是一种处理注释的工具&#xff0c;它对源代码文件进行检测找出其中的Annotation&#xff0c;使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文…

100种思维模型之启发式偏差思维模型-017

曾国藩在给儿子的一封家书中曾写道&#xff1a;余于凡事皆用困知勉行工夫&#xff0c;尔不可求名太骤&#xff0c;求效太捷也。熬过此关&#xff0c;便可少进。再进再困&#xff0c;再熬再奋&#xff0c;自有亨通精进之日。 不急躁不求捷径&#xff0c;小火慢炖&#xff0c;将事…

burp小程序抓包

身为一名码农&#xff0c;抓包肯定是一项必备技能。工作中遇到很多次需要对小程序进行抓包排查问题。下面分享一下我的抓包方式&#xff0c;使用的是电脑版小程序抓包&#xff0c;跟手机的方式都差不多的。 一、环境 微信版本&#xff1a;3.6.0.18 Burpsuite版本&#xff1a…

Python容器

容器 容器是一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素&#xff0c;每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。 数据容器根据特点的不同&#xff0c;如&#xff1a; 是否支持重复元素是否可以修改是否…

k8s1.17.2+centos7.7+docker18.06

1.简介 1.1pod网络 总述&#xff1a;Kubernetes 的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中 Flannel&#xff1a;基于L2&#xff0c;构建用于各个pod通信的网络架构。使用iptables进行数据包过滤。Calico&#xff1a;纯L3&#xff0c;构建用于各个pod通…

【Java基础】反射

概述 引入 package ref;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.r…

VUCA项目环境中如何提升项目进度计划与控制工作有效性的系统思考【一杯咖啡谈项目】

VUCA环境下&#xff0c;项目进度延迟是经常遇见的问题&#xff0c;如何解决此类问题&#xff1f;今天分享一个案例&#xff0c;在这个案例中&#xff0c;基于“根因分析法”&#xff0c;对某主题客户端项目的进度延迟问题进行了分析&#xff0c;找到根本原因&#xff0c;提出了…

鼠标指针文件格式解析

鼠标指针文件格式解析 文章目录鼠标指针文件格式解析windowsico文件格式分析文件头&#xff1a;图像数据头段&#xff1a;图像数据段&#xff1a;Ani动态光标格式解析数据结构&#xff1a;anihseq **rate**LISTcur静态光标文件格式解析macOSLinuxwindows ico文件格式分析 是一…

2023年PMP考试复习攻略

如何备考PMP呢&#xff1f; 这本书是PMP认证考试的官方教材&#xff0c;体系完善&#xff0c;可以迅速帮助入门者搭建项目管理知识体系&#xff0c;备考PMP考试的伙伴&#xff0c;这本书一定要读一遍&#xff01; 经验都是积累出来的&#xff0c;交流小队里有很多分享面试经验…

Vue3 中生命周期的使用

目录前言&#xff1a;一、什么是生命周期函数二、什么是 Keep-Alive 组件三、生命周期函数分为几种&#xff0c;分别有什么用&#xff1f;1. beforeCreate2. created3. beforeMount/onBeforeMount4. mounted/onMounted5. beforeUpdate/onBeforeUpdate6. updated/onUpdated7. be…

spring之事务概述

文章目录前言一、事务概述1、什么是事务2、事务的四个处理过程3、事务的四个特性二、引入事务场景1、引入依赖2、数据库创建3、建包4、spring.xml配置文件5、测试程序6、运行结果&#xff08;成功&#xff09;7、模拟异常三、Spring对事务的支持1、Spring实现事务的两种方式2、…

数值方法笔记4:插值、近似和拟合

1. 插值1.1 插值的一些概念1.1.1 插值的定义1.1.2 插值的存在性1.1.3 插值的误差分析1.2 拉格朗日插值(Lagrange Interpolation)1.2.1 拉格朗日插值误差分析1.3 Newton多项式插值1.3.1 Newton多项式插值误差分析1.4 Chebyshev多项式确定插值点1.4.1 Chebyshev多项式性质1.5 有理…