引言

在当今移动互联网时代,微信已成为人们日常生活中不可或缺的一部分。利用微信公众号实现第三方网站的扫码登录,不仅能提供便捷的用户体验,还能有效减少用户注册的门槛。本文将详细介绍如何通过SpringBoot集成weixin-java-mp实现公众号扫码登录功能,帮助开发者快速掌握这一技术。

原理简介

不同于常见的OAuth2.0授权登录方式,本文介绍的是通过微信公众号的消息回调机制实现的扫码登录流程。其核心逻辑是:用户扫描网站上的临时二维码,公众号接收到关注或扫码事件后,通过预先配置的回调接口将用户信息传递给网站服务器,从而完成身份验证和登录过程。

技术栈

  • SpringBoot 2.6.x

  • weixin-java-mp 4.4.0

  • Redis (用于存储临时登录凭证)

  • MySQL (用户信息存储)

  • Thymeleaf (前端模板引擎)

实现流程

  1. 用户访问登录页面,后端生成临时登录标识并生成对应二维码

  2. 用户通过微信扫描二维码

  3. 微信公众号接收到扫码事件,将事件通过回调接口传递给服务端

  4. 服务端处理事件,将用户信息与临时登录标识关联

  5. 公众号向用户发送登录成功通知

  6. 前端页面通过轮询检测登录状态,获取用户信息并完成登录

详细实现步骤

1. 公众号配置

首先,需要在微信公众平台完成基本配置:

  • 登录微信公众平台,进入"开发 -> 基本配置"

  • 配置服务器地址(URL),用于接收微信的消息通知

  • 设置Token和EncodingAESKey,用于消息加解密

  • 开启"接收消息"和"网页授权"功能

2. 项目依赖配置

pom.xml中添加必要的依赖:

<!-- 微信Java SDK -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.4.0</version>
</dependency>

<!-- Redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- SpringBoot Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 二维码生成 -->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.4.1</version>
</dependency>

3. 微信公众号配置类

创建微信公众号配置类,用于初始化SDK:

package com.example.wechatlogin.config;

import lombok.Data;
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.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpConfig {
    
    // 公众号appId
    private String appId;
    
    // 公众号appSecret
    private String secret;
    
    // 公众号token
    private String token;
    
    // 消息加解密密钥
    private String aesKey;
    
    /**
     * 初始化微信公众号服务
     * @return WxMpService实例
     */
    @Bean
    public WxMpService wxMpService() {
        WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
        config.setAppId(appId);
        config.setSecret(secret);
        config.setToken(token);
        config.setAesKey(aesKey);
        
        WxMpService service = new WxMpServiceImpl();
        service.setWxMpConfigStorage(config);
        return service;
    }
}

4. 配置application.yml

server:
  port: 8080
  
spring:
  redis:
    host: localhost
    port: 6379
  datasource:
    url: jdbc:mysql://localhost:3306/wx_login?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false
    
wx:
  mp:
    app-id: 你的公众号appId
    secret: 你的公众号secret
    token: 你设置的token
    aes-key: 你的EncodingAESKey

5. 二维码生成服务

package com.example.wechatlogin.service;

import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class QrCodeService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private WxMpService wxMpService;
    
    private static final long EXPIRE_TIME = 300; // 二维码有效期5分钟
    
    /**
     * 生成微信公众号临时二维码
     * @return 包含二维码链接和临时标识的对象
     */
    public QrCodeInfo generateLoginQrCode() {
        try {
            // 生成临时登录标识
            String loginId = UUID.randomUUID().toString();
            
            // 生成二维码场景值,用于公众号识别
            String sceneStr = "login_" + loginId;
            
            // 将登录标识存入Redis,设置过期时间
            redisTemplate.opsForValue().set("qrcode:login:" + loginId, "WAITING", EXPIRE_TIME, TimeUnit.SECONDS);
            
            // 调用微信接口生成带参数的临时二维码
            WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTempTicket(sceneStr, (int)EXPIRE_TIME);
            
            // 获取二维码图片URL
            String qrcodeUrl = wxMpService.getQrcodeService().qrCodePictureUrl(ticket.getTicket());
            
            log.info("生成临时二维码 - loginId: {}, qrcodeUrl: {}", loginId, qrcodeUrl);
            
            QrCodeInfo qrCodeInfo = new QrCodeInfo();
            qrCodeInfo.setLoginId(loginId);
            qrCodeInfo.setQrCodeUrl(qrcodeUrl);
            qrCodeInfo.setExpireTime(EXPIRE_TIME);
            
            return qrCodeInfo;
        } catch (WxErrorException e) {
            log.error("生成微信二维码失败", e);
            throw new RuntimeException("生成微信二维码失败", e);
        }
    }
    
    /**
     * 二维码信息类
     */
    public static class QrCodeInfo {
        private String loginId;
        private String qrCodeUrl;
        private long expireTime;
        
        // getter和setter方法
        public String getLoginId() {
            return loginId;
        }
        
        public void setLoginId(String loginId) {
            this.loginId = loginId;
        }
        
        public String getQrCodeUrl() {
            return qrCodeUrl;
        }
        
        public void setQrCodeUrl(String qrCodeUrl) {
            this.qrCodeUrl = qrCodeUrl;
        }
        
        public long getExpireTime() {
            return expireTime;
        }
        
        public void setExpireTime(long expireTime) {
            this.expireTime = expireTime;
        }
    }
}

6. 微信消息处理器

package com.example.wechatlogin.handler;

import com.example.wechatlogin.service.UserService;
import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class ScanQrCodeHandler implements WxMpMessageHandler {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserService userService;

    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
                                   WxMpService wxMpService, WxSessionManager sessionManager) {
        // 处理扫码事件
        if (wxMessage.getMsgType().equals(XmlMsgType.EVENT)) {
            // 如果是扫码事件
            if ("SCAN".equals(wxMessage.getEvent()) || "subscribe".equals(wxMessage.getEvent())) {
                return handleScanEvent(wxMessage, wxMpService);
            }
        }
        
        // 默认返回空消息
        return null;
    }
    
    /**
     * 处理扫码事件
     * @param wxMessage 微信消息
     * @param wxMpService 微信服务
     * @return 回复消息
     */
    private WxMpXmlOutMessage handleScanEvent(WxMpXmlMessage wxMessage, WxMpService wxMpService) {
        // 获取事件KEY值,也就是二维码参数
        String eventKey = wxMessage.getEventKey();
        
        // 根据不同类型事件处理
        if ("subscribe".equals(wxMessage.getEvent())) {
            // 如果是关注事件,需要处理qrscene_前缀
            if (eventKey != null && eventKey.startsWith("qrscene_")) {
                eventKey = eventKey.substring(8);
            } else {
                // 普通关注,不是扫码关注
                return WxMpXmlOutMessage.TEXT().content("感谢关注!").fromUser(wxMessage.getToUser())
                        .toUser(wxMessage.getFromUser()).build();
            }
        }
        
        // 处理登录场景
        if (eventKey != null && eventKey.startsWith("login_")) {
            String loginId = eventKey.substring(6);
            String openId = wxMessage.getFromUser();
            
            // 处理登录逻辑
            handleLogin(loginId, openId);
            
            // 回复消息通知用户登录成功
            return WxMpXmlOutMessage.TEXT().content("登录成功,请返回网页继续操作!")
                    .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).build();
        }
        
        return null;
    }
    
    /**
     * 处理登录逻辑
     * @param loginId 登录标识
     * @param openId 用户openId
     */
    private void handleLogin(String loginId, String openId) {
        // 检查登录标识是否存在
        String key = "qrcode:login:" + loginId;
        Boolean exists = redisTemplate.hasKey(key);
        
        if (exists != null && exists) {
            // 获取用户信息并存储登录状态
            userService.saveOrUpdateUserByOpenId(openId);
            
            // 更新Redis中的登录状态
            redisTemplate.opsForValue().set(key, openId, 60, TimeUnit.SECONDS);
        }
    }
}

7. 微信消息路由配置

package com.example.wechatlogin.config;

import com.example.wechatlogin.handler.ScanQrCodeHandler;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WxMpMessageRouterConfig {

    @Autowired
    private ScanQrCodeHandler scanQrCodeHandler;
    
    @Bean
    public WxMpMessageRouter wxMpMessageRouter(WxMpService wxMpService) {
        final WxMpMessageRouter router = new WxMpMessageRouter(wxMpService);
        
        // 注册扫码事件处理器
        router.rule()
            .async(false)
            .msgType(WxConsts.XmlMsgType.EVENT)
            .event(WxConsts.EventType.SCAN)
            .handler(scanQrCodeHandler)
            .end();
            
        // 注册关注事件处理器
        router.rule()
            .async(false)
            .msgType(WxConsts.XmlMsgType.EVENT)
            .event(WxConsts.EventType.SUBSCRIBE)
            .handler(scanQrCodeHandler)
            .end();
            
        return router;
    }
}

8. 微信接口控制器

package com.example.wechatlogin.controller;

import lombok.extern.slf4j.Slf4j;
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@RestController
@RequestMapping("/wx/portal")
public class WxPortalController {

    @Autowired
    private WxMpService wxMpService;
    
    @Autowired
    private WxMpMessageRouter wxMpMessageRouter;
    
    /**
     * 处理微信服务器发来的验证请求
     */
    @GetMapping(produces = "text/plain;charset=utf-8")
    public String authGet(HttpServletRequest request) {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        
        log.info("收到微信验证请求 - signature: {}, timestamp: {}, nonce: {}, echostr: {}", 
                signature, timestamp, nonce, echostr);
        
        if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
            log.error("请求参数不完整");
            return "请求参数不完整";
        }
        
        if (wxMpService.checkSignature(timestamp, nonce, signature)) {
            log.info("微信验证成功");
            return echostr;
        }
        
        log.error("微信验证失败");
        return "非法请求";
    }
    
    /**
     * 处理微信服务器发来的消息
     */
    @PostMapping(produces = "application/xml; charset=UTF-8")
    public String post(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        
        log.info("收到微信消息 - signature: {}, timestamp: {}, nonce: {}", signature, timestamp, nonce);
        
        if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
            log.error("非法请求,可能属于伪造的请求");
            return "非法请求";
        }
        
        // 解析XML消息
        WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
        log.info("收到微信消息:{}", inMessage.toString());
        
        // 路由消息并获取响应
        WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
        
        // 有响应消息则返回
        return outMessage == null ? "" : outMessage.toXml();
    }
}

9. 登录控制器

package com.example.wechatlogin.controller;

import com.example.wechatlogin.service.QrCodeService;
import com.example.wechatlogin.service.QrCodeService.QrCodeInfo;
import com.example.wechatlogin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private QrCodeService qrCodeService;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserService userService;
    
    /**
     * 显示登录页面
     */
    @GetMapping
    public String loginPage(Model model) {
        // 生成公众号二维码
        QrCodeInfo qrCodeInfo = qrCodeService.generateLoginQrCode();
        
        // 将二维码信息传递给页面
        model.addAttribute("loginId", qrCodeInfo.getLoginId());
        model.addAttribute("qrCodeUrl", qrCodeInfo.getQrCodeUrl()); // 使用公众号返回的二维码URL
        model.addAttribute("expireTime", qrCodeInfo.getExpireTime());
        
        return "login";
    }
    
    /**
     * 检查登录状态
     */
    @GetMapping("/check")
    @ResponseBody
    public Map<String, Object> checkLoginStatus(@RequestParam String loginId, HttpSession session) {
        Map<String, Object> result = new HashMap<>();
        
        // 从Redis获取登录状态
        String key = "qrcode:login:" + loginId;
        String value = redisTemplate.opsForValue().get(key);
        
        if (value == null) {
            // 二维码已过期
            result.put("success", false);
            result.put("status", "expired");
            result.put("message", "二维码已过期,请刷新页面");
        } else if ("WAITING".equals(value)) {
            // 等待扫码
            result.put("success", false);
            result.put("status", "waiting");
            result.put("message", "等待扫码");
        } else {
            // 已登录,获取用户信息
            result.put("success", true);
            result.put("status", "logged");
            result.put("message", "登录成功");
            
            // 获取用户信息
            Map<String, Object> userInfo = userService.getUserInfoByOpenId(value);
            result.put("userInfo", userInfo);
            
            // 将用户信息存入Session
            session.setAttribute("user", userInfo);
            
            // 登录成功后删除Redis中的临时数据
            redisTemplate.delete(key);
        }
        
        return result;
    }
}

10. 用户服务

package com.example.wechatlogin.service;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserService {

    @Autowired
    private WxMpService wxMpService;
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 根据OpenID保存或更新用户信息
     * @param openId 用户OpenID
     * @return 用户信息
     */
    public Map<String, Object> saveOrUpdateUserByOpenId(String openId) {
        try {
            // 获取微信用户信息
            WxMpUser wxUser = wxMpService.getUserService().userInfo(openId);
            
            // 检查用户是否已存在
            String checkSql = "SELECT COUNT(*) FROM wx_user WHERE open_id = ?";
            Integer count = jdbcTemplate.queryForObject(checkSql, Integer.class, openId);
            
            if (count != null && count > 0) {
                // 更新用户信息
                String updateSql = "UPDATE wx_user SET nickname = ?, avatar = ?, update_time = NOW() WHERE open_id = ?";
                jdbcTemplate.update(updateSql, wxUser.getNickname(), wxUser.getHeadImgUrl(), openId);
            } else {
                // 插入新用户
                String insertSql = "INSERT INTO wx_user (open_id, nickname, avatar, create_time, update_time) VALUES (?, ?, ?, NOW(), NOW())";
                jdbcTemplate.update(insertSql, openId, wxUser.getNickname(), wxUser.getHeadImgUrl());
            }
            
            // 返回用户信息
            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("openId", wxUser.getOpenId());
            userInfo.put("nickname", wxUser.getNickname());
            userInfo.put("avatar", wxUser.getHeadImgUrl());
            
            return userInfo;
            
        } catch (WxErrorException e) {
            throw new RuntimeException("获取微信用户信息失败", e);
        }
    }
    
    /**
     * 根据OpenID获取用户信息
     * @param openId 用户OpenID
     * @return 用户信息
     */
    public Map<String, Object> getUserInfoByOpenId(String openId) {
        String sql = "SELECT open_id, nickname, avatar FROM wx_user WHERE open_id = ?";
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, openId);
        
        if (results.isEmpty()) {
            return saveOrUpdateUserByOpenId(openId);
        }
        
        return results.get(0);
    }
}

11. 前端登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>微信扫码登录</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f5f5f5;
        }
        .login-container {
            text-align: center;
            background-color: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            width: 320px;
        }
        .qrcode-container {
            margin: 20px 0;
        }
        .qrcode-img {
            width: 200px;
            height: 200px;
        }
        .status-text {
            color: #666;
            margin: 10px 0;
        }
        .countdown {
            color: #999;
            font-size: 14px;
        }
        .success-info {
            display: none;
        }
        .avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            margin: 10px auto;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>微信扫码登录</h2>
        
        <div class="qrcode-container">
            <!-- 使用微信公众号返回的二维码图片URL -->
            <img th:src="${qrCodeUrl}" class="qrcode-img" alt="微信扫码登录">
            <p class="status-text">请使用微信扫描二维码登录</p>
            <p class="countdown">二维码有效期: <span id="countdown">300</span>秒</p>
        </div>
        
        <div class="success-info">
            <img src="" class="avatar" id="userAvatar" alt="用户头像">
            <p>欢迎, <span id="userNickname"></span></p>
            <p>登录成功,即将跳转...</p>
        </div>
    </div>
    
    <script th:inline="javascript">
        // 获取登录ID
        const loginId = [[${loginId}]];
        const expireTime = [[${expireTime}]];
        
        let countdownTimer;
        let checkStatusTimer;
        let countdown = expireTime;
        
        // 启动倒计时
        function startCountdown() {
            const countdownEl = document.getElementById('countdown');
            countdownTimer = setInterval(() => {
                countdown--;
                countdownEl.textContent = countdown;
                
                if (countdown <= 0) {
                    clearInterval(countdownTimer);
                    document.querySelector('.status-text').textContent = '二维码已过期,请刷新页面';
                    stopCheckingStatus();
                }
            }, 1000);
        }
        
        // 开始检查登录状态
        function startCheckingStatus() {
            checkStatusTimer = setInterval(() => {
                fetch(`/login/check?loginId=${loginId}`)
                    .then(response => response.json())
                    .then(data => {
                        if (data.status === 'expired') {
                            document.querySelector('.status-text').textContent = '二维码已过期,请刷新页面';
                            stopCheckingStatus();
                        } else if (data.status === 'waiting') {
                            document.querySelector('.status-text').textContent = '等待扫码';
                        } else if (data.status === 'logged') {
                            // 登录成功
                            handleLoginSuccess(data.userInfo);
                        }
                    })
                    .catch(error => {
                        console.error('检查登录状态出错:', error);
                    });
            }, 2000);
        }
        
        // 停止检查登录状态
        function stopCheckingStatus() {
            if (checkStatusTimer) {
                clearInterval(checkStatusTimer);
            }
            if (countdownTimer) {
                clearInterval(countdownTimer);
            }
        }
        
        // 处理登录成功
        function handleLoginSuccess(userInfo) {
            stopCheckingStatus();
            
            // 显示用户信息
            document.querySelector('.qrcode-container').style.display = 'none';
            document.querySelector('.success-info').style.display = 'block';
            
            document.getElementById('userNickname').textContent = userInfo.nickname;
            document.getElementById('userAvatar').src = userInfo.avatar;
            
            // 3秒后跳转到首页
            setTimeout(() => {
                window.location.href = '/';
            }, 3000);
        }
        
        // 页面加载后启动
        window.addEventListener('load', () => {
            startCountdown();
            startCheckingStatus();
        });
    </script>
</body>
</html>

12. 数据库表结构

CREATE DATABASE IF NOT EXISTS wx_login;

USE wx_login;

CREATE TABLE IF NOT EXISTS wx_user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    open_id VARCHAR(64) NOT NULL COMMENT '用户OpenID',
    nickname VARCHAR(128) COMMENT '用户昵称',
    avatar VARCHAR(512) COMMENT '用户头像URL',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '更新时间',
    UNIQUE KEY uk_open_id (open_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表';

总结

本文详细介绍了如何通过SpringBoot集成weixin-java-mp实现微信公众号扫码登录功能。与传统的OAuth2.0授权方式不同,这种实现方式通过微信公众号的消息回调机制,实现了更灵活的扫码登录流程。开发者可以根据实际需求,对代码进行适当调整和优化,以满足不同场景下的使用需求。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐