Spring Boot 3.5.7 对接微信小程序获取手机号完整实现
·
Spring Boot 3.5.7 对接微信小程序获取手机号完整实现
核心流程
- 前端:调用微信小程序
wx.getPhoneNumber获取手机号专属code - 后端:
- 从Redis缓存获取微信
access_token(缓存有效期7100秒,避免7200秒过期) - 缓存未命中时,调用微信接口获取新
access_token并写入Redis - 用
access_token + 手机号code调用微信接口获取用户手机号
- 从Redis缓存获取微信
- 技术栈:Spring Boot 3.5.7、JDK 17、Redis、Spring Data Redis、WebClient(Spring3推荐HTTP客户端)
一、项目依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>wechat-mini-phone</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(Redis必需) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、配置文件(application.yml)
server:
port: 8080
# 微信小程序配置
wechat:
mini:
app-id: 你的微信小程序APPID
app-secret: 你的微信小程序APPSECRET
# access_token缓存key
access-token-key: wechat:mini:program:access_token
# 缓存有效期(秒):微信官方7200秒,这里设置7100秒防止过期
token-expire: 7100
# Redis配置
spring:
data:
redis:
host: localhost
port: 6379
password: # 无密码留空
database: 0
# 连接池配置
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
三、核心代码实现
1. 统一返回结果封装(Result.java)
package com.example.wechatmini.entity;
import lombok.Data;
/**
* 统一接口返回结果
*/
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg(msg);
result.setData(null);
return result;
}
}
2. 微信自定义异常(WechatException.java)
package com.example.wechatmini.exception;
/**
* 微信接口调用异常
*/
public class WechatException extends RuntimeException {
public WechatException(String message) {
super(message);
}
}
3. Redis配置类(RedisConfig.java)
解决JDK17下Redis序列化乱码问题
package com.example.wechatmini.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// String序列化器
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// JSON序列化器
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jsonSerializer.setObjectMapper(om);
// key和hashKey使用String序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value和hashValue使用JSON序列化
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
4. 微信常量/配置类(WechatMiniProperties.java)
读取yml中的微信配置
package com.example.wechatmini.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信小程序配置参数
*/
@Data
@Component
@ConfigurationProperties(prefix = "wechat.mini")
public class WechatMiniProperties {
/**
* 小程序APPID
*/
private String appId;
/**
* 小程序APPSECRET
*/
private String appSecret;
/**
* access_token缓存key
*/
private String accessTokenKey;
/**
* token缓存有效期(秒)
*/
private Long tokenExpire;
// 微信接口地址常量
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
public static final String GET_PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
}
5. AccessToken缓存服务(WechatTokenService.java)
核心:从Redis获取/刷新access_token,防止并发重复请求微信接口
package com.example.wechatmini.service;
import com.example.wechatmini.config.WechatMiniProperties;
import com.example.wechatmini.exception.WechatException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 微信access_token获取与缓存服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WechatTokenService {
private final RedisTemplate<String, Object> redisTemplate;
private final WechatMiniProperties wechatProperties;
private final WebClient webClient;
/**
* 获取有效的access_token(优先从缓存取)
* 加synchronized防止并发重复请求微信接口
*/
public synchronized String getAccessToken() {
// 1. 从Redis缓存获取
String token = (String) redisTemplate.opsForValue().get(wechatProperties.getAccessTokenKey());
if (token != null && !token.isEmpty()) {
log.info("从Redis缓存获取access_token: {}", token);
return token;
}
// 2. 缓存无数据,调用微信接口获取新token
return refreshAccessToken();
}
/**
* 调用微信接口刷新access_token并写入Redis
*/
private String refreshAccessToken() {
String url = String.format(
WechatMiniProperties.ACCESS_TOKEN_URL,
wechatProperties.getAppId(),
wechatProperties.getAppSecret()
);
try {
// 调用微信接口
Map<String, Object> response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(Map.class)
.block();
// 校验返回结果
if (response == null || response.containsKey("errcode")) {
String errMsg = response == null ? "接口调用失败" : response.get("errmsg").toString();
log.error("获取微信access_token失败: {}", errMsg);
throw new WechatException("获取access_token失败:" + errMsg);
}
String accessToken = response.get("access_token").toString();
// 3. 存入Redis,设置过期时间
redisTemplate.opsForValue().set(
wechatProperties.getAccessTokenKey(),
accessToken,
wechatProperties.getTokenExpire(),
TimeUnit.SECONDS
);
log.info("调用微信接口获取access_token成功,已缓存到Redis: {}", accessToken);
return accessToken;
} catch (Exception e) {
log.error("刷新access_token异常", e);
throw new WechatException("刷新access_token异常:" + e.getMessage());
}
}
}
6. 小程序获取手机号服务(WechatMiniPhoneService.java)
package com.example.wechatmini.service;
import com.example.wechatmini.config.WechatMiniProperties;
import com.example.wechatmini.exception.WechatException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 微信小程序获取手机号服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WechatMiniPhoneService {
private final WechatTokenService wechatTokenService;
private final WebClient webClient;
/**
* 获取用户手机号
* @param code 前端wx.getPhoneNumber返回的手机号专属code
* @return 手机号
*/
public String getUserPhone(String code) {
// 1. 获取有效的access_token
String accessToken = wechatTokenService.getAccessToken();
// 2. 拼接微信获取手机号接口
String url = String.format(WechatMiniProperties.GET_PHONE_URL, accessToken);
// 3. 构造请求参数
Map<String, String> requestParam = new HashMap<>();
requestParam.put("code", code);
try {
// 4. 调用微信接口
Map<String, Object> response = webClient.post()
.uri(url)
.bodyValue(requestParam)
.retrieve()
.bodyToMono(Map.class)
.block();
// 5. 校验返回结果
if (response == null || !"0".equals(response.get("errcode").toString())) {
String errMsg = response == null ? "接口调用失败" : response.get("errmsg").toString();
log.error("获取手机号失败: {}", errMsg);
throw new WechatException("获取手机号失败:" + errMsg);
}
// 6. 解析手机号
Map<String, Object> phoneInfo = (Map<String, Object>) response.get("phone_info");
String phoneNumber = phoneInfo.get("phoneNumber").toString();
log.info("获取用户手机号成功: {}", phoneNumber);
return phoneNumber;
} catch (Exception e) {
log.error("获取手机号异常", e);
throw new WechatException("获取手机号异常:" + e.getMessage());
}
}
}
7. WebClient配置(WebClientConfig.java)
Spring Boot3推荐使用WebClient替代RestTemplate
package com.example.wechatmini.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.create();
}
}
8. 控制器(WechatMiniController.java)
对外提供接口,供前端调用
package com.example.wechatmini.controller;
import com.example.wechatmini.entity.Result;
import com.example.wechatmini.service.WechatMiniPhoneService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 微信小程序接口
*/
@RestController
@RequestMapping("/wechat/mini")
@RequiredArgsConstructor
public class WechatMiniController {
private final WechatMiniPhoneService phoneService;
/**
* 获取用户手机号
* @param code 前端传入的手机号code
* @return 手机号
*/
@PostMapping("/getPhone")
public Result<String> getPhoneNumber(@RequestParam String code) {
String phone = phoneService.getUserPhone(code);
return Result.success(phone);
}
}
9. 全局异常处理器(GlobalExceptionHandler.java)
package com.example.wechatmini.exception;
import com.example.wechatmini.entity.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(WechatException.class)
public Result<String> handleWechatException(WechatException e) {
log.error("微信业务异常: {}", e.getMessage());
return Result.error(e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后再试");
}
}
四、前端配合代码(微信小程序)
// 页面wxml
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机号</button>
// 页面js
getPhoneNumber(e) {
// 获取手机号code
const code = e.detail.code;
if (!code) {
wx.showToast({ title: '授权失败', icon: 'error' });
return;
}
// 调用后端接口
wx.request({
url: 'http://localhost:8080/wechat/mini/getPhone',
method: 'POST',
data: { code: code },
success: (res) => {
if (res.data.code === 200) {
console.log('用户手机号:', res.data.data);
wx.showToast({ title: '获取成功' });
} else {
wx.showToast({ title: res.data.msg, icon: 'error' });
}
}
});
}
五、关键说明
-
access_token缓存
- 缓存到Redis,有效期7100秒(微信官方7200秒,预留100秒缓冲)
- 加
synchronized防止并发场景下多次请求微信接口 - 缓存key:
wechat:mini:program:access_token
-
接口权限
- 小程序必须完成企业认证才能调用获取手机号接口
code是一次性有效的,前端每次获取都要重新调用wx.getPhoneNumber
-
环境要求
- JDK 17+
- Redis 5.0+
- Spring Boot 3.5.7
- 小程序APPID/APPSECRET必须正确
-
测试方式
- 启动Redis
- 填写yml中的小程序
app-id和app-secret - 启动Spring Boot项目
- 用微信开发者工具测试前端授权获取手机号
总结
- 实现了Redis缓存access_token,避免频繁调用微信接口
- 完整对接微信小程序获取手机号全流程
- 代码遵循Spring Boot3最佳实践,使用WebClient、全局异常、统一返回值
- 支持高并发场景,防止重复刷新token
- 前端代码可直接复用,后端可直接部署运行
更多推荐


所有评论(0)