一、申请开发者资质

在开始之前我们需要去微信公众平台申请开发者资质,这样才可以获取自己的appidsecret

这里先说一下微信登录的大致逻辑,首先我们每个人的微信都有一个openId,以此来区分不同的微信用户,用户端扫码登录的逻辑分为一下几步:

  1. 首先向微信申请登录二维码,这里注意,同一时刻可能会有多个用户来请求,为了区分他们,我们需要给每个二维码绑定对应的场景id,从而达到区分用户的目的,后续这个场景值用code表示。
  2. 我们需要在微信公众平台绑定自己的回调地址,用户扫码后会将用户对应的code返回给我们,我们需要根据这个code去获取access_token
  3. 最后根据access_token来获取用户的信息
  4. 最后推送授权链接给用户,让用户确认授权
    在这里插入图片描述

申请完之后我们需要配置回调的地址和token,后面会再application.yml中带着大家配置。在修改URL的时候我们需要保证我们的程序正在运行。
在这里插入图片描述

二、代码实现

首先我们需要引入微信的sdk

	<dependency>
	    <groupId>com.github.binarywang</groupId>
	    <artifactId>weixin-java-mp</artifactId>
	    <version>4.4.0</version>
	</dependency>

里面集成了我们需要用到的工具类,我们需要先创建一个配置类来对我们需要配置的信息进行配置(代码中的WxMpProperties ScanHandler 类的代码会在后面给出):

WxMpConfiguration.java

package com.wang.common.config;

import com.wang.common.service.handler.ScanHandler;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.stream.Collectors;

import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SCAN;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;

/**
 * @ClassDescription:
 * @Author:Wangzd
 * @Date: 2025/1/24
 **/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
    private final ScanHandler scanHandler;

    private final WxMpProperties properties;

    @Bean
    public WxMpService wxMpService() {
    	// 这里是为了适配多个公众号的情况
        final List<WxMpProperties.MpConfig> configs = properties.getConfigs();
        if (configs == null) {
            throw new RuntimeException("微信相关配置有误,请仔细核对自己的公众号参数");
        }

        WxMpService service = new WxMpServiceImpl();
        service.setMultiConfigStorages(configs
                .stream().map(a -> {
                    WxMpDefaultConfigImpl configStorage;
                    configStorage = new WxMpDefaultConfigImpl();

                    configStorage.setAppId(a.getAppId());
                    configStorage.setSecret(a.getSecret());
                    configStorage.setToken(a.getToken());
                    configStorage.setAesKey(a.getAesKey());
                    return configStorage;
                }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
        return service;
    }

    @Bean
    public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
        // 扫码事件
        newRouter.rule().async(false).msgType(EVENT).event(SCAN).handler(this.scanHandler).end();

        return newRouter;
    }

}

WxMpProperties.java

package com.wang.common.config;

import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

/**
 * @ClassDescription:
 * @Author:Wangzd
 * @Date: 2025/1/24
 **/
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
    /**
     * 多个公众号配置信息
     */
    private List<MpConfig> configs;

    @Data
    public static class MpConfig {
        /**
         * 设置微信公众号的appid
         */
        private String appId;

        /**
         * 设置微信公众号的app secret
         */
        private String secret;

        /**
         * 设置微信公众号的token
         */
        private String token;

        /**
         * 设置微信公众号的EncodingAESKey
         */
        private String aesKey;
    }

    @Override
    public String toString() {
        return JSONUtil.toJsonStr(this);
    }
}

ScanHandler.java

package com.wang.common.service.handler;


import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.URLEncoder;
import java.util.Map;

/**
 * @ClassDescription:
 * @Author:Wangzd
 * @Date: 2024/11/8
 **/
@Component
public class ScanHandler implements WxMpMessageHandler {

    @Value("${wx.mp.callback}")
    private String callback;

    public static final String URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";


    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map,
                                    WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
        String openId = wxMpXmlMessage.getFromUser();
        Integer code;

        try {
            String eventKey = wxMpXmlMessage.getEventKey();
            eventKey = eventKey.replace("qrscene_", "");
            code = Integer.parseInt(eventKey);
        } catch (Exception e) {
            System.err.println("getEventKey error eventKey:{}" + wxMpXmlMessage.getEventKey() + e);
            return null;
        }

        // 推送链接让用户授权
        String authorizeUrl = String.format(URL, wxMpService.getWxMpConfigStorage().getAppId(), URLEncoder.encode(callback + "/wx/portal/public/callBack"));
        String content = "请点击登录:<a href=\"" + authorizeUrl + "\">登录</a>";
        WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content)
                .fromUser(wxMpXmlMessage.getToUser()).toUser(wxMpXmlMessage.getFromUser())
                .build();
        return m;
    }

}

application.yml中需要配置的信息如下

wx:
  mp:
    callback: ${wx.callback}
    configs:
      - appId: ${wx.appId} # 第一个公众号的appid
        secret: ${wx.secret} # 公众号的appsecret
        token: ${wx.token} # 接口配置里的Token值

假设我们已经搭建好了websocket,不会搭建的可以去我主页看另一篇文章👉 如何使用netty搭建websocket ,搭建完之后在我们的channelRead0方法中,这里我预设的逻辑为收到用户通过websocket发送消息即认为用户请求登录,可以根据不同业务需求编写不同逻辑。

	@Override
	protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        String text = textWebSocketFrame.text();
        Channel channel = channelHandlerContext.channel();
        // 生成随机码
        Integer code = new Random().nextInt(Integer.MAX_VALUE);
        // 找微信申请带参二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) Duration.ofHours(1).getSeconds());
        // 把二维码推送给前端
        channel.writeAndFlush(new TextWebSocketFrame(wxMpQrCodeTicket.getUrl()));
    }

这里有个需要注意的地方,我们的WebSocketHandler中使用的netty框架,不是由spring容器创建的,需要在初始连接时再从spring容器里获取注入的类:

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        wxMpService = SpringUtil.getBean(WxMpService.class);
        log.info("channel ==> {}",ctx.channel());
    }

使用测试工具测试:
在这里插入图片描述
发现可以正确的将二维码对应的网址传递给前端,我们这里可以在前端接入第三方工具来将其转化为二维码展示给用户。

接下来就是等待用户扫码,我们需要编写对用handler来实现对应逻辑,这里有很多handler可以自定义,例如:扫码事件handler、关注事件handler…我们这里只讲扫码事件。

扫码对应的handler在上面已给出。

用户扫码之后,微信会通过回调函数将用户信息传递给我们,代码中第一个接口是用于文章最开始提到的微信修改用的接口,第二个则是用户扫码后的回调接口

package com.wang.common.controller;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassDescription:
 * @Author:Wangzd
 * @Date: 2025/1/24
 **/
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/wx/portal/public")
public class WxPortalController {
    @Autowired
    private WxMpService wxMpService;
    private final WxMpService wxService;

    @GetMapping(produces = "text/plain;charset=utf-8")
    public String authGet(@RequestParam(name = "signature", required = false) String signature,
                          @RequestParam(name = "timestamp", required = false) String timestamp,
                          @RequestParam(name = "nonce", required = false) String nonce,
                          @RequestParam(name = "echostr", required = false) String echostr) {

        log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature,
                timestamp, nonce, echostr);
        if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
            throw new IllegalArgumentException("请求参数非法,请核实!");
        }


        if (wxService.checkSignature(timestamp, nonce, signature)) {
            return echostr;
        }

        return "非法请求";
    }

    @GetMapping("/callBack")
    public String callBack(@RequestParam String code) throws WxErrorException {
        WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
        WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, "zh_CN");
        // 登录逻辑...
        return "<h1>登录成功!<h1/>";
    }

}

我们将完整的代码完善后,扫描二维码后会给我们推送授权链接
在这里插入图片描述

点击链接后我们可以获取用户的openId,头像,和用户名称,可以用于第一次的用户信息保存,后续可以根据此来判断不同的用户。
在这里插入图片描述
到此用户登录成功

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐