前置条件
1.首先获取手机号需要认证企业资质的小程序
2.获取手机号需要一台服务器编写解密代码
正文
1.首先理一下流程,下面是微信开发文档中的一张图
下面是我自己写的流程
注意
微信官方文档-手机号获取
按照例程打印不出来code,看看答应e.detail的结果
{
errMsg: "getPhoneNumber:ok",
encryptedData: "/7uD9+GJ7qd0YBVX30Fg0Fj8W/2IXHbUhzZ3TfHv+LyRktBtZw…KKLFhmzK5jyiLxweSspezjVP7EEaifXPaAFeGG4fIl3u4GQ==",
iv: "jfnSdhGbU4OS3JmwsoxL2Q=="
}
只能获取encryptedData和iv因此我们需要从wx.login()中获取code
2.小程序代码
1.在app.js的onLaunch()或index.js页面的onLoad()下面写wx.login()
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
// 1.获取临时登录凭证code
wx.login({
success: res => {
if(res.code){
console.log("code->", res.code)
wx.setStorageSync('resCode', res.code)
}
}
})
}
})
将获取到的code保存在缓存里
2.由于getPhoneNumber现在不支持自动弹出,需要用按钮去拉起,所以我们在wxml写上
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
并且在对应的js里写
Page({
// 获取手机号授权
getPhoneNumber (e) {
// 用户拒绝授权
if(e.detail.errMsg == "getPhoneNumber:fail user deny") {
wx.showToast({
icon: "none",
title: '请允许获取手机号,否则功能不可用!',
})
return
}
/// 用户允许授权
console.log("e.detail.errMsg="+e.detail.errMsg)
console.log("e.detail.iv=->", e.detail.iv); //包括敏感数据在内的完整用户信息的加密数据,需要解密
console.log("e.detail.encryptedData->", e.detail.encryptedData); //加密算法的初始向量,解密需要用到
/// 获取手机号
let resCode = wx.getStorageSync('resCode');
console.log("resCode = " +resCode)
console.log(" e.detail = " + e.detail)
if(resCode){
this.getphone(resCode, e.detail.encryptedData, e.detail.iv);
this.triggerEvent("isHiddenPopup");
}
},
// 访问登录凭证校验接口获取session_key 并用session_key获取手机号
getphone: function(js_code, encryptedData, iv) {
wx.request({
url: globalData.getSessionKeyUrl,//需要填写自己的服务器请求地址
data: {
'js_code': js_code,
'encryptedData' : encryptedData,
'iv':iv,
'sign': 'sign',
},
method: 'GET',
header: {
'content-type': 'application/json'
}, // 设置请求的 header
success: function(data) {
console.log("获取手机号返回JSON数据 =", data.data)
console.log("手机号 = "+data.data.phoneNumber)
if(data.data==undefined){
wx.showToast({
icon: "none",
title: '手机号获取失败,请重新登录!',
})
return
}
if(data.statusCode == 200) {
if(data.data.phoneNumber==undefined){
// 获取手机号失败
console.log("获取手机号失败");
return
}
// 4.跳转web-view页面
wx.switchTab({
url: "/pages/mine/mine"
})
}
},
fail: function(err) {
console.log(err);
wx.showToast({
icon: "none",
title: 'session_key获取失败,请重新登录!',
})
return
}
})
}
})
至此小程序主要代码写完了
3.后台代码
后台选择SpringBoot搭建,首先建一个基础工程,然后直接贴代码
1.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>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.server</groupId>
<artifactId>server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>server</name>
<description>server</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<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>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</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>
2.ServerApplication.java
package com.server;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.server.data.globalData;
import com.server.httpUtil.PostData;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
@RestController
@SpringBootApplication
public class ServerApplication {
com.server.data.globalData globalData = new globalData();
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
//解析电话号码
@GetMapping("/getsessionkey")
public Object getsessionkey(String js_code,String encryptedData, String iv) throws JsonProcessingException {
PostData PostData = new PostData();
String responseData = PostData.PostData(globalData.url, globalData.appid, globalData.secret,js_code,globalData.grant_type);
//获取SessionKey 和openid
ObjectMapper mapper = new ObjectMapper();
JsonNode resData = mapper.readTree(responseData);
try{
if(resData.get("session_key").asText()!=null)
{
String SessionKey = resData.get("session_key").asText();
String openid = resData.get("openid").asText();
System.out.println("SessionKey="+SessionKey);
System.out.println("openid="+openid);
System.out.println("encryptedData="+encryptedData);
System.out.println("iv="+iv);
String wxDecrypt = WechatUtils.wxDecrypt(encryptedData,SessionKey,iv);
System.out.println(wxDecrypt);
return wxDecrypt;
}else {
System.out.println("请求微信服务器异常");
}
}catch (Exception e)
{
System.out.println("请求微信服务器异常");
}
return 0;
}
}
3.WechatUtils.java解密工具类
package com.server;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class WechatUtils {
public static final String AES = "AES";
public static final String AES_CBC_PADDING = "AES/CBC/PKCS7Padding";
/**
* * 微信 数据解密<br/>
* * 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充<br/>
* * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)<br/>
* * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节<br/>
* * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节<br/>
* *
* * @param encrypted 目标密文
* * @param session_key 会话ID
* * @param iv 加密算法的初始向量
*
*/
public static String wxDecrypt(String encrypted, String session_key, String iv) {
String result = null;
byte[] encrypted64 = org.apache.commons.codec.binary.Base64.decodeBase64(encrypted);
byte[] key64 = org.apache.commons.codec.binary.Base64.decodeBase64(session_key);
byte[] iv64 = org.apache.commons.codec.binary.Base64.decodeBase64(iv);
try {
init();
result = new String(decrypt(encrypted64, key64, generateIV(iv64)));
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* * 初始化密钥
*
*/
public static void init() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyGenerator.getInstance(AES).init(128);
}
/**
* * 生成iv
*
*/
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
// iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0
// Arrays.fill(iv, (byte) 0x00);
AlgorithmParameters params = AlgorithmParameters.getInstance(AES);
params.init(new IvParameterSpec(iv));
return params;
}
/**
* * 生成解密
*
*/
public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv)
throws Exception {
Key key = new SecretKeySpec(keyBytes, AES);
Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encryptedData);
}
}
4.HttpRestUtils.java http请求工具类
package com.server.httpUtil;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
public class HttpRestUtils {
/**
* http post
* */
public static String post(String url, MultiValueMap<String, String> params) throws IOException {
return httpRestClient(url, HttpMethod.POST, params);
}
/**
* http get
* */
public static String get(String url, MultiValueMap<String, String> params) throws IOException {
return httpRestClient(url, HttpMethod.GET, params);
}
/**
* HttpMethod post/get
* */
private static String httpRestClient(String url, HttpMethod method, MultiValueMap<String, String> params) throws IOException {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(10*1000);
requestFactory.setReadTimeout(10*1000);
RestTemplate client = new RestTemplate(requestFactory);
HttpHeaders headers = new HttpHeaders();
// 以表单的方式提交
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// headers.setContentType(MediaType.APPLICATION_JSON_UTF8);//不好使,不能用get方法
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers);
// 执行HTTP请求
ResponseEntity<String> response = null;
try{
response = client.exchange(url, HttpMethod.POST, requestEntity, String.class);
System.out.println("response="+response);
return response.getBody();
}
catch (HttpClientErrorException e){
System.out.println( "------------- 出现异常 HttpClientErrorException -------------");
System.out.println(e.getMessage());
System.out.println(e.getStatusText());
System.out.println( "-------------responseBody-------------");
System.out.println( e.getResponseBodyAsString());
e.printStackTrace();
return "";
}
catch (Exception e) {
System.out.println( "------------- HttpRestUtils.httpRestClient() 出现异常 Exception -------------");
System.out.println(e.getMessage());
return "";
}
}
}
5.PostData.java http请求业务类
package com.server.httpUtil;
import org.springframework.http.HttpMethod;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
public class PostData {
public String PostData(String url ,String appid,String appSecret,String code,String authorization_code) {
try {
//post请求
HttpMethod method = HttpMethod.GET;
// 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("appid",appid);
params.add("secret",appSecret);
params.add("js_code",code);
params.add("grant_type",authorization_code);
System.out.print("发送数据:" + params.toString()+"\n");
//发送http请求并返回结果
String result = HttpRestUtils.get(url, params);
System.out.print("接收反馈:" + result+"\n");
return result;
} catch (Exception e) {
System.out.println("------------- " + this.getClass().toString() + ".PostData() : 出现异常 Exception -------------");
System.out.println(e.getMessage());
return "";
}
}
}
6.globalData.java 全局变量
package com.server.data;
public class globalData {
public String url = "https://api.weixin.qq.com/sns/jscode2session";//微信服务器接口
public String appid = "xxxxxx填自己小程序的xxxxx";
public String secret = "xxxxxx填自己小程序的xxxxx";
public String grant_type = "authorization_code";//这样写就行
}
至此前后台就ok了,可以获取用户手机号了
后台数据打印如下