在开始开发前,您需要注册一个微信公众号(服务号或认证订阅号),并在微信公众平台获取您的AppID和AppSecret。这些凭证将用于身份验证和调用微信API。
登录微信公众平台,进入“开发”选项,配置您的服务器URL、Token和消息加解密密钥(AESKey)。确保您的Spring Boot应用能够通过外网访问,以便微信服务器能够与之通信。您可以使用内网穿透工具如Ngrok来实现本地开发环境的公网访问。
# 启动Ngrok,将本地8080端口映射到公网
ngrok http 8080
配置微信公众号接口地址为Ngrok分配的域名,例如:http://your-ngrok-domain.ngrok.io
本指南将使用Java Spring Boot作为后端开发框架,结合WxJava作为微信开发SDK,以简化与微信API的交互。此外,可以选择MySQL作为用户信息的存储数据库(可选)。
在您的Spring Boot项目的pom.xml
文件中添加WxJava的Maven依赖:
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>最新版</version>
</dependency>
在application.yml
文件中配置微信公众号的基本信息:
wx:
mp:
configs:
- appId: 你的appid
secret: 你的appsecret
token: 你的token
aesKey: 你的aesKey
用户需要在微信公众号中主动授权,以便您的应用能够获取其手机号。以下是实现用户授权的基本流程:
code
。code
换取access_token
和openid
。前端需要实现用户点击“获取手机号”按钮后,触发微信API获取加密的手机号数据,并将其发送至后端进行解密。
wx.login({
success: res => {
let code = res.code; // 获取登录code
// 用户点击获取手机号按钮后调用
wx.getPhoneNumber({
success: phoneRes => {
// phoneRes 包含 encryptedData、iv
wx.request({
url: 'https://你的域名/api/getPhone',
method: 'POST',
data: {
code: code,
encryptedData: phoneRes.encryptedData,
iv: phoneRes.iv
},
success: res => {
// 处理返回结果,得到手机号
}
})
}
})
}
});
后端需要处理前端发送的code
、encryptedData
和iv
,并通过微信API解密获取用户手机号。
code
调用微信接口获取session_key
和openid
。
session_key
、encryptedData
和iv
进行数据解密,提取手机号。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.client.RestTemplate;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
@RestController
@RequestMapping("/api")
public class WeChatController {
private final String appId = "你的appid";
private final String appSecret = "你的appSecret";
@PostMapping("/getPhone")
public ResponseEntity<?> getPhone(@RequestBody PhoneRequest request) {
String sessionUrl = "https://api.weixin.qq.com/sns/jscode2session" +
"?appid=" + appId +
"&secret=" + appSecret +
"&js_code=" + request.getCode() +
"&grant_type=authorization_code";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(sessionUrl, String.class);
JSONObject sessionInfo = JSONObject.parseObject(response.getBody());
if (sessionInfo.getString("session_key") == null) {
return ResponseEntity.badRequest().body("获取 session_key 失败:" + sessionInfo.getString("errmsg"));
}
String sessionKey = sessionInfo.getString("session_key");
try {
String phoneNumber = decryptData(request.getEncryptedData(), sessionKey, request.getIv());
Map<String,Object> result = new HashMap<>();
result.put("phoneNumber", phoneNumber);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("解密失败:" + e.getMessage());
}
}
private String decryptData(String encryptedData, String sessionKey, String iv) throws Exception {
byte[] dataByte = Base64.getDecoder().decode(encryptedData);
byte[] keyByte = Base64.getDecoder().decode(sessionKey);
byte[] ivByte = Base64.getDecoder().decode(iv);
int base = 16;
if(keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
cipher.init(Cipher.DECRYPT_MODE, spec, ivSpec);
byte[] resultByte = cipher.doFinal(dataByte);
if(resultByte != null && resultByte.length > 0){
String result = new String(resultByte, "UTF-8");
JSONObject jsonObject = JSONObject.parseObject(result);
return jsonObject.getString("phoneNumber");
}
return null;
}
}
// 请求数据对应的实体类
class PhoneRequest {
private String code;
private String encryptedData;
private String iv;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getEncryptedData() {
return encryptedData;
}
public void setEncryptedData(String encryptedData) {
this.encryptedData = encryptedData;
}
public String getIv() {
return iv;
}
public void setIv(String iv) {
this.iv = iv;
}
}
WxJava是一个功能强大的微信开发SDK,能够简化与微信API的交互。通过配置WxJava,您可以更加便捷地实现用户授权、消息处理及数据解密等功能。
@Configuration
public class WeChatConfig {
@Bean
public WxMpService wxMpService(WxMpProperties properties) {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage(properties));
return wxMpService;
}
@Bean
public WxMpConfigStorage wxMpConfigStorage(WxMpProperties properties) {
WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
config.setAppId(properties.getAppId());
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
return config;
}
}
在获取用户手机号后,您需要安全地存储这些敏感信息。建议采用加密存储,并严格控制数据访问权限,确保用户隐私不被泄露。
确保您的应用符合相关的数据保护法规,如《中华人民共和国个人信息保护法》(PIPL)。在收集和使用用户手机号前,必须获得用户的明确同意,并告知其用途。
在前后端数据传输过程中,务必使用HTTPS协议,确保数据在传输过程中不被窃取或篡改。同时,对存储的用户数据进行加密处理,防止数据泄露。
获取session_key
失败可能由于code
过期或错误。确保在获取code
后尽快使用,并处理微信API返回的错误信息。
解密过程中可能会遇到数据格式错误或密钥不匹配的问题。请检查session_key
、iv
和encryptedData
的正确性,确保使用正确的加密算法。
如果用户拒绝授权,您需要设计友好的提示信息,引导用户重新授权或提供其他联系方式获取手机号。
为了加深理解,您可以参考以下资源和示例项目,这些项目实现了完整的用户手机号获取流程,并提供详细的代码示例:
项目名称 | 描述 | 链接 |
---|---|---|
WeChat Phone Number Demo | 一个基于Spring Boot和WxJava的示例项目,实现了微信公众号获取用户手机号的功能。 | GitHub链接 |
WxJava 官方文档 | 详细介绍了WxJava的使用方法和API,帮助开发者快速上手。 | WxJava GitHub |
微信开放文档 | 微信官方提供的开发者文档,涵盖所有API的详细说明和使用示例。 | 微信开放文档 |
通过结合Java Spring Boot与WxJava,您可以高效地实现微信公众号获取用户手机号的功能。关键在于正确配置微信公众号平台、实现用户授权流程、确保数据的安全处理以及遵守相关隐私保护法规。借助WxJava等开发工具,您可以简化开发流程,专注于业务逻辑的实现。同时,务必重视用户数据的安全与隐私,建立健全的数据保护机制,提升用户信任度。