小程序面世已经有几年了,最近才在项目中开始用到。花了一些时间阅读了相关的文档,整体上感觉比公众号开发要友好一些。
1 几个概念
1.1 appId -小程序id
1.2 appSecret-小程序密钥
1.3 openId-同公众号openId
1.4 unionId-同公众号unionId
1.4.1 对于unionId,一般是通过解析userInfo的加密字符串得到,但是也有一些限制
1.4.1.1 只有当用户在同时使用2个及以上的小程序、公众号、app才会能获取到的,如果用户只用了同平台下的一个小程序,是拿不到unionID的
1.4.1.2 如果小程序绑定的是个人开放平台也无法获取到unionId(这个没有验证过)
1.5 sessionKey-这个功能类似于公众号的access_token;但是又有些不同。
1.5.1 获取: sessionKey是通过解析code获取到的,且是有生命周期的,每次刷新得到一个新的sessionKey同时旧的sessionKey会失效。
1.5.2 使用场景:目前在解析加密userInfo信息和获取手机号时会使用到
1.5.3 有效期:没有明确的过期时间,但是小程序提供了一个验证是否有效的方法
wx.checkSession({
success () {
//session_key 未过期,并且在本生命周期一直有效
},
fail () {
// session_key 已经失效,需要重新执行登录流程
wx.login() //重新登录
}
})
2 开发前配置
2.1 申请小程序什么的就不说了。
2.2 配置服务器域名:在小程序后台-开发-开发设置-服务器域名下面添加小程序需要的域名信息。必须是https协议,这个域名配好了在小程序开发工具里面就可以通过这个域名地址进行接口调试了。
3 开发步骤-个人理解
3.1 正常来说第一步总要去拿到用户的openId的,小程序的openId也是通过解析code得到的
3.1.1 获取code
wx.login({
success (res) {
if (res.code) {
//发起网络请求
wx.request({
url: 'https://test.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
3.1.2 后台解析code-这一步比较简单,通过提供的接口得到解析的信息。需要注意的是code只能使用一次,解析过后当前code就失效了。
3.1.2.1 请求地址
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
3.1.2.2 自己写的一个示例:
使用的是hutool的工具类
public WechatUser getOpenId(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code";
String result = HttpRequest.get(url).header("Content-Type", "application/json").execute().body();
JSONObject jsonObject = JSONUtil.parseObj(result);
System.out.println("resut:" + result);
String openId = jsonObject.getStr("openid");// 拿到openid
if (StringUtil.isBlank(openId)) {
throw new ServiceException("无效的code");
}
String sessionKey = jsonObject.getStr("session_key");// 拿到sessionKey
return null;
}
这样就拿到openId和sessionKey了,openId可以自行处理。sessionKey可以考虑传给前台或者存入缓存。
3.2 获取用户基本信息userInfo,userInfo里面可以拿到昵称,头像等基本信息,如果想要尝试得到unionId或者openId等信息需要对encryptedData进行解密,当然即使解密成功是否能拿到unionId还是要取决于步骤1.4提到的情况,就我目前的情况来说只得到了openId并没有获取unionId,就比较尴尬。因为openId在解析code的时候已经得到了。
// 必须是在用户已经授权的情况下调用
wx.getUserInfo({
success: function(res) {
var userInfo = res.userInfo
var nickName = userInfo.nickName
var avatarUrl = userInfo.avatarUrl
var gender = userInfo.gender //性别 0:未知、1:男、2:女
var province = userInfo.province
var city = userInfo.city
var country = userInfo.country
}
})
3.2.1 调用这个方法之前最好先通过wx.checkSession()方法验证一下当前的sessionKey是否有效,因为加密的时候如果sessionKey过期了,小程序会自动生成一个新的sessionKey去加密,而你解密的时候用了之前失效的sesionKey是无法得到期望的结果的。
wx.checkSession({
success () {
//session_key 未过期,并且在本生命周期一直有效
},
fail () {
// session_key 已经失效,需要重新执行登录流程
wx.login() //重新登录
}
})
3.2.2 一个示例:
package org.springblade.modules.wechat.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import org.springblade.modules.lecoffee.entity.WechatUser;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.Security;
/**
* 微信工具类
*/
public class WechatUtil {
public static void main(String[] args) {
String phoneNumber = getPhone(
"5JulIUPEKE4IVBulNxoIS3PAJKgM9GgfKyL45PbfXCCmoeALbHDcl/FTq0vWiwsemcKESaQXAzuPyk59tn/pEoZunF+ChpIMss6GgXHVHod+0Qc8LoYy1yT1wz7regmKZJQCB1a/jNaLzFPvGCkV6eaBOF7myT0ZrZZc7x3YS/6UxQTVW6nF2DvlDFPxungL1amwn6vLW0TgazH8TONcGCNwnOVkOR/3Cn1c6g5pO3s2Ib3304aCyX6QiYCpTAC7NoaWMDrf+Kh7OLQfXucidncYpKS8f05QX9/VlyI1cbhYE+t0QBmDt4dQ3yBBPaTTsnB3MSV+RkzDJ5wE8iTlbGQik+z7p6PlTKmp0e6S3dYfS0aQhyLZzpZUO26ZT9QGB1X8k8nBA2BkFUY3/lCIQ5X5Lyt41RKeyMvMHAGtcSUuX86KGOi1FhD+PLLVtxA18gtYrx2cXDDD8atY3JCkquGI9IZZU9Wvxfw4MaipkVs=",
"JbLSRmHbMK9qJJS/VOrGBw==",
"yDA6h06w+Ip1trfPRiZAsw=="
);
System.out.println("phoneNumber:" +phoneNumber);
}
public static WechatUser getUserInfo(String encryptDataB64, String sessionKeyB64, String ivB64){
WechatUser user = new WechatUser();
String result = decryptData( encryptDataB64, sessionKeyB64, ivB64);
System.out.println(result);
JSONObject jsonObject = JSONUtil.parseObj(result);
String nickName = jsonObject.getStr("nickName");
user.setNickName(nickName);
user.setAvatarUrl(jsonObject.getStr("avatarUrl"));
user.setProvince(jsonObject.getStr("province"));
user.setCity(jsonObject.getStr("city"));
return user;
}
public static String getPhone(String encryptDataB64, String sessionKeyB64, String ivB64){
String result = decryptData( encryptDataB64, sessionKeyB64, ivB64);
System.out.println(result);
JSONObject jsonObject = JSONUtil.parseObj(result);
String phoneNumber = jsonObject.getStr("phoneNumber");
return phoneNumber;
}
public static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) {
return new String(
decryptOfDiyIV(
Base64.decode(encryptDataB64),
Base64.decode(sessionKeyB64),
Base64.decode(ivB64)
)
);
}
private static final String KEY_ALGORITHM = "AES";
private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding";
private static Key key;
private static Cipher cipher;
private static void init(byte[] keyBytes) {
int base = 16;
if (keyBytes.length % base != 0) {
int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
keyBytes = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
// 转化成JAVA的密钥格式
key = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
try {
// 初始化cipher
cipher = Cipher.getInstance(ALGORITHM_STR, "BC");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解密方法
*
* @param encryptedData 要解密的字符串
* @param keyBytes 解密密钥
* @param ivs 自定义对称解密算法初始向量 iv
* @return 解密后的字节数组
*/
private static byte[] decryptOfDiyIV(byte[] encryptedData, byte[] keyBytes, byte[] ivs) {
byte[] encryptedText = null;
init(keyBytes);
try {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs));
encryptedText = cipher.doFinal(encryptedData);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedText;
}
}
3.3 获取用户手机号-这个地方需要用到组件button。用户授权后就可以拿到加密后的手机信息,后台解密就可以了,解密方法和解析userInfo的方法是一样的。
<view class="fixedModel" wx:if="{{showModel}}" >
<view class="viewTip">授权微信允许获取你的手机号快速注册登录</view>
<button class="btnlogin" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
<image src="/images/wechat.png" class="wechat" style="margin-right:20rpx;"></image>
一键登录</button>
</view>
getPhoneNumber(e) {
this.animate(".fixedModel", [
{bottom: '-0rpx'},
{bottom: '-84rpx'},
{bottom: '-184rpx'},
{bottom: '-284rpx'},
{bottom: '-384rpx',}], 150,()=>{this.setData({
showModel: false
})} )
console.log(e, 23)
var sessionKey = wx.getStorageSync('sessionKey')
if (e.detail.encryptedData) {
var data = {
'sessionKey': sessionKey,//需要提前验证是否有效
'encryptedData': e.detail.encryptedData,//加密密文
'iv': e.detail.iv//加密偏移向量
}
}
通过这几步基本上需要从用户拿到的数据都拿到了,剩下的就是基本的业务相关功能开发了。