微信公众号扫码登录

1.设计

我们采用的是个人号登录方式,这样拿不到我们的userInfo用户信息,然后我们将用户发来的消息(xml消息体)中的FromUser作为我们唯一的openId
整体流程:
1.用户扫码公众号码,然后发一条消息:验证码,我们就会通过api回复一个随机的码存入Redis中(主要结构是loginCode.随机码,value为openId)
2.当用户输入后点击登录就进入我们的注册模块,同时关联角色和权限,实现网关的统一鉴权
3.用户就可以根据个人的openId来维护个人信息
4.用户登录成功后,返回token,前端所有亲亲贵带着token就可以访问了

2.代码

1.微信的callback接口,实现了对微信公众平台的回调验证功能,确保请求真实有效
在接收微信公众平台的回调请求时,该方法会对请求中的参数进行验证,确保这个请求是真实的,如果验证成功就会返回一个随机的字符串确保它的有效性;

 /*** 1.回调验证(当前这个服务是否同样的)* 该代码实现了微信公众平台的回调验证功能。在接收到来自微信服务器的消息回调请求时,* 该方法会首先对请求中的参数进行签名验证,以确保请求是真实的。如果验证通过,* 则方法会返回一个随机字符串,作为确认该请求的有效性。如果验证失败,则方法会返回一个错误消息。* @param signature:微信加密签名* @param timestamp:时间戳作加密使用* @param nonce:随机数* @param echostr:随机字符串* @return:如果通过则返回随机字符串 echostr,否则unknown*/@GetMapping("callback")public String callback(@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("echostr") String echostr) {log.info("get验签请求参数:signature:{},timestamp:{},nonce:{},echostr:{}",signature, timestamp, nonce, echostr);String shaStr = SHA1.getSHA1(token, timestamp, nonce, "");if (signature.equals(shaStr)) { //判断生成的字符串签名与传入的签名是否一致return echostr;}return "unknown";}

在这里插入图片描述
确定请求有效性后,返回:在这里插入图片描述
2.对于普通消息的处理:
**1.在我们接收到来自微信服务器的普通消息时,我们会将消息解析为XML格式——>2.然后我们利用子当以的UtilXml转为Map集合,然后提取消息类型事件类型,然后将消息类型和事件类型封装起来——>3.**根据类型确定一个消息处理器(WxChatMsgHandler对象),我们的处理器会根据不同的类型,生成对应的回复内容

 /*** 2.普通消息的处理* 。在接收到来自微信服务器的普通消息时,该方法会将消息解析为 XML 格式,并从消息中提取出消息类型和事件类型(如果有)。* 根据消息类型和事件类型,该方法会选择一个适当的消息处理器(即 WxChatMsgHandler 对象),并将消息的具体内容传给该处理器进行处理。* 处理器会根据不同的消息类型和事件类型,生成相应的回复内容,并返回给微信服务器。* @param requestBody* @param signature* @param timestamp* @param nonce* @param msgSignature* @return*/@PostMapping(value = "callback", produces = "application/xml;charset=UTF-8")public String callback(@RequestBody String requestBody, // 接收到的原始 XML 消息内容@RequestParam("signature") String signature, // 签名串@RequestParam("timestamp") String timestamp, // 时间戳@RequestParam("nonce") String nonce, // 随机数@RequestParam(value = "msg_signature", required = false) String msgSignature // 消息签名) {// 打印接收到的消息内容log.info("接收到微信消息:requestBody:{}", requestBody);// 使用 MessageUtil 工具类将 XML 消息解析为 MapMap<String, String> messageMap = MessageUtil.parseXml(requestBody);// 获取消息类型和事件类型String msgType = messageMap.get("MsgType");String event = messageMap.get("Event") == null ? "" : messageMap.get("Event");log.info("msgType:{},event:{}", msgType, event);// 构造一个字符串,用于标识消息类型和事件类型StringBuilder sb = new StringBuilder();sb.append(msgType);if (!StringUtils.isEmpty(event)) {sb.append(".");sb.append(event);}// 根据消息类型和事件类型获取对应的处理器String msgTypeKey = sb.toString();WxChatMsgHandler wxChatMsgHandler = wxChatMsgFactory.getHandlerByMsgType(msgTypeKey);if (Objects.isNull(wxChatMsgHandler)) {return "unknown";}// 使用处理器处理消息,并生成回复内容String replyContent = wxChatMsgHandler.dealMsg(messageMap);log.info("replyContent:{}", replyContent);return replyContent;}

将XML转为Map的工具类:
https://blog.csdn.net/weixin_57128596/article/details/136136650?spm=1001.2014.3001.5501

根据类型确定处理器:
实现InitalLizingBean,重写afterPropertiesSet方法,将类型以及对应的处理器放入Map中

package com.wyh.wx.handler;import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Component
public class WxChatMsgFactory implements InitializingBean {@Resourceprivate List<WxChatMsgHandler> wxChatMsgHandlerList;private Map<WxChatMsgTypeEnum, WxChatMsgHandler> handlerMap = new HashMap<>();public WxChatMsgHandler getHandlerByMsgType(String msgType) {WxChatMsgTypeEnum msgTypeEnum = WxChatMsgTypeEnum.getByMsgType(msgType);return handlerMap.get(msgTypeEnum);}@Overridepublic void afterPropertiesSet() throws Exception {for (WxChatMsgHandler wxChatMsgHandler : wxChatMsgHandlerList) {handlerMap.put(wxChatMsgHandler.getMsgType(), wxChatMsgHandler);}}}

处理器WxChatMsgHandler根据具体类型响应消息:

package com.wyh.wx.handler;import com.wyh.wx.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;@Component
@Slf4j
public class ReceiveTextMsgHandler implements WxChatMsgHandler {private static final String KEY_WORD = "验证码";private static final String LOGIN_PREFIX = "loginCode";@Resourceprivate RedisUtil redisUtil;@Overridepublic WxChatMsgTypeEnum getMsgType() {return WxChatMsgTypeEnum.TEXT_MSG; // 处理文本消息}@Overridepublic String dealMsg(Map<String, String> messageMap) {log.info("接收到文本消息事件");String content = messageMap.get("Content"); // 文本消息内容if (!KEY_WORD.equals(content)) {return ""; // 如果不是“验证码”,则返回空字符串}String fromUserName = messageMap.get("FromUserName"); // 发送者的 OpenIDString toUserName = messageMap.get("ToUserName"); // 接收者的 OpenIDRandom random = new Random();int num = random.nextInt(9000) + 1000; // 生成一个 4 位数字的验证码String numKey = redisUtil.buildKey(LOGIN_PREFIX, String.valueOf(num)); // 验证码的键redisUtil.setNx(numKey, fromUserName, 5L, TimeUnit.MINUTES); // 将发送者的 OpenID 作为值,并将其存储在 Redis 中,有效期为 5 分钟String numContent = "您当前的验证码是:" + num + "! 5分钟内有效"; // 验证码的文本内容String replyContent = "<xml>\n" +"  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +"  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +"  <CreateTime>12345678</CreateTime>\n" +"  <MsgType><![CDATA[text]]></MsgType>\n" +"  <Content><![CDATA[" + numContent + "]]></Content>\n" +"</xml>"; // 回复的 XML 内容return replyContent; // 返回回复的 XML 内容}}

3.得到验证码后我们请求登录

流程:
在得到验证码后,我们利用验证码请求登录,将用户注册到数据库中并得到tokenValue

代码:

 @RequestMapping("doLogin")public Result<SaTokenInfo> doLogin(@RequestParam("validCode") String validCode) {try {Preconditions.checkArgument(!StringUtils.isBlank(validCode), "验证码不能为空!");return Result.ok(authUserDomainService.doLogin(validCode));} catch (Exception e) {log.error("UserController.doLogin.error:{}", e.getMessage(), e);return Result.fail("用户登录失败");}}

业务Domain层:
1:首先会根据我们的验证码得到我们的唯一的openId,然后将其封装到AuthUser类中,进行注册(如果数据库中已经存在则直接返回true),否则进行注册,关联角色权限
2:注册完后,利用SaToken进行登录loginId(),然后获取token信息
在这里插入图片描述
3:然后我们就可以通过tokenValue去请求一些权限接口

 /*** 4.登录1. 根据验证码生成登录键(loginKey)2. 从 Redis 中获取 openId,如果不存在则返回 null3. 如果 openId 不为空,则使用 AuthUserBO 对象封装用户信息,并调用 register 方法将其注册到系统中4. 使用 StpUtil.login 方法登录系统,并获取 Token 信息5. 返回 Token 信息* @param validCode* @return*/@Overridepublic SaTokenInfo doLogin(String validCode) {// 根据验证码生成登录键String loginKey = redisUtil.buildKey(LOGIN_PREFIX, validCode);// 从 Redis 中获取 openId,如果不存在则返回 nullString openId = redisUtil.get(loginKey);if (StringUtils.isBlank(openId)) {return null;}// 使用 AuthUserBO 对象封装用户信息,并调用 register 方法将其注册到系统中(new一个authUser封装openid去数据库查询)AuthUserBO authUserBO = new AuthUserBO();authUserBO.setUserName(openId);this.register(authUserBO);// 使用 StpUtil.login 方法登录系统,并获取 Token 信息StpUtil.login(openId);SaTokenInfo tokenInfo = StpUtil.getTokenInfo();// 返回 Token 信息return tokenInfo;}/*** 注册** @param authUserBO* @return*/@Override@SneakyThrows@Transactional(rollbackFor = Exception.class)public Boolean register(AuthUserBO authUserBO) {//校验用户是否存在,如果存在该用户,直接returnAuthUser existAuthUser = new AuthUser();existAuthUser.setUserName(authUserBO.getUserName());List<AuthUser> existUser = authUserService.queryByCondition(existAuthUser);if (existUser.size() > 0) {return true;}//1.Bo转entity类AuthUser authUser = AuthUserBOConverter.INSTANCE.convertBOToEntity(authUserBO);//2.如果密码不为空,则进行md+盐值加密(照顾微信登录)if(!StringUtils.isBlank(authUser.getPassword())){authUser.setPassword(SaSecureUtil.md5BySalt(authUser.getPassword(), salt));}//3.用户open状态authUser.setStatus(AuthUserStatusEnum.OPEN.getCode());authUser.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());//4.插入用户数据Integer count = authUserService.insert(authUser);//5.建立一个角色与用户的关系AuthRole authRole = new AuthRole();authRole.setRoleKey(AuthConstant.NORMAL_USER);AuthRole roleResult = authRoleService.queryByCondition(authRole); //查询 普通用户 这一角色Long roleId = roleResult.getId();Long userId = authUser.getId();//5.将用户与角色的关系进行建立AuthUserRole authUserRole = new AuthUserRole();authUserRole.setUserId(userId);authUserRole.setRoleId(roleId);authUserRole.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());authUserRoleService.insert(authUserRole);//4.auth业务中将用户角色以及对应的权限注入redis(UserName作为openId)String roleKey = redisUtil.buildKey(authRolePrefix, authUser.getUserName());LinkedList<AuthRole> roleList = new LinkedList<>();roleList.add(authRole);redisUtil.set(roleKey,new Gson().toJson(roleList));//4.2将权限注入RedisAuthRolePermission authRolePermission = new AuthRolePermission();authRolePermission.setRoleId(roleId); //新注册用户的角色idList<AuthRolePermission> authRolePermissionList = authRolePermissionService.queryByCondition(authRolePermission);List<Long> permissionIdList = authRolePermissionList.stream().map(AuthRolePermission::getPermissionId).collect(Collectors.toList()); //对应角色id的所有权限idsList<AuthPermission> permissionList = authPermissionService.queryByPermissionIds(permissionIdList);String permissionKey = redisUtil.buildKey(authPermissionPrefix, authUser.getUserName());redisUtil.set(permissionKey,new Gson().toJson(permissionList)); //将权限对应key和集合注入redis中return count > 0;}

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

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

相关文章

2.13日学习打卡----初学RocketMQ(四)

2.13日学习打卡 目录&#xff1a; 2.13日学习打卡一.RocketMQ之Java ClassDefaultMQProducer类DefaultMQPushConsumer类Message类MessageExt类 二.RocketMQ 消费幂消费过程幂等消费速度慢的处理方式 三.RocketMQ 集群服务集群特点单master模式多master模式多master多Slave模式-…

C语言希尔排序详解!!!速过

目录 希尔排序是什么&#xff1f; 关于时间复杂度 希尔排序的源代码 希尔排序源代码的详解 希尔排序是什么&#xff1f; 之前我们说了三个排序&#xff08;插入排序&#xff0c;选择排序&#xff0c;冒泡排序&#xff09;有需要的铁铁可以去看看之前的讲解。 但因为之前的…

基于Python的信息加密解密网站设计与实现【源码+论文+演示视频+包运行成功】

博主介绍&#xff1a;✌csdn特邀作者、博客专家、java领域优质创作者、博客之星&#xff0c;擅长Java、微信小程序、Python、Android等技术&#xff0c;专注于Java、Python等技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; …

CSS 圆形的时钟秒针状的手柄绕中心点旋转的效果

<template><!-- 创建一个装载自定义加载动画的容器 --><view class="cloader"><!-- 定义加载动画主体部分 --><view class="clface"><!-- 定义类似秒针形状的小圆盘 --><view class="clsface"><!-…

docker (五)-docker存储-数据持久化

将数据存储在容器中&#xff0c;一旦容器被删除&#xff0c;数据也会被删除。同时也会使容器变得越来越大&#xff0c;不方便恢复和迁移。 将数据存储到容器之外&#xff0c;这样删除容器也不会丢失数据。一旦容器故障&#xff0c;我们可以重新创建一个容器&#xff0c;将数据挂…

Linux常见指令(一)

一、基本指令 1.1ls指令 语法 &#xff1a; ls [ 选项 ][ 目录或文件 ] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项&#xff1a; -a 列出目录下的所有文件&#xff0c;包括以 .…

github Two-factor authentication (2FA)is required for your GitHub account

问题 github 2FA认证 详细问题 笔者使用GitKraken&#xff0c;使用github登录&#xff0c;github要去 Two-factor authentication (2FA)is required for your GitHub account&#xff0c;即进行2FA认证 解决方案 解决方案一、 微信 → \rightarrow →搜索腾讯身份验证器…

css篇---移动端适配的方案有哪几种

移动端适配 移动端适配是指同一个页面可以在不同的移动端设备上都有合理的布局。主流实现的方案有 响应式布局通过rem或者vw,vh 等实现不同设备有相同的比例而实现适配 首先需要了解viewport 【视口】 视口代表了一个可看见的多边形区域&#xff08;通常来说是矩形&#xff0…

【c++】vector的增删查改

1.先定义一个类对象vector 为了防止和库里面发生冲突&#xff0c;定义一个命名空间&#xff0c;将类对象放在命名空间 里面 #include<iostream> using namespace std; namespace zjw {class vector {public:private:}; }2.定义变量&#xff0c;需要一个迭代器&#xff…

Android---DslTabLayout实现底部导航栏

1. 在 Android 项目中引用 JitPack 库 AGP 8. 根目录的 settings.gradle dependencyResolutionManagement {...repositories {...maven { url https://jitpack.io }} } AGP 8. 根目录如果是 settings.gradle.kts 文件 dependencyResolutionManagement {...repositories {...…

单片机学习笔记---LED呼吸灯直流电机调速

目录 LED呼吸灯 直流电机调速 模型结构 波形 定时器初始化函数 中断函数 主程序 上一节讲了电机的工作原理&#xff0c;这一节开始代码演示&#xff01; 我们上一篇说Ton的时间长Toff时间短电机会快&#xff0c;Ton的时间短Toff时间长电机会慢 并且我们还要保证无论Ton和…

element-UI 组件 dialog 中 ref 获取不到元素

项目场景&#xff1a; vue3集成bpmn.js 渲染过程中&#xff0c;进行流程图查看 问题描述 dialog弹窗加载获取canvas中 加载不到&#xff0c;导致偶尔流程展示加载失败 原因分析&#xff1a; 提示&#xff1a;官方解释如下&#xff0c;主要就是获取的时候&#xff0c;组件没有…

qml之Control类型布局讲解,padding属性和Inset属性细讲

1、Control布局图 2、如何理解&#xff1f; *padding和*Inset参数如何理解呢&#xff1f; //main.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 import QtQml 2.12ApplicationWindow {id: windowvisible: …

Leetcode - 周赛384

目录 一&#xff0c;3033. 修改矩阵 二&#xff0c;3035. 回文字符串的最大数量 三&#xff0c;3036. 匹配模式数组的子数组数目 II 一&#xff0c;3033. 修改矩阵 这道题直接暴力求解&#xff0c;先算出每一列的最大值&#xff0c;再将所有为-1的区域替换成该列的最大值&am…

算法刷题:无重复字符的最长字串

无重复字符的最长字串 .题目链接题目详情算法原理题目解析滑动窗口定义指针进窗口判断出窗口更新结果 我的答案 . 题目链接 无重复字符的最长字串 题目详情 算法原理 题目解析 首先,为了使字符串遍历的更加方便,我们选择将字符串转换为数组 题目要求子串中不能有重复的字符…

知识图谱:py2neo将csv文件导入neo4j

文章目录 安装py2neo创建节点-连线关系图导入csv文件删除重复节点并连接边 安装py2neo 安装python中的neo4j操作库&#xff1a;pip install py2neo 安装py2neo后我们可以使用其中的函数对neo4j进行操作。 图数据库Neo4j中最重要的就是结点和边&#xff08;关系&#xff09;&a…

隐函数的求导【高数笔记】

1. 什么是隐函数&#xff1f; 2. 隐函数的做题步骤&#xff1f; 3. 隐函数中的复合函数求解法&#xff0c;与求导中复合函数求解法有什么不同&#xff1f; 4. 隐函数求导的过程中需要注意什么&#xff1f;

【Linux网络编程五】Tcp套接字编程(四个版本服务器编写)

【Linux网络编程五】Tcp套接字编程(四个版本服务器编写&#xff09; [Tcp套接字编程]一.服务器端进程&#xff1a;1.创建套接字2.绑定网络信息3.设置监听状态4.获取新连接5.根据新连接进行通信 二.客户端进程&#xff1a;1.创建套接字2.连接服务器套接字3.连接成功后进行通信 三…

爱上JVM——常见问题(一):JVM组成

1 JVM组成 1.1 JVM由那些部分组成&#xff0c;运行流程是什么&#xff1f; 难易程度&#xff1a;☆☆☆ 出现频率&#xff1a;☆☆☆☆ JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 好处&#xff1a; 一次编写&…

第三十三回 镇三山大闹青州道 霹雳火夜走瓦砾场-python分割字符串

黄信和刘知寨押解宋江和花荣向青州走&#xff0c;碰到了燕顺等三人来劫囚车&#xff0c;黄信逃走了&#xff0c;刘知寨被抓住&#xff0c;被花荣一刀杀了。 黄信把情况报给青州知府&#xff0c;派来了青州兵马秦统制&#xff0c;人称霹雳火的秦明。秦明与花荣打&#xff0c;花…