前言
微信扫码登录是目前非常流行的第三方登录方式,本文将详细介绍如何在Vue前端和Java后端中实现微信扫码登录功能。
准备工作
在开始之前,你需要:
- 注册微信开放平台账号
- 创建网站应用并获取以下信息:
- AppID
- AppSecret
- 设置授权回调域名
技术栈
- 前端:Vue 3 + Vite + Axios
- 后端:Spring Boot 2.7.x + Java 8+
- 数据库:MySQL
项目结构
├── frontend/ # Vue前端项目
│ ├── src/
│ │ ├── components/
│ │ │ └── WxLogin.vue # 微信登录组件
│ │ ├── api/
│ │ │ └── auth.js # API请求
│ │ └── store/
│ │ └── user.js # 用户状态管理
│ └── package.json
│
└── backend/ # Spring Boot后端项目
├── src/main/java/
│ └── com/example/demo/
│ ├── controller/
│ │ └── WxAuthController.java
│ ├── service/
│ │ └── WxAuthService.java
│ ├── model/
│ │ └── WxUserInfo.java
│ └── config/
│ └── WxConfig.java
└── pom.xml
前端实现
1. 创建微信登录组件 (WxLogin.vue)
<template>
<div class="wx-login-container">
<div class="qrcode-wrapper">
<div id="wx-qrcode"></div>
<div v-if="scanStatus === 'waiting'" class="status-text">
请使用微信扫描二维码登录
</div>
<div v-else-if="scanStatus === 'scanned'" class="status-text">
扫描成功,请在手机上确认
</div>
<div v-else-if="scanStatus === 'expired'" class="status-text">
二维码已过期,点击刷新
<button @click="refreshQrCode">刷新</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getQrCode, checkLoginStatus } from '@/api/auth'
const scanStatus = ref('waiting')
let checkTimer = null
const initQrCode = async () => {
try {
const { data } = await getQrCode()
// 渲染二维码
new QRCode(document.getElementById('wx-qrcode'), {
text: data.qrCodeUrl,
width: 200,
height: 200
})
// 开始轮询检查扫码状态
startCheckingStatus(data.state)
} catch (error) {
console.error('获取二维码失败:', error)
}
}
const startCheckingStatus = (state) => {
checkTimer = setInterval(async () => {
try {
const { data } = await checkLoginStatus(state)
scanStatus.value = data.status
if (data.status === 'success') {
clearInterval(checkTimer)
// 登录成功,存储token并跳转
localStorage.setItem('token', data.token)
router.push('/dashboard')
} else if (data.status === 'expired') {
clearInterval(checkTimer)
}
} catch (error) {
console.error('检查状态失败:', error)
}
}, 2000)
}
const refreshQrCode = () => {
document.getElementById('wx-qrcode').innerHTML = ''
scanStatus.value = 'waiting'
initQrCode()
}
onMounted(() => {
initQrCode()
})
onUnmounted(() => {
if (checkTimer) {
clearInterval(checkTimer)
}
})
</script>
<style scoped>
.wx-login-container {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
}
.qrcode-wrapper {
text-align: center;
}
.status-text {
margin-top: 20px;
color: #666;
}
button {
margin-left: 10px;
padding: 4px 12px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
</style>
2. API请求封装 (auth.js)
import axios from 'axios'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL
})
export const getQrCode = () => {
return api.get('/api/wx/qrcode')
}
export const checkLoginStatus = (state) => {
return api.get(`/api/wx/check-status?state=${state}`)
}
后端实现
1. 配置文件 (application.yml)
wx:
app-id: your_app_id
app-secret: your_app_secret
redirect-uri: http://your-domain.com/api/wx/callback
2. 微信配置类 (WxConfig.java)
@Configuration
@ConfigurationProperties(prefix = "wx")
@Data
public class WxConfig {
private String appId;
private String appSecret;
private String redirectUri;
}
3. 控制器 (WxAuthController.java)
@RestController
@RequestMapping("/api/wx")
@RequiredArgsConstructor
public class WxAuthController {
private final WxAuthService wxAuthService;
@GetMapping("/qrcode")
public ResponseEntity<?> getQrCode() {
try {
Map<String, String> qrCodeInfo = wxAuthService.generateQrCode();
return ResponseEntity.ok(qrCodeInfo);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("获取二维码失败");
}
}
@GetMapping("/check-status")
public ResponseEntity<?> checkStatus(@RequestParam String state) {
try {
Map<String, Object> status = wxAuthService.checkLoginStatus(state);
return ResponseEntity.ok(status);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("检查状态失败");
}
}
@GetMapping("/callback")
public void callback(@RequestParam String code, @RequestParam String state) {
wxAuthService.handleCallback(code, state);
}
}
4. 服务层 (WxAuthService.java)
@Service
@RequiredArgsConstructor
public class WxAuthService {
private final WxConfig wxConfig;
private final RedisTemplate<String, String> redisTemplate;
public Map<String, String> generateQrCode() {
String state = UUID.randomUUID().toString();
String qrCodeUrl = String.format(
"https://open.weixin.qq.com/connect/qrconnect?" +
"appid=%s&redirect_uri=%s&response_type=code&" +
"scope=snsapi_login&state=%s#wechat_redirect",
wxConfig.getAppId(),
URLEncoder.encode(wxConfig.getRedirectUri(), StandardCharsets.UTF_8),
state
);
// 存储state到Redis,设置过期时间
redisTemplate.opsForValue().set(
"wx:qrcode:" + state,
"WAITING",
5,
TimeUnit.MINUTES
);
Map<String, String> result = new HashMap<>();
result.put("qrCodeUrl", qrCodeUrl);
result.put("state", state);
return result;
}
public Map<String, Object> checkLoginStatus(String state) {
String key = "wx:qrcode:" + state;
String status = redisTemplate.opsForValue().get(key);
Map<String, Object> result = new HashMap<>();
if (status == null) {
result.put("status", "expired");
} else if (status.equals("WAITING")) {
result.put("status", "waiting");
} else if (status.equals("SCANNED")) {
result.put("status", "scanned");
} else if (status.startsWith("SUCCESS:")) {
String token = status.substring(8);
result.put("status", "success");
result.put("token", token);
// 清除Redis中的状态
redisTemplate.delete(key);
}
return result;
}
public void handleCallback(String code, String state) {
// 1. 通过code获取access_token
String accessTokenUrl = String.format(
"https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=%s&secret=%s&code=%s&grant_type=authorization_code",
wxConfig.getAppId(),
wxConfig.getAppSecret(),
code
);
// 发起HTTP请求获取access_token
// 使用access_token获取用户信息
// 生成JWT token
String token = generateToken(userInfo);
// 更新Redis中的状态
redisTemplate.opsForValue().set(
"wx:qrcode:" + state,
"SUCCESS:" + token,
5,
TimeUnit.MINUTES
);
}
private String generateToken(WxUserInfo userInfo) {
// 实现JWT token生成逻辑
return "jwt_token";
}
}
数据库设计
CREATE TABLE `wx_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`openid` varchar(64) NOT NULL COMMENT '微信openid',
`unionid` varchar(64) DEFAULT NULL COMMENT '微信unionid',
`nickname` varchar(64) DEFAULT NULL COMMENT '微信昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实现流程
- 用户访问登录页面,前端向后端请求获取二维码
- 后端生成state并返回微信授权URL
- 前端使用URL生成二维码,并开始轮询检查登录状态
- 用户扫描二维码并在手机上确认登录
- 微信服务器回调后端接口,带上code和state
- 后端通过code获取用户信息,生成token
- 前端轮询检测到登录成功,获取token并完成登录
注意事项
-
安全性考虑
- 使用HTTPS
- state参数防止CSRF攻击
- 及时清理过期的二维码状态
- 敏感信息加密存储
-
性能优化
- Redis缓存减少数据库压力
- 合理设置轮询间隔
- 二维码状态及时过期
-
用户体验
- 清晰的状态提示
- 二维码过期自动刷新
- 登录成功后的平滑跳转
依赖配置
前端 (package.json)
{
"dependencies": {
"vue": "^3.3.0",
"axios": "^1.6.0",
"qrcode.vue": "^3.4.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0"
}
}
后端 (pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
总结
本文详细介绍了如何使用Vue和Java实现微信扫码登录功能。主要包括:
- 前端二维码展示和状态管理
- 后端接口实现和数据处理
- 完整的登录流程和状态处理
- 安全性和性能考虑
通过这个实现,用户可以方便地使用微信扫码完成网站登录,提供了良好的用户体验。在实际项目中,还需要根据具体需求进行适当的调整和优化。
9万+

被折叠的 条评论
为什么被折叠?



