微信小程序一键登录

0.简介

实现在微信小程序的点击一键登录的按钮获取当前微信用户唯一且统一的openId,也可以获取手机号和用户名,本来想和网页一起做一个二维码绑定,发现要收300的注册费用。

核心就是小程序前端调用自带的login向后端发送一个带code给后端,后端将这个code和小程序校验的数据给微信,微信返回该用户的唯一openid。

wx.login() 是微信小程序官方提供的用户登录凭证获取接口,其核心功能是:
当你在小程序中调用 wx.login() 时,微信客户端会向微信官方服务器发起请求,微信服务器验证请求合法性后,生成一个临时的、唯一的登录凭证,并通过 success 回调函数的 res 参数返回给小程序前端,这个凭证就是 res.code

img

1.小程序

创建一个简单的登录按钮和显示界面

index.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<view class="container">
<button
class="login-btn"
bindtap="getOpenid"
open-type="getUserInfo"
withCredentials="true"
>
一键登录
</button>
<view class="result" wx:if="{{openid}}">
登录成功!你的openid是:{{openid}}
</view>
</view>

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Page({
data: {
openid: '',
errorMsg: ''
},

// 获取openid的方法
getOpenid() {
// 显示加载中
wx.showLoading({
title: '登录中...',
})
this.setData({ errorMsg: '' })

// 1. 调用微信登录接口获取code
wx.login({
success: (res) => {
console.log('wx.login成功,code:', res.code);
if (res.code) {
// 2. 将code发送到后端服务器
wx.request({
// url: 'http://47.121.130.20:4678/api/login',
url: 'http://localhost:3000/api/login',
method: 'POST',
data: {
code: res.code
},
success: (response) => {
wx.hideLoading()
console.log('后端返回结果:', response.data); // 打印完整返回数据

if (response.data.success && response.data.openid) {
// 3. 保存openid并显示
this.setData({
openid: response.data.openid
})

// 可以将openid存储到本地
wx.setStorageSync('openid', response.data.openid)

wx.showToast({
title: '登录成功',
icon: 'success'
})
} else {
// 显示具体错误信息
const msg = response.data.message || '登录失败';
this.setData({ errorMsg: msg });
wx.showToast({
title: msg,
icon: 'none',
duration: 3000
})
}
},
fail: (err) => {
wx.hideLoading()
const errMsg = '网络请求失败: ' + (err.errMsg || '未知错误');
this.setData({ errorMsg: errMsg });
wx.showToast({
title: errMsg,
icon: 'none',
duration: 3000
})
console.error('请求失败:', err)
}
})
} else {
wx.hideLoading()
const errMsg = '获取code失败: ' + res.errMsg;
this.setData({ errorMsg: errMsg });
wx.showToast({
title: errMsg,
icon: 'none',
duration: 3000
})
console.error('登录失败!' + res.errMsg)
}
},
fail: (err) => {
wx.hideLoading()
const errMsg = '登录接口调用失败: ' + err.errMsg;
this.setData({ errorMsg: errMsg });
wx.showToast({
title: errMsg,
icon: 'none',
duration: 3000
})
console.error('wx.login失败:', err)
}
})
}
})

index.wxss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #f5f5f5;
}

.login-btn {
width: 80%;
height: 48px;
line-height: 48px;
background-color: #07c160;
color: white;
border-radius: 24px;
font-size: 16px;
}

.result {
margin-top: 30px;
padding: 20px;
background-color: white;
border-radius: 8px;
word-break: break-all;
max-width: 80%;
}

image-20250903120643027

2. SpringBoot后端

pom

加入json格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot配置处理器 - 用于处理@ConfigurationProperties注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- 工具类库 - 用于JSON处理等 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

image-20250903164035376

RestTemplateConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.kangdd.wxlogin;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();

// 创建新的消息转换器列表
List<HttpMessageConverter<?>> converters = new ArrayList<>();

// 找到Jackson转换器并修改其支持的媒体类型
MappingJackson2HttpMessageConverter jacksonConverter = null;
for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
break;
}
}

// 如果找到Jackson转换器,修改其支持的媒体类型
if (jacksonConverter != null) {
// 创建新的可修改的媒体类型列表
List<MediaType> mediaTypes = new ArrayList<>(jacksonConverter.getSupportedMediaTypes());
mediaTypes.add(MediaType.TEXT_PLAIN);
jacksonConverter.setSupportedMediaTypes(mediaTypes);
converters.add(jacksonConverter);
}

// 添加其他转换器
for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
if (!(converter instanceof MappingJackson2HttpMessageConverter)) {
converters.add(converter);
}
}

restTemplate.setMessageConverters(converters);
return restTemplate;
}
}

WechatLoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.kangdd.wxlogin;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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

@RestController
public class WechatLoginController {

@Value("${wechat.appid}")
private String appId;

@Value("${wechat.secret}")
private String appSecret;

// 移除@Autowired注解,构造函数注入不需要它
private final RestTemplate restTemplate;

public WechatLoginController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

@PostMapping("/api/login")
public Map<String, Object> login(@RequestBody Map<String, String> request) {
Map<String, Object> response = new HashMap<>();
System.out.println("接收到请求!code: " + request.get("code"));

// 获取前端传来的code
String code = request.get("code");
if (code == null || code.isEmpty()) {
response.put("success", false);
response.put("message", "缺少code参数");
System.out.println("返回结果: " + response);
return response;
}

// 检查appid和secret是否配置
if (appId == null || appId.isEmpty() || appSecret == null || appSecret.isEmpty()) {
response.put("success", false);
response.put("message", "appid或secret未配置");
System.out.println("返回结果: " + response);
return response;
}

// 构建微信接口URL
String url = String.format(
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
appId, appSecret, code
);
System.out.println("请求微信接口URL: " + url);

try {
// 调用微信接口
Map<String, Object> result = restTemplate.getForObject(url, Map.class);
System.out.println("微信接口返回: " + result);

// 检查是否有错误
if (result.containsKey("errcode")) {
response.put("success", false);
response.put("message", "微信接口返回错误: " + result.get("errmsg"));
response.put("errcode", result.get("errcode"));
System.out.println("返回结果: " + response);
return response;
}

// 返回成功结果
response.put("success", true);
response.put("openid", result.get("openid"));
System.out.println("返回结果: " + response);
return response;
} catch (Exception e) {
response.put("success", false);
response.put("message", "请求微信接口失败: " + e.getMessage());
e.printStackTrace();
System.out.println("返回结果: " + response);
return response;
}
}
}

配置

1
2
3
4
5
6
# 微信小程序配置
wechat.appid=###
wechat.secret=###

server.port=4678

3.效果

image-20250903164548047

image-20250903164617447