微信公众号扫码登录

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 消息解析为 Map
        Map<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 {

    @Resource
    private List<WxChatMsgHandler> wxChatMsgHandlerList;

    private Map<WxChatMsgTypeEnum, WxChatMsgHandler> handlerMap = new HashMap<>();

    public WxChatMsgHandler getHandlerByMsgType(String msgType) {
        WxChatMsgTypeEnum msgTypeEnum = WxChatMsgTypeEnum.getByMsgType(msgType);
        return handlerMap.get(msgTypeEnum);
    }

    @Override
    public 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";

    @Resource
    private RedisUtil redisUtil;

    @Override
    public WxChatMsgTypeEnum getMsgType() {
        return WxChatMsgTypeEnum.TEXT_MSG; // 处理文本消息

    }

    @Override
    public String dealMsg(Map<String, String> messageMap) {
        log.info("接收到文本消息事件");
        String content = messageMap.get("Content"); // 文本消息内容
        if (!KEY_WORD.equals(content)) {
            return ""; // 如果不是“验证码”,则返回空字符串
        }
        String fromUserName = messageMap.get("FromUserName"); // 发送者的 OpenID
        String toUserName = messageMap.get("ToUserName"); // 接收者的 OpenID

        Random 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,如果不存在则返回 null
     3. 如果 openId 不为空,则使用 AuthUserBO 对象封装用户信息,并调用 register 方法将其注册到系统中
     4. 使用 StpUtil.login 方法登录系统,并获取 Token 信息
     5. 返回 Token 信息
     * @param validCode
     * @return
     */
    @Override
    public SaTokenInfo doLogin(String validCode) {
        // 根据验证码生成登录键
        String loginKey = redisUtil.buildKey(LOGIN_PREFIX, validCode);
        // 从 Redis 中获取 openId,如果不存在则返回 null
        String 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) {
        //校验用户是否存在,如果存在该用户,直接return
        AuthUser 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将权限注入Redis
        AuthRolePermission authRolePermission = new AuthRolePermission();
        authRolePermission.setRoleId(roleId); //新注册用户的角色id
        List<AuthRolePermission> authRolePermissionList = authRolePermissionService.queryByCondition(authRolePermission);
        List<Long> permissionIdList = authRolePermissionList.stream()
                .map(AuthRolePermission::getPermissionId)
                .collect(Collectors.toList()); //对应角色id的所有权限ids
        List<AuthPermission> permissionList = authPermissionService.queryByPermissionIds(permissionIdList);

        String permissionKey = redisUtil.buildKey(authPermissionPrefix, authUser.getUserName());
        redisUtil.set(permissionKey,new Gson().toJson(permissionList)); //将权限对应key和集合注入redis中
        return count > 0;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fairy要carry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值