嗨,您好 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
文章所在专栏:微信体系
我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
向我询问任何您想要的东西,ID:vnjohn
觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客
代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏
目录
自 2023年8月28日起,手机号验证组件(无论是快速还是实时验证组件)将进行付费使用,由于微信官方作出的调整,各个业务的小程序不得不跟随着一起进行版本的更新,进行系统的新组件兼容
小程序手机验证组件在个人开发者下是无法使用的,它只提供给那些完成了认证的小程序开发(不包含海外主体)
新规调整,给业务系统留有一段的缓冲时间,它与 6 月份就发表了官方说明,留有两个月时间作出调整,对于前端或全栈的小伙伴来说,应该会第一时间去关注这种重大事项的,为公司所在系统及时作出规划和调整
在新版本出来时,每个小程序账号将有 1000 次体验额度,用于开发、调试和体验,但这 1000 次额度不区分正式版、体验版和开发版,都是共用的,一旦超过了这个额度后,一旦调用额度都要采取收费措施
新版变更,手机号验证组件区分两种调用方式,手机号快速验证组件、手机号实时验证组件
手机号快速验证组件,平台会对号码进行验证,但不保证是实时验证,每次调用函数成功收费:0.03 元
后即好实时验证组件,在每次请求时,平台均会对用户选择的手机号进行实时验证,每次调用成功收费:0.04 元
手机号快速验证组件:手机号快速验证组件
手机号实时验证组件:手机号实时验证组件
快速验证组件与实时验证组件所调用的 API 不同,在考虑企业的成本以及用户手机号信息准确性来适中进行选择
手机号快速验证组件
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">button>
手机号实时验证组件
<button open-type="getRealtimePhoneNumber" bindgetrealtimephonenumber="getrealtimephonenumber">button>
区别在于一个是 getPhoneNumber,一个是 getRealtimePhoneNumber
登录小程序后台以后,在左侧菜单「管理 > 付费管理」进行手机号验证组件的余额、购买查看
体验额度是没有有效期的,只有小程序认证主体一直存在,这个体验额度是一直会保留的
快速验证组件选择的有效期越短,所收取的费用就越低,次数也就在有效期的基础上作出适当调整
实时验证组件选择的有效期越短,所收取的费用就越低,次数也就在有效期的基础上作出适当调整
对比很旧的系统来说,相信大家都是使用的那一套解密三个参数「sessionKey、encryptedData、iv」的方法来获取手机号的,解密的方法大致如下所示:
/**
* AES解密.
*
* @param sessionKey session_key
* @param encryptedData 消息密文
* @param ivStr iv字符串
*/
public static String decrypt(String sessionKey, String encryptedData, String ivStr) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(Base64.decodeBase64(ivStr)));
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params);
return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), UTF_8);
} catch (Exception e) {
throw new WxRuntimeException("AES解密失败!", e);
}
}
若在你的系统中仍然是采用这种方式来获取手机号的,那么在微信新版更新以后,在你的服务端不得不更改代码来进行微信新版的适配了
引入服务端最新的 pom 依赖,这是非常重要的一个变更
在这个依赖的版本中会适配上微信手机号验证组件新规的内容,如下:
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>weixin-java-miniappartifactId>
<version>${binarywang.version}version>
dependency>
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>weixin-java-openartifactId>
<version>${binarywang.version}version>
dependency>
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>weixin-java-mpartifactId>
<version>${binarywang.version}version>
dependency>
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>weixin-java-commonartifactId>
<version>${binarywang.version}version>
dependency>
以上包括了微信官方公共工具包、微信小程序、微信公众号、微信开放平台的依赖,引入以上的依赖,在我们的服务端内部就能任意的调用方法一样的去远程调用微信的官方 API 了
引入 binarywang 微信依赖的方式,不用我们再到代码中写一大堆的地址配置、AccessToken 认证及保存,它会帮我们再底层进行封装后。然后,对接过微信支付的小伙伴应该也知晓,以前调用微信支付接口的参数和返回的内容都是 XML 方式的,需要我们在项目中再通过 XmlStream 进行 XML 解析成 JSON 格式;最后,一个我喜欢的地方,之前返回的字段命名都是下划线隔开的,不是我熟悉的那种首字母小写驼峰命名规则,所以我们不得不在代码中进行 JSON 序列化时 + 上 @JsonProperty 注解,然而在这个依赖中这些问题都不需要我们考虑,我们只要安心的调用和处理返回的结果即可.
wx.mini.app.appId=xx
wx.mini.app.secret=xx
# 开发版
#wx.mini.app.envVersion=develop
# 体验版
#wx.mini.app.envVersion=trial
# 生产版
wx.mini.app.envVersion=release
wx.mini.app.homePagePath=pages/index/index
/**
* @author vnjohn
* @since 2023/7/11
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "wx.mini.app")
@PropertySource(value ={"classpath:wx-mini-app.properties"})
public class WxMaConfiguration {
private String appId;
private String secret;
private String envVersion;
private String homePagePath;
private static WxMaService WX_MA_SERVICE = null;
public static String WX_APP_ID;
public static String ENV_VERSION;
public static String HOME_PAGE_PATH;
public static WxMaService getWxMaService() {
return WX_MA_SERVICE;
}
@PostConstruct
public void init() {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(appId);
config.setSecret(secret);
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(config);
WX_MA_SERVICE = service;
ENV_VERSION = envVersion;
WX_APP_ID = appId;
HOME_PAGE_PATH = homePagePath;
}
}
在配置类中的初始化方法将微信小程序提供的服务配置类先进行初始化,这是以前我们会调用微信小程序 API 获取 AccessToken 的演变,后续在调用微信 API 时只需要通过调用 getWxMaService 方法即可.
在进行手机号验证组件源码前,微信授权是小程序用户信息的唯一判别标准,因为在授权接口中会返回 open_id,这个参数代表微信小程序用户的唯一标注,所以先来通过调用它实现结果解析
/**
* 通过小程序获取的Code 进行微信授权
*
* @param code 登录时获取的 code
* @return WxMaJscode2SessionResult
*/
public WxMaJscode2SessionResult wxAccredit(String code) {
// 参数校验
notEmpty(code, ResultMsgCodeEnum.ResultMsgCode.CODE_INVALID);
WxMaJscode2SessionResult wxMaJscode2SessionResult;
try {
// 微信登录
wxMaJscode2SessionResult = WxMaConfiguration.getWxMaService().getUserService().getSessionInfo(code);
} catch (WxErrorException e) {
log.error("wxAccredit 微信授权失败,", e);
return null;
}
log.info("wxAccredit 微信授权 {},", JsonUtils.objToJsonStr(wxMaJscode2SessionResult));
// 返回信息
return wxMaJscode2SessionResult;
}
wxAccredit 方法中的 code 参数是前端调用 wx.login 组件进行获取到的,调用实例如下:
wx.login({
success (res) {
if (res.code) {
// 向你的服务端授权接口发起网络请求
wx.request({
url: 'https://example.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
通过 wx.login 组件获取到 code 参数后,再调用服务端的微信授权接口,即可实现小程序的微信信息获取,服务端接口请求成功后返回的内容会有这些信息:
@SerializedName("session_key")
private String sessionKey;
@SerializedName("openid")
private String openid;
@SerializedName("unionid")
private String unionid;
sessionKey:参数代表当前会话的微信标识,它的有效期在一个会话内,当微信小程序那边通过组件调用 wx.login 后,该值就会发生改变,这个参数在服务端兼容旧版手机号验证会使用到,一般会在登录接口进行返回作为前端的全局参数以便在后续能够传递进行手机号绑定==
规避一个不好的系统设计:有的旧系统会使用这个参数来作为小程序登录的会话缓存,也就是说登录完成以后,后面所有的请求都通过这个参数值来进行服务端接口的请求,这样存在的问题是,该参数的有效期是由微信那边进行管理的,若你的系统在某些地方由于用户手动将小程序移除掉了或小程序退出超过了静默时间又或者是在某处重新调用了 wx.login,那这个值在微信那边就是属于无效的了,但是你的服务端还仍然在使用这个参数值进行别的请求处理,从而引起的报错你又浪费了很多时间,小程序登录会话值最好是由业务系统自行管理,这点功夫没必要省,后续只会有更多乏而无味的事情等你去收拾
openid:该参数代表当前小程序用户的唯一标识,一般在未认证手机号之前,openid 值就能作为小程序业务端的唯一标识,再说,手机号是可以更换的,若你用手机号作为唯一索引,包不准那天用户就把手机号换了,那你系统里面的信息都看不到了
unionid:该参数在普通的小程序主体下是不会返回的,只有当前主体有开通微信开放平台,并且将小程序绑定到了微信开放平台上,这个值才会在微信授权接口返回
微信小程序、微信公众号两个业务系统,唯一可以识别是同一个用户的,也就是靠这个值来进行区分了
/**
* 通过小程序获取 Code 进行微信授权获取手机号
*
* @param code 动态令牌
* @return WxMaPhoneNumberInfo
*/
public WxMaPhoneNumberInfo wxGetPhone(String code) {
// 参数校验
notEmpty(code, ResultMsgCodeEnum.ResultMsgCode.CODE_INVALID);
if (StringUtils.isEmpty(code)) {
log.info("小程序获取手机号 Code,参数为空");
return null;
}
// 解密手机号信息
WxMaPhoneNumberInfo phoneNoInfo;
try {
phoneNoInfo = WxMaConfiguration.getWxMaService().getUserService().getPhoneNoInfo(code);
} catch (WxErrorException e) {
log.error("wxGetPhone 微信获取手机号失败,", e);
return null;
}
log.info("wxGetPhone 微信获取手机号信息 {},", JsonUtils.objToJsonStr(phoneNoInfo));
// 返回信息
return phoneNoInfo;
}
在微信新版本获取手机号时,无论是快速还是实时验证组件,对于服务端都是一样的,都是只需要一个参数:code 即可了,返回该 code 在前端是这些进行处理的,源码如下:
快速验证组件
<u-button class="register" type="primary" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
去授权
u-button>
实时验证组件
<u-button class="register" type="primary" open-type="getRealtimePhoneNumber" @bindgetrealtimephonenumber="getPhoneNumber">
去授权
u-button>
getPhoneNumber 函数的处理过程如下:
// authPhone 是封装的,请求服务端的 API
const getPhoneNumber = (e) =>{
authPhone({
authMobileCode: e.detail.code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
sessionKey: userInfo.sessionKey
}).then(res =>{
changeStatus()
if(props.type === 'order') return
uni.navigateBack()
})
}
在用户的微信老、新的版本中,返回的参数会有所区别,可能在旧版本的微信中 code 参数是返回空的,此时在授权接口返回的 sessionKey 会话参数就有作用了,配合 encryptedData、iv 参数一起请求后端,进行手机号获取
从这里看出,服务端这块是跟随着小程序规则调整以后都需要进行代码更新的,在微信新版本中,前端只返回了 code 参数,服务端就必须接入 API 处理单个 code 参数获取手机号的业务逻辑
收费取决于在前端运用的组件是哪一种,在手机号验证的入口就已经牢牢给你捆绑住了,前后端都要进行相对应的适配和更新
在旧有系统的服务端 API 基于微信旧版本的用户还是仍然可以保留下来的,只是在前面介绍的基础配置中,github 微信组件库为我们提供了更加便利的方式调用,只是参数的变更,其他的返回参数的区别,该方法调用示例如下:
/**
* 通过小程序 SessionKey、encryptedData、iv 进行解密获取手机号
*
* @param sessionKey – 会话密钥
* @param encryptedData 消息密文
* @param iv 加密算法的初始向量
* @return WxMaPhoneNumberInfo
*/
public WxMaPhoneNumberInfo wxGetPhoneByIv(String sessionKey, String encryptedData, String iv) {
// 参数校验
if (StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(encryptedData) || StringUtils.isEmpty(iv)) {
log.info("解密获取手机号,参数为空");
return null;
}
// 解密手机号信息
WxMaPhoneNumberInfo phoneNumberInfo = WxMaConfiguration.getWxMaService().getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
log.info("wxGetPhoneByIv 微信获取手机号信息 {},", JsonUtils.objToJsonStr(phoneNumberInfo));
// 返回信息
return phoneNumberInfo;
}
微信新版本获取手机号的方法调用 wxGetPhone 方法即可,微信旧版本获取手机号的方法调用 wxGetPhoneByIv 方法即可.
从组件库源码中,发现该方法已经被丢弃,但是并不影响我们进行调用,我们仍然需要这种方式去进行微信旧版本的兼容处理!!
该篇博文介绍了微信从 8.28 起进行了微信获取手机号的变更,介绍了在微信官方的组件 API 调用示例以及在后台的付费管理收费方式概述,最重要是在源码章节中,采用了 Github 微信组件库的封装代码进行微信授权、新版|旧版微信手机号获取 API 调用,对微信接口返回的一些核心参数进行了详细的阐述以及在业务系统中需要注意的地方,避免在工作中偷一时的懒而影响后续的系统整改(使用微信 sessionKey 作为小程序用户缓存唯一标识)
愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!
博文放在 微信体系 专栏里,欢迎订阅,会持续更新!
如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
推荐专栏:Spring、MySQL,订阅一波不再迷路
大家的「关注❤️ + 点赞 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!