1. 前言
微信小程序
开发平台,提供有一类 API
,可以让开发者获取到微信登录用户的个人数据。这类 API
统称为开放接口
。
Tip:微信小程序开发平台,会把微信登录用户的个人信息分为明文数据和敏感数据。
明文数据也称为公开数据,开发者可以直接获取到,如登录者的昵称、头像……
敏感数据如电话号码、唯一标识符……等数据,只有高级认证开发者和经过登录者授权后才能解密获取到。
这一类 API
较多,且 API
之间功能有重叠之处,相互之间的区别较微小。有的适用于低版本,有的适用于高版本。
为了避免在使用时出现选择混乱,本文将通过具体应用案例介绍几个常用 API
的使用。
2. 开放接口
开放接口
是对一类 API
的统称,开发者
可以通过调用这类接口得到微信登录用户的授权
或获取登录者的个人数据
。
开放接口
又分成几个子类 API
:
-
登录接口: 包括
wx.pluginLogin(Object args)
、wx.login(Object object)
、wx.checkSession(Object object)
几 个API
。 -
账号信息: 包括
Object wx.getAccountInfoSync()
此接口用来获取开发者的账号信息。 -
用户信息: 包括
wx.getUserProfile(Object object)
、wx.getUserInfo(Object object)
、UserInfo
。使用频率非常高的接口,常用于小程序中获取登录者个人公开数据。 -
授权接口:
wx.authorizeForMiniProgram(Object object)
、wx.authorize(Object object)
除上述列出的子类接口,还有收货地址、生物认证……等诸多子类 API
,有兴趣者可以自行了解。
2.1 登录接口
登录
接口中有 3
个 API
,对于开发者来说,使用频率较高的是 login
接口,此环节将重点介绍此接口。
非本文特别关注的接口,会简略带过。
wx.pluginLogin(Object args)
:此接口只能在插件中可以调用,调用此接口获得插件用户的标志凭证code
,插件可使用此凭证换取用于识别用户的唯一标识 OpenpId
。
用户不同、宿主小程序不同或插件不同的情况下,该标识均不相同,即当且仅当同一个用户在同一个宿主小程序中使用同一个插件时,OpenpId
才会相同。
对于一般开发者,此 接口用的不是很多,具体使用细节在此处也不做过多复述。
什么是 OpenId
?
当微信用户登录公众号或小程序时,微信平台为每一个微信登录者分配的一个唯一标识符号。
2.1.1 wx.login(Object object)
功能描述:
开发者使用此接口可以获取到微信登录者
的登录凭证(code)
。
登录凭证
具有临时性,也就是每次调用时都会不一样,所以code
只能使用一次。
开发者可以通过临时code
,再向微信接口服务器索取登录者的唯一标识符 OpenId
、微信开发平台账号的唯一标识 UnionID
(需要当前小程序已绑定到微信开放平台帐号)、以及会话密钥 session_key
。
那么,获取到的openId
和session_key
对于开发者而言,有什么实质性的意义?
-
根据
OpenId
的唯一性特点,可以在微信用户第一次登录时,把OpenID
保存在数据库或缓存中,在后续登录时,只需要检查用户的OpenId
是否存在于数据库或缓存中,便能实现自动登录功能。 -
session_key
也称会话密钥,用来解密微信登录者的敏感数据。
后文将详细介绍。
如何获取OpenId
?
现通过一个简单案例,实现微信小程序端与开发者服务器之间的数据交互。以此了解开发者服务器如何通过微信小程序传递过来的用户临时 code
换取到登录者的更多信息。
实现之前,先通过一个简易演示图了解其过程。
简单描述整个请求过程:
-
微信用户打开微信小程序后,开发者在微信小程序中通过调用
wx.login
接口获取到临时登录凭证code
。 -
在微信小程序中调用
wx.request
接口向开发者服务器发送http
请求,需要把登录凭证code
一并发送过去。 -
开发者服务器使用发送过来的
code
以及开发者凭证信息向微信接口服务器
索取微信登录者的openId
和session_key
。
简而言之,就是 3
者(微信小程序、开发者服务器、微信接口服务器)之间的一个击鼓传花游戏。
开发流程:
第一步:项目结构分析
完整的系统由 2
个部分组成:
微信小程序端 APP
。
如对微信小程序开发不是很了解,请先阅读官方提供的相关文档。
服务器端应用程序。
本文的服务器端应用程序基于 Spring Boot
开发平台。
本项目结构是标准的前后端分离模式,微信小程序是前端应用,服务器端应用程序为后台应用。
第二步:新建微信小程序(前端应用)
打开微信开发工具,新建一个名为 guokeai
的小程序项目 ,项目会初始化一个index
页面。在 index.js
中编写如下代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
//index.js const app = getApp() const httpRequest = require( "../../utils/request.js" ) Page({ data: { isHasUserInfo: null , userInfo: null }, //启动时 onLoad: function () { let this_ = this /*** * 检查微信用户是否已经登录到后台服务器 * 已经登录的标志,数据库中存在 OPENID */ let code = null //调用 login 接口 wx.login({ success: (res) => { //得到登录用户的临时 code code = res.code //向开发者服务器发送请求 let api = "wx/getLoginCertificate" let config = { url: api, method: "GET" , data: { code: code } } let promise = httpRequest.wxRequest(config) promise.then(res => { let isHas = null // 有没有完整的微信登录者信息 isHas = res.data == 0 ? false : true app.globalData.isHasUserInfo = isHas this_.setData({ isHasUserInfo: isHas }) }). catch (res => { console.log( "fail" , res) }); } }) } }) |
代码解释:
-
一般会在微信小程序启动时,也就是在页面
onload
函数中调用wx.login
接口,检查用户是否登录过。 -
http://127.0.0.1:8080/wx/getLoginCertificate
是开发者服务器
提供的对外处理微信用户信息的接口。 -
最后只是简单地输出开发者服务器端返回的数据。
httpRequest.wxRequest(config)
是自定义的封装wx.request
接口的请求组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
function wxRequest(config) { //返回的数据类型 let dataType = config.dataType == null ? "json" : config.dataType; let responseType = config.responseType == null ? "text" : config.responseType; //服务器基地址 let serverUrl = "http://127.0.0.1:8080/" //超时 let timeout = config.timeout == null ? 50000 : config.timeout; //目标地址,基地址+接口 let url = serverUrl + config.url; //数据提交方式 let method = config.method == null ? "GET" : config.method; //提交数据 let data = config.data == null ? null : config.data //头信息 let header = { // 默认值 'content-type' : 'application/json' , 'x-requested-with' : 'XMLHttpRequest' } let sessionId = wx.getStorageSync( 'sessionId' ) if (sessionId) { header[ "cookie" ] = sessionId } return new Promise( function (resolve, reject) { wx.request({ url: url, data: data, //返回的数据类型(json) dataType: dataType, enableCache: false , enableHttp2: false , enableQuic: false , method: method, header: header, responseType: responseType, timeout: timeout, success: (res) => { console.log( "requestData" , res) if (res.cookies != null && res.cookies.length != 0) wx.setStorageSync( 'sessionId' , res.cookies[0]) resolve(res) }, fail: (res) => { console.log( "requestException" , res) reject(res) } }) }) } |
第三步:创建开发者服务器程序(后台应用)
本文使用 spring boot
快速搭建后台应用程序。在项目的 pom.xml
文件中除了必要的依赖包外,还需要添加以下 的依赖包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
< dependency > < groupId >com.alibaba</ groupId > < artifactId >fastjson</ artifactId > < version >1.2.73</ version > </ dependency > < dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > < version >4.5.13</ version > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < scope >runtime</ scope > </ dependency > < dependency > < groupId >org.projectlombok</ groupId > < artifactId >lombok</ artifactId > < optional >true</ optional > </ dependency > < dependency > < groupId >com.baomidou</ groupId > < artifactId >mybatis-plus-boot-starter</ artifactId > < version >3.5.1</ version > </ dependency > |
fastjson
是阿里云
提供的开源 JSON
解析框架。
微信小程序
和开发者服务器
构建的项目结构,是标准的前后端分离模式。
请求与响应时,数据交互常使用JSON
格式。这时使用 fastjson
作为json
解析器,当然,也可以选择其它的类似解析器。
-
httpclient
是一个http
请求组件。 -
mysql-connector-java
本文案例使用MySQL
数据库,需要加载相应的驱动包。 -
mybatis-plus-boot-starter
,mybatis-plus
依赖包。
在后台应用中编写处理器(响应)组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@RestController @RequestMapping ( "/wx" ) public class WxAction { @Autowired private IWxService wxService; /*** * 获取到微信用户的 OPENID */ @GetMapping ( "/getLoginCertificate" ) public String getLoginCertificate( @RequestParam ( "code" ) String code) throws Exception { WxUserInfo wxInfo = this .wxService.getLoginCertificate(code); //用户不存在,或者用户的信息不全 return wxInfo== null || wxInfo.getNickName()== null ? "0" : "1" ; } |
代码解释:
IWxService
是处理器依赖的业务组件,提供有 getLoginCertificate()
方法用来实现通过code
向微信接口服务器
换取微信登录者的 openId
和session_key
。
编写业务组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Service public class WxService implements IWxService { @Override public WxUserInfo getLoginCertificate(String code) throws Exception { //请求地址 String requestUrl = WxUtil.getWxServerUrl(code); // 发送请求 String response = HttpClientUtils.getRequest(requestUrl); //格式化JSON数据 WxUserInfo wxUserInfo = JSONObject.parseObject(response, WxUserInfo. class ); //检查数据库中是否存在 OPENID WxUserInfo wxUserInfo_ = this .wxUserMapper.selectById(wxUserInfo.getOpenId()); if (wxUserInfo_ == null ) { //数据库中没有用户的 OPENID,添加到数据库中 this .wxUserMapper.insert(wxUserInfo); } else { if (!wxUserInfo.getSessionKey().equals(wxUserInfo_.getSessionKey())) { //如果数据库保存的session_key和最新的session_key 不相同,则更新 wxUserInfo_.setSessionKey(wxUserInfo.getSessionKey()); this .wxUserMapper.updateById(wxUserInfo_); } } return wxUserInfo_; } } |
代码解释:
-
WxUtil
是自定义的一个工具组件,用来构建请求微信接口服务器
的url
。
https://api.weixin.qq.com/sns/jscode2session
是微信接口服务器
对外提供的接口,请求此接口时,需要提供 4
个请求数据。
appid
:小程序 appId。
secret
:小程序 appSecret。
js_code
:获取到的微信登录者的临时 code
。
grant_type
:授权类型,此处只需填写 authorization_code
。
1
2
3
4
5
6
7
8
9
10
|
public class WxUtil { private final static String APP_ID = "微信小程序开发者申请的 appid" ; private final static String APP_SECRET = "微信小程序开发者申请的 APP_SECRET" ; // private final static String WX_LOGIN_SERVER_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code" ; public static String getWxServerUrl(String code) throws IOException { String url = MessageFormat.format(WX_LOGIN_SERVER_URL, new String[]{APP_ID, APP_SECRET, code}); return url; } } |
HttpClientUtils
也是一个自定义组件,用来向指定的服务器发送 http
请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class HttpClientUtils { /** * GET请求 */ public static String getRequest(String url) throws Exception { //HttpClient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null ; try { HttpGet httpGet = new HttpGet(url); response = httpClient.execute(httpGet); //响应体 HttpEntity entity = response.getEntity(); if (entity != null ) { //格式化响应体 return EntityUtils.toString(entity); } } catch (ClientProtocolException e) { throw e; } catch (IOException e) { throw e; } finally { response.close(); httpClient.close(); } return null ; } } |
WxUserInfo
是自定义的数据封装类。微信接口服务器
返回的数据是以JSON
格式组装的,这里需要格式成对象数据,便于在 java
中处理。本文使用 MyBatisPlus
操作数据库,此类也对应数据库中的gk_wx_user
表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@Data @AllArgsConstructor @NoArgsConstructor @TableName ( "gk_wx_user" ) public class WxUserInfo { //OPEN_id @TableId (type = IdType.ASSIGN_ID, value = "open_id" ) private String openId; //会话密钥 @TableField (value = "session_key" ) private String sessionKey; //头像路径 @TableField ( "avatar_url" ) private String avatarUrl; //城市 private String city; //国家 private String country; //性别 private String gender; //语言 private String language; //昵称 @TableField ( "nick_name" ) private String nickName; //备注名或真实名 @TableField ( "real_name" ) private String realName; //省份 private String province; //学生ID @TableField ( "stu_id" ) private Integer stuId; } |
MyBatis 数据库映射组件:
1
2
3
|
@Repository public interface WxUserMapper extends BaseMapper<WxUserInfo> { } |
第四步:测试。
先启动后台应用程序,再启动微信小程序,可以在数据库表中查看到如下信息。
微信用户的openid
和session_key
已经保存到后台的数据库表中。
2.1.2 wx.checkSession(Object object)
官方文档中,有一段对 session_key
的生命周期的描述。
-
session_key
的生命周期有不确定性,可以使用wx.login
接口刷新session_key
。为了避免频繁调用wx.login
接口,可以通过调用wx.checkSession(Object object)
接口判断session_key
是否已经过期。 -
当开发者在实现自定义登录态时,可以考虑以
session_key
有效期作为自身登录态有效期,也可以实现自定义的时效性策略。
wx.checkSession
的功能,可以使用此接口判断session_key
是否过期。
-
调用成功说明当前
session_key
未过期。 -
调用失败说明
session_key
已过期。
2.2 用户信息接口
wx.login
接口仅能获取到微信登录者的有限数据,如果想要获取到登录者的更多个人信息,可以使用用户信息接口中的相关API
。
-
wx.getUserProfile(Object object)
。获取用户信息,页面产生点击事件(例如button
上bindtap
的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回userInfo
。 -
wx.getUserInfo(Object object)
。和wx.getUserProfile
的功能一样,在基础库 2.10 的后续版本中,其功能已经被削弱。 -
UserInfo
是用户信息封装类。
getUserProfile
是从 基础库2.10.4
版本开始支持的接口,该接口用来替换 wx.getUserInfo
,意味着官方不建议再使用getUserInfo
接口获取用户的个人信息。
下图是官方提供的 2
个接口的功能对比图。
为了避免频繁弹窗,可以在第一次获取到用户信息后保存在数据库中以备以后所用。为了获取到用户的敏感数据,在后台要通过getUserProfile
接口所获取的数据进行解密操作。
2.2.2 wx.getUserProfile
下面通过具体代码讲解如何保存微信登录者的个人数据。先了解一下整个数据获取的流程,这里直接截取官方提供的一张流程图。
获取微信登录者的个人信息,需要经过 2
个步骤。
签名效验:
-
通过调用
wx.getUserProfile
接口获取数据时,接口会同时返回rawData
、signature
,其中signature = sha1( rawData + session_key )
。 -
开发者将
signature
、rawData
发送到开发者服务器进行校验。服务器利用用户对应的session_key
使用相同的算法计算出签名signature2
,比对signature
与signature2
即可校验数据的完整性。
解密加密数据:
-
对称解密使用的算法为
AES-128-CBC
,数据采用PKCS#7
填充。 -
对称解密的目标密文为
Base64_Decode(encryptedData)
。 -
对称解密秘钥
aeskey = Base64_Decode(session_key)
,aeskey
是16
字节。 -
对称解密算法初始向量 为
Base64_Decode(iv)
,其中iv
由数据接口返回。
具体编写实现。
第一步:在微信小程序端编码。
在index.wxml
页面中添加一个按钮,并注册bindtap
事件。
1
2
3
|
<view> <button bindtap= "getUserProfile" >获取用户数据</button> </view> |
在index.js
中添加一个名为getUserProfile
的事件回调函数。为了避免不必要的弹窗,只有当后台没有获取到个人数据时,才调用wx.getUserProfile
接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
getUserProfile: function (e) { let this_ = this if (! this .data.isHasUserInfo) { //如果服务器端没有保存完整的微信登录者信息 wx.getUserProfile({ desc: '需要完善您的资料!' , success: (res) => { this_.setData({ //小程序中用来显示个人信息 userInfo: res.userInfo, isHasUserInfo: true }) //再次登录,因为 session_key 有生命中周期 wx.login({ success(res_) { //保存到服务器端 let config = { url: "wx/wxLogin" , method: "GET" , data: { code: res_.code, //明文数据 rawData: res.rawData, //加密数据 encryptedData: res.encryptedData, iv: res.iv, //数字签名 signature: res.signature } } let promise = httpRequest.wxRequest(config) promise.then(res => { //返回 console.log( "wxLogin" , res) }). catch (res => { console.log( "fail" , res) }); } }) } }) } } |
服务器端代码:
在pom.xml
文件中添加如下依赖包,用来解密数据。
1
2
3
4
5
6
7
8
9
10
|
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.46</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> |
在处理器类WxAction
中添加wxLogin
响应方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@RestController @RequestMapping ( "/wx" ) public class WxAction { @Autowired private IWxService wxService; /*** * * @param code * @param rawData * @param encryptedData * @param iv * @param signature * @return * @throws Exception */ @GetMapping ( "/wxLogin" ) public WxUserInfo wxLogin( @RequestParam ( "code" ) String code, @RequestParam ( "rawData" ) String rawData, @RequestParam ( "encryptedData" ) String encryptedData, @RequestParam ( "iv" ) String iv, @RequestParam ( "signature" ) String signature) throws Exception { WxUserInfo wxInfo = this .wxService.getWxUserInfo(code, rawData, encryptedData, iv, signature); return wxInfo; } } |
业务代码:
小程序中传递过来的数据是经过base64
编码以及加密的数据,需要使用 Base64
解码字符串,再使用解密算法解密数据。先提供一个解密方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public String decrypt(String session_key, String iv, String encryptData) { String decryptString = "" ; //解码经过 base64 编码的字符串 byte [] sessionKeyByte = Base64.getDecoder().decode(session_key); byte [] ivByte = Base64.getDecoder().decode(iv); byte [] encryptDataByte = Base64.getDecoder().decode(encryptData); try { Security.addProvider( new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS7Padding" ); //得到密钥 Key key = new SecretKeySpec(sessionKeyByte, "AES" ); //AES 加密算法 AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance( "AES" ); algorithmParameters.init( new IvParameterSpec(ivByte)); cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters); byte [] bytes = cipher.doFinal(encryptDataByte); decryptString = new String(bytes); } catch (Exception e) { e.printStackTrace(); } return decryptString; } |
具体获取数据的业务实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Override public WxUserInfo getWxUserInfo( @NotNull String code, @NotNull String rawData, @NotNull String encryptedData, @NotNull String iv, @NotNull String signature) throws Exception { //会话密钥 WxUserInfo wxUserInfo = this .getLoginCertificate(code); String signature2 = DigestUtils.sha1Hex(rawData + wxUserInfo.getSessionKey()); if (!signature.equals(signature2)) { throw new Exception( "数字签名验证失败" ); } //数字签名验证成功,解密 String infos = this .decrypt(wxUserInfo.getSessionKey(), iv, encryptedData); //反序列化 JSON 数据 WxUserInfo wxUserInfo_ = JSONObject.parseObject(infos, WxUserInfo. class ); wxUserInfo_.setSessionKey(wxUserInfo.getSessionKey()); wxUserInfo_.setOpenId(wxUserInfo.getOpenId()); //更新数据库 this .wxUserMapper.updateById(wxUserInfo_); return wxUserInfo_; } |
测试,启动微信小程序和后台应用,在小程序中触发按钮事件。
在弹出的对话框中,选择允许。
查看后台数据库表中的数据。
能够获取到的微信登录者个人信息都保存到了数据库表中。至于怎么使用这些数据,可以根据自己的业务需要定制。
3.总结
微信开发平台,提供有诸多接口,可以帮助开发者获取到有用的数据。本文主要介绍 wx.login
和wx.getProfile
接口,因篇幅所限,不能对其它接口做详细介绍 ,有兴趣者可以查阅官方文档。
官方文档只会对接口功能做些介绍 ,如要灵活运用这些接口,还需要结合实际需要演练一下,如此方能有切身体会。
到此这篇关于Spring Boot+微信小程序开发平台保存微信登录者的个人信息的文章就介绍到这了,更多相关springboot微信小程序内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://www.cnblogs.com/guo-ke/p/16276843.html