SpringBoot 整合微信开放平台网站应用扫码登录 无需关注直接扫码授权登录 (可内签到自己网站二维码 可点击跳转打开二维码) 全部兼容
SpringBoot 整合微信开放平台网站应用扫码登录(精简版)
一、前言
本文基于 微信开放平台 网站应用扫码登录 能力,实现后端核心逻辑,仅保留获取授权链接、授权回调两个核心接口,扫码授权后自动完成系统账号创建/登录、生成业务 Token,适配 SpringBoot + Redis 。
适用场景:后台系统、PC 网站微信扫码快捷登录,无需前端轮询状态,回调接口直接完成登录逻辑。
二、前置准备
1. 微信开放平台配置
- 登录微信开放平台,创建网站应用
- 拿到
AppID、AppSecret - 配置授权回调域名/地址:填写域名即可
2. 项目依赖
已基于现有项目技术栈,核心依赖说明:
- SpringBoot、SpringWeb
- Redis(RedisUtils 工具类)
- Jackson(JSON 序列化)
- RestTemplate(调用微信接口)
- 自定义 DTO、工具类、Mapper/Service
3. 自定义实体/DTO
3.1 微信Token返回DTO WxOAuth2TokenResponse
import lombok.Data;
@Data
public class WxOAuth2TokenResponse {
private String access_token;
private String openid;
private String unionid;
private Integer expires_in;
private String errmsg;
private Integer errcode;
}
3.2 微信用户信息DTO WxUserInfoResponse
import lombok.Data;
@Data
public class WxUserInfoResponse {
private String openid;
private String unionid;
private String nickname;
private Integer sex; // 1-男 2-女 0-未知
private String province;
private String city;
private String country;
private String headimgurl;
private String errmsg;
private Integer errcode;
}
三、完整后端代码(仅保留核心两个接口)
3.1 控制器代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* 微信开放平台网站应用扫码登录 精简版
* 仅保留:获取授权链接 + 授权回调 两个核心接口
*/
@RestController
@RequestMapping("wx/open")
public class WeiXinController {
// ========== 微信开放平台配置项(务必修改为自己平台信息) ==========
/** 网站应用 AppID */
private static final String APP_ID = "xxxxxxxxxxxxxxxxxx";
/** 网站应用 AppSecret */
private static final String APP_SECRET = "xxxxxxxxx";
/** 授权回调地址(开放平台必须配置一致) */
private static final String REDIRECT_URL = "https://www.xxx.com/api-web/wx/open/callback";
/** 微信获取access_token接口 */
private static final String WX_OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
/** 微信获取用户信息接口 */
private static final String WX_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";
/** 二维码/临时数据过期时间 10分钟 */
private static final long QR_EXPIRE_SECONDS = 600L;
// ========== 业务默认机构配置(根据自身业务修改 这里我用到登录的一些信息要直接插入进去) ==========
private static final Long DEFAULT_SEARCH_STATION_ID = 59L;
private static final Integer DEFAULT_INSTITUTION_ID = 19737;
private static final Long DEFAULT_CLIENT_ID = 19737L;
// ========== 依赖注入 ==========
@Autowired
private RedisUtils redisUtils;
@Autowired
private SysUserService sysUserService;
@Autowired
private ProductToken productToken;
@Autowired
private cn.net.xxx.dao.system.SysUserMapper sysUserMapper;
@Autowired
private cn.net.xxx.dao.system.SysRoleMapper sysRoleDao;
@Autowired
private cn.net.xxx.dao.web.LibNewSearchStationMapper libNewSearchStationMapper;
@Value("${redisToken.outTimes}")
private int redisTokenOutTimes;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 初始化RestTemplate,统一编码为UTF-8,避免微信中文乱码
*/
public WeiXinController() {
RestTemplate rt = new RestTemplate();
rt.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
this.restTemplate = rt;
}
// ===================== 接口1:获取微信授权链接(生成二维码用) =====================
/**
* @api {GET} /wx/open/getQrCodeUrl 获取微信扫码授权链接
* @apiDescription 前端拿到 authorizeUrl 后生成二维码,用户扫码跳转微信授权页
* @return 授权链接 + 随机state(防CSRF)
*/
@GetMapping("getQrCodeUrl")
public JsonResult getQrCodeUrl() {
JsonResult jsonResult = new JsonResult();
try {
// 1. 生成随机state,防止CSRF攻击
String state = UUID.randomUUID().toString().replaceAll("-", "");
// 2. state存入Redis,设置过期时间
redisUtils.set("wx:login:state:" + state, "0", QR_EXPIRE_SECONDS);
// 3. 编码回调地址(微信要求URL编码)
String encodedRedirectUri = URLEncoder.encode(REDIRECT_URL, StandardCharsets.UTF_8.name());
// 4. 拼接微信扫码登录授权链接 scope=snsapi_login 固定为网站应用扫码
String authorizeUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=" + APP_ID +
"&redirect_uri=" + encodedRedirectUri +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=" + state +
"#wechat_redirect";
Map<String, Object> data = new HashMap<>();
data.put("authorizeUrl", authorizeUrl);
data.put("state", state);
jsonResult.setResultCode(200);
jsonResult.setData(data);
jsonResult.setMessage("获取授权链接成功");
} catch (Exception e) {
e.printStackTrace();
jsonResult.setResultCode(-1);
jsonResult.setMessage("获取授权链接失败:" + e.getMessage());
}
return jsonResult;
}
// ===================== 接口2:微信授权回调(核心登录逻辑) =====================
/**
* @api {GET} /wx/open/callback 微信授权回调接口
* @apiDescription 用户扫码并同意授权后,微信自动跳转该地址,携带code、state
* 逻辑:获取token -> 获取微信用户信息 -> 自动注册/查询系统用户 -> 生成登录Token
*/
@GetMapping("callback")
public JsonResult callback(@RequestParam String code, @RequestParam String state) {
JsonResult jsonResult = new JsonResult();
String redisKey = "wx:login:state:" + state;
try {
// 标记状态:已扫码
redisUtils.set(redisKey, "1", QR_EXPIRE_SECONDS);
// 1. 根据code换取微信access_token
String tokenUrl = WX_OAUTH2_ACCESS_TOKEN_URL +
"?appid=" + APP_ID +
"&secret=" + APP_SECRET +
"&code=" + code +
"&grant_type=authorization_code";
String tokenStr = restTemplate.getForObject(tokenUrl, String.class);
WxOAuth2TokenResponse tokenResult = objectMapper.readValue(tokenStr, WxOAuth2TokenResponse);
if (tokenResult == null || tokenResult.getAccess_token() == null) {
redisUtils.set(redisKey, "-1", 300L);
jsonResult.setResultCode(-1);
jsonResult.setMessage("微信授权失败:" + (tokenResult != null ? tokenResult.getErrmsg() : "获取Token为空"));
return jsonResult;
}
// 2. 根据access_token + openid 获取微信用户详细信息
String userInfoUrl = WX_USER_INFO_URL +
"?access_token=" + tokenResult.getAccess_token() +
"&openid=" + tokenResult.getOpenid();
String userStr = restTemplate.getForObject(userInfoUrl, String.class);
WxUserInfoResponse userInfo = objectMapper.readValue(userStr, WxUserInfoResponse);
if (userInfo == null || userInfo.getUnionid() == null) {
redisUtils.set(redisKey, "-1", 300L);
jsonResult.setResultCode(-1);
jsonResult.setMessage("获取微信用户信息失败");
return jsonResult;
}
// 3. 以微信unionid作为账号前缀,查询系统用户
String wxUsername = "wx_" + userInfo.getUnionid();
SysUser sysUser = sysUserService.getUserByUnionId(wxUsername);
// 4. 新用户:自动注册账号
if (sysUser == null) {
sysUser = new SysUser();
sysUser.setUsername(wxUsername);
sysUser.setCreateTime(new Date());
// Shiro 双重SHA256加密密码
String pwdHash = new Sha256Hash(new Sha256Hash(wxUsername).toHex()).toHex();
sysUser.setPassword(pwdHash);
sysUser.setFullName(userInfo.getNickname());
// 性别转换:微信 1男/2女/0未知 → 系统 1男/0女
if (userInfo.getSex() != null) {
if (userInfo.getSex() == 1) {
sysUser.setSex(1L);
} else if (userInfo.getSex() == 2) {
sysUser.setSex(0L);
} else {
sysUser.setSex(1L);
}
}
sysUser.setEmail("");
sysUser.setMobile("");
sysUser.setStatus((byte) 1);
sysUser.setSysType(2);
sysUser.setUserType(0);
sysUser.setInstitutionId(DEFAULT_INSTITUTION_ID);
sysUser.setClientId(DEFAULT_CLIENT_ID);
sysUser.setSearchStationId(DEFAULT_SEARCH_STATION_ID);
// 执行注册
int userId = sysUserService.registered(sysUser);
sysUser.setUserId(Long.valueOf(userId));
// 重新查询完整用户信息
sysUser = sysUserMapper.selectByPrimaryKey(Long.valueOf(userId));
}
// 5. 生成系统登录Token
String randomCode = UUID.randomUUID().toString().replaceAll("-", "");
Map<String, Object> tokenMap = productToken.productToken(sysUser.getUserId().toString(), randomCode);
String loginToken = tokenMap.get("token").toString();
// 清空密码、更新登录时间
sysUser.setPassword("");
sysUser.setLoginTime(new Date());
// 6. 查询用户角色
List<SysRoleEntity> roleList = sysRoleDao.getRoleByUserId(sysUser.getUserId());
if (roleList != null && !roleList.isEmpty()) {
SysRoleEntity role = roleList.get(0);
sysUser.setRoleId(role.getRoleId().intValue());
sysUser.setUserType(role.getType());
}
// 7. 补充其他信息
if (sysUser.getUserType() != 1 && sysUser.getSearchStationId() != null) {
LibNewSearchStation station = sysUserMapper.getInstitutionById(sysUser.getSearchStationId().intValue());
if (station != null) {
sysUser.setInstitutionName(station.getName());
}
}
// 8. 数据存入Redis,设置过期时间
redisUtils.set(loginToken, sysUser, (long) redisTokenOutTimes);
redisUtils.set("wx:login:token:" + state, loginToken, QR_EXPIRE_SECONDS);
redisUtils.set(redisKey, "2", 300L);
// 9. 组装返回结果,前端直接使用token登录系统
Map<String, Object> resultData = new HashMap<>();
resultData.put("token", loginToken);
resultData.put("user", sysUser);
// 补充配置
LibNewSearchStation station = libNewSearchStationMapper.getById(DEFAULT_SEARCH_STATION_ID);
resultData.put("workType", (station != null && station.getWorkType() != null) ? station.getWorkType() : "1");
jsonResult.setResultCode(200);
jsonResult.setData(resultData);
jsonResult.setMessage("微信扫码登录成功");
} catch (Exception e) {
e.printStackTrace();
redisUtils.set(redisKey, "-1", 300L);
jsonResult.setResultCode(-1);
jsonResult.setMessage("微信登录异常:" + e.getMessage());
}
return jsonResult;
}
}
四、接口调用流程
1. 整体流程
- 前端调用
getQrCodeUrl→ 获取authorizeUrl和state - 前端将
authorizeUrl生成二维码,用户微信扫码 - 用户在微信中同意授权 → 微信自动跳转到后端
callback接口,携带code和state - 后端
callback完成:微信接口调用 → 用户查询/自动注册 → 生成业务 Token - 后端返回
token+ 用户信息,前端拿着token完成系统登录
2. 接口请求示例
(1)获取授权链接
- 请求地址:
GET /wx/open/getQrCodeUrl - 返回示例:
{
"resultCode": 200,
"message": "获取授权链接成功",
"data": {
"authorizeUrl": "https://open.weixin.qq.com/connect/qrconnect?xxx",
"state": "xxxxxxxxx"
}
}
(2)授权回调(微信自动调用)
- 请求地址:
GET /wx/open/callback?code=xxx&state=xxx - 登录成功返回示例:
{
"resultCode": 200,
"message": "微信扫码登录成功",
"data": {
"token": "系统登录token",
"user": {},
"workType": "1"
}
}
五、关键要点与踩坑总结
-
回调地址严格匹配
微信开放平台配置的回调域名/地址,必须和代码中REDIRECT_URL的域名完全一致。 -
URL编码问题
回调地址必须使用URLEncoder编码,否则微信授权链接报错。 -
scope固定值
网站应用扫码登录必须使用scope=snsapi_login,和公众号授权snsapi_userinfo区分开。 -
Code 一次性有效
微信返回的code有效期 5 分钟,且只能使用一次,重复调用会报错。 -
UnionID 作为唯一标识
使用unionid而非openid,同一微信账号在同一开放平台下所有应用unionid唯一。 -
Redis 过期策略
state、临时 token 均设置过期时间,防止 Redis 数据堆积,同时避免二维码长期有效。 -
编码乱码
手动初始化RestTemplate,强制使用UTF-8,解决微信返回昵称、中文乱码问题。
六、前端对接简单说明
- 调用
/wx/open/getQrCodeUrl拿到authorizeUrl,使用二维码组件渲染二维码。 - 用户扫码授权后,页面会跳转到
callback接口,接口直接返回登录token。 - 前端拿到
token后,走原有登录逻辑(如存入本地、请求登录校验接口)即可。
上面的内容为前端点击微信跳转页面打开让用户扫码 下面为 直接内嵌到当前网站里面扫码登录
微信网站应用扫码登录 前端集成教程(HTML原生 + Vue 双版本)
结合上一篇后端接口,本文讲解原生HTML页面、Vue项目两种场景接入微信扫码登录,包含代码说明、配置、样式、踩坑点,配套你现有的后端接口 + 前端扫码轮询逻辑。
技术栈说明
后端接口:getQrCodeUrl(获取授权链接+state)、checkScan(轮询扫码状态)、callback(微信回调)
前端依赖:微信官方wxLogin.js生成扫码弹窗/二维码,fetch请求后端接口,定时器轮询状态
一、整体业务流程回顾
- 前端加载微信官方 JS,调用后端
/getQrCodeUrl获取state - 初始化微信扫码组件,渲染登录二维码
- 开启定时器轮询
/checkScan接口,监听扫码、授权状态 - 用户微信扫码并授权 → 微信跳转到后端
callback接口完成登录逻辑 - 轮询检测到
token后,本地存储 Token,跳转主页完成登录
二、通用前置配置(必看)
2.1 微信开放平台配置(全局统一)
- 登录微信开放平台 → 网站应用
- 授权回调地址:填写
www.xxx.com只需要填写域名即可 - 配置授权域名:
www.xxx.com - 确认应用已开通
snsapi_login网站扫码登录权限
2.2 接口地址说明
- 获取二维码&state:
/api-web/wx/open/getQrCodeUrl - 轮询扫码状态:
/api-web/wx/open/checkScan?state=xxx - 微信回调地址:
/api-web/wx/open/callback
2.3 跨域说明
后端需配置 CORS跨域,允许前端域名请求接口,否则 fetch 会请求失败。
三、版本一:原生 HTML 页面接入(独立页面/内嵌页面)
适合传统多页面网站、静态页面、内嵌弹窗场景,直接复制可用。
3.1 完整代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>微信扫码登录</title>
<style>
/* 页面基础样式 */
body {
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.login-box {
text-align: center;
margin-top: 100px;
}
.title {
font-size: 20px;
color: #333;
margin-bottom: 30px;
}
</style>
</head>
<body>
<div class="login-box">
<div class="title">请使用微信扫码登录</div>
<!-- 微信二维码容器ID:wx_qr_console_login -->
<div id="wx_qr_console_login"></div>
</div>
<script>
(function(){
// ==================== 【核心配置项 - 请根据实际环境修改】 ====================
const APPID = "xxxxxxxxxxxxxxxxxxxx";
// 微信回调地址
const REDIRECT_URI = "https://www.xxx.com/api-web/wx/open/callback";
// 接口根路径,本地/同域留空,跨域填写完整域名
const BASE_URL = "";
// 轮询间隔 单位:毫秒
const POLL_INTERVAL = 1500;
// 二维码容器ID
const containerId = "wx_qr_console_login";
// =========================================================================
let state = "";
let pollTimer = null;
/**
* 1. 加载微信官方扫码JS
*/
function loadWxScript() {
return new Promise((resolve, reject) => {
// 已加载则直接返回
if(window.WxLogin) return resolve();
const script = document.createElement("script");
script.src = "https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
script.onload = resolve;
script.onerror = () => reject("微信登录脚本加载失败,请刷新重试");
document.head.appendChild(script);
});
}
/**
* 2. 请求后端接口,获取 state
*/
async function getState() {
const url = `${BASE_URL}/api-web/wx/open/getQrCodeUrl`;
const response = await fetch(url);
const res = await response.json();
if(res.resultCode !== 200) {
throw new Error(res.message || "获取二维码信息失败");
}
state = res.data.state;
return state;
}
/**
* 3. 轮询扫码状态
*/
function startPoll() {
pollTimer = setInterval(async () => {
try {
const url = `${BASE_URL}/api-web/wx/open/checkScan?state=${state}`;
const response = await fetch(url);
const res = await response.json();
// 二维码过期
if(res.data?.status === -1) {
clearInterval(pollTimer);
alert("二维码已过期,请刷新页面重新扫码");
return;
}
// 登录成功:后端返回 token
if(res.data?.token) {
clearInterval(pollTimer);
// 存储登录令牌
localStorage.setItem("token", res.data.token);
localStorage.setItem("userInfo", JSON.stringify(res.data.user));
alert("登录成功,正在跳转首页");
// 跳转首页,可修改为自己业务地址
window.location.href = "/";
}
} catch (err) {
console.error("轮询接口异常:", err);
}
}, POLL_INTERVAL);
}
/**
* 4. 初始化微信二维码
*/
function initWxQr() {
new window.WxLogin({
id: containerId,
appid: APPID,
scope: "snsapi_login", // 固定:网站应用扫码登录
redirect_uri: encodeURIComponent(REDIRECT_URI),
state: state,
style: "black", // 二维码样式 black / white
self_redirect: false
});
}
/**
* 主入口函数
*/
async function run() {
try {
await loadWxScript();
await getState();
initWxQr();
startPoll();
console.log("微信二维码加载完成,请扫码");
} catch (e) {
console.error("登录初始化失败:", e.message);
alert(e.message);
}
}
// 启动登录逻辑
run();
// 页面销毁时清除定时器(防止内存泄漏)
window.addEventListener("beforeunload", () => {
if(pollTimer) clearInterval(pollTimer);
});
})();
</script>
</body>
</html>
3.2 使用说明
- 修改顶部配置项:
APPID、REDIRECT_URI保持和你业务一致 BASE_URL:- 前端和后端同域名:留空
- 前后端分离跨域:填写后端域名
https://www.xxx.com
- 页面打开自动加载二维码,微信扫码即可登录
- 二维码过期会弹窗提示,刷新页面重新加载
3.3 弹窗版(浮窗二维码)
如果需要居中悬浮二维码(不占用页面布局),替换容器样式即可:
#wx_qr_console_login {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: #fff;
box-shadow: 0 0 20px #ccc;
z-index: 99999;
}
四、版本二:Vue 项目接入(Vue2 / Vue3 通用)
分为两种场景:独立登录页、弹窗登录,基于 Vue + fetch/axios 实现,推荐使用 axios 统一管理请求。
4.1 前置准备
- 项目已安装
axios(前后端分离必备)npm install axios --save - 全局配置后端接口地址(
axios基础地址) - 路由/页面提前规划好登录页
4.2 Vue 组件完整代码(单文件组件 .vue)
方式1:独立登录页面(Vue2/Vue3 通用)
<template>
<div class="login-container">
<div class="login-title">微信扫码登录</div>
<!-- 微信二维码容器 -->
<div id="wx_qr_console_login"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "WxLogin",
data() {
return {
// 核心配置
APPID: "xxxxxxxxxxxxxxxxxxxx",
REDIRECT_URI: "https://www.xxx.com/api-web/wx/open/callback",
POLL_INTERVAL: 1500,
containerId: "wx_qr_console_login",
state: "",
pollTimer: null
}
},
mounted() {
// 页面挂载后执行登录逻辑
this.initLogin();
},
beforeDestroy() {
// 组件销毁,清除定时器,防止内存泄漏
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
},
methods: {
/**
* 加载微信官方JS
*/
loadWxScript() {
return new Promise((resolve, reject) => {
if (window.WxLogin) return resolve();
const script = document.createElement("script");
script.src = "https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
script.onload = resolve;
script.onerror = () => reject("微信脚本加载失败");
document.head.appendChild(script);
})
},
/**
* 获取后端 state
*/
async getState() {
const res = await axios.get("/api-web/wx/open/getQrCodeUrl");
if (res.data.resultCode !== 200) {
throw new Error(res.data.message);
}
this.state = res.data.data.state;
},
/**
* 初始化微信二维码
*/
initWxQr() {
new window.WxLogin({
id: this.containerId,
appid: this.APPID,
scope: "snsapi_login",
redirect_uri: encodeURIComponent(this.REDIRECT_URI),
state: this.state,
style: "black",
self_redirect: false
})
},
/**
* 轮询扫码状态
*/
startPoll() {
this.pollTimer = setInterval(async () => {
try {
const res = await axios.get(`/api-web/wx/open/checkScan`, {
params: { state: this.state }
})
const data = res.data.data;
// 二维码过期
if (data.status === -1) {
clearInterval(this.pollTimer);
this.$message.warning("二维码已过期,请刷新页面");
return;
}
// 登录成功
if (data.token) {
clearInterval(this.pollTimer);
// 存储token(根据你的项目改造:Vuex / localStorage / Pinia)
localStorage.setItem("token", data.token);
localStorage.setItem("userInfo", JSON.stringify(data.user));
this.$message.success("登录成功");
// 跳转首页
this.$router.push("/");
}
} catch (err) {
console.error("轮询异常:", err);
}
}, this.POLL_INTERVAL)
},
/**
* 入口方法
*/
async initLogin() {
try {
await this.loadWxScript();
await this.getState();
this.initWxQr();
this.startPoll();
} catch (e) {
this.$message.error(e.message || "登录初始化失败");
}
}
}
}
</script>
<style scoped>
.login-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f6f6f6;
}
.login-title {
font-size: 22px;
color: #333;
margin-bottom: 30px;
}
</style>
方式2:弹窗形式(点击按钮弹出扫码浮窗)
在原有页面增加按钮,点击弹出居中二维码,核心逻辑不变,仅增加显示/隐藏控制:
<template>
<div class="page">
<button @click="openWxLogin">微信扫码登录</button>
<!-- 二维码浮窗 -->
<div v-if="showQr" id="wx_qr_console_login" class="qr-box"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
showQr: false,
APPID: "xxxxxxxxxxxxxxxxxxxx",
REDIRECT_URI: "https://www.xxxxx.com/api-web/wx/open/callback",
POLL_INTERVAL: 1500,
containerId: "wx_qr_console_login",
state: "",
pollTimer: null
}
},
beforeDestroy() {
if (this.pollTimer) clearInterval(this.pollTimer);
},
methods: {
// 打开扫码弹窗
async openWxLogin() {
this.showQr = true;
try {
await this.loadWxScript();
await this.getState();
this.initWxQr();
this.startPoll();
} catch (e) {
this.$message.error(e.message);
this.showQr = false;
}
},
// 下方方法和上面【独立登录页】完全一致,直接复制
loadWxScript(){/* 省略,同上 */},
getState(){/* 省略,同上 */},
initWxQr(){/* 省略,同上 */},
startPoll(){/* 省略,同上 */}
}
}
</script>
<style scoped>
.qr-box {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: #fff;
box-shadow: 0 0 20px #999;
z-index: 9999;
}
</style>
4.3 Vue 项目关键改造点
- Token 存储
可根据项目架构替换:- 简单项目:
localStorage - 中大型项目:
Vuex / Pinia全局状态管理
- 简单项目:
- UI 提示
示例使用element-ui的$message,无UI库可替换为原生alert
五、通用踩坑 & 问题排查
5.1 二维码无法显示
- 检查网络是否能加载微信JS:
https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js - 容器
id必须和代码中containerId完全一致 - 页面存在
z-index遮挡,提高二维码层级
5.2 微信扫码后跳转空白/回调失败
- 最高发问题:
REDIRECT_URI和 微信开放平台配置不一致(协议、域名、路径必须一字不差) - 回调地址必须是 HTTPS(微信网站应用强制要求)
- 后端接口报错:查看后端日志,检查
AppID / AppSecret是否正确
5.3 轮询一直等待,不会登录
- 后端
checkScan接口逻辑异常,打印接口返回数据排查 state在 Redis 过期,延长后端QR_EXPIRE_SECONDS- 微信
code只能使用一次,重复扫码会失效
5.4 中文乱码
后端已对 RestTemplate 设置 UTF-8,前端无需额外处理;若仍乱码,检查页面 <meta charset="UTF-8">。
5.5 跨域报错
- 后端添加 CORS 配置,放行前端域名
- 开发环境可使用代理(Vue
vue.config.js配置代理)
六、部署上线注意事项
- 上线前确认:微信开放平台
AppID、回调地址、授权域名全部配置完成 - 后端密钥
AppSecret禁止前端暴露,全部保留在后端 - 定时清除轮询定时器,避免页面常驻定时器造成性能问题
- 二维码过期时间、Token 有效期根据业务调整后端 Redis 配置
更多推荐


所有评论(0)