相信不少喜歡開發的朋友都已經知道微信小程序是個什么物種了,樓主也是從小程序內測期間就開始關注,并且也寫過幾個已經上線的微信小程序。但是基本上都是寫的純前端,最近樓主從后端到前端寫一個完整的小程序項目,中間碰到了一些問題,樓主會找一些個人覺得有學習價值的點不定時的拿出來跟大家分享,希望對你有一些幫助。
本次就從最基本的微信小程序登錄態維護開始吧。小程序官方api文檔里面有對登錄態的一個完整的解釋,并且有相關的代碼。想看詳情,可以出門右轉:https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html#wxloginobject 我第一次看的時候沒怎么看懂,并且代碼沒有提供java版本的,這讓一個java程序員情何以堪,所以在努力研究了以后決定要做一個java版本的簡單的demo放出來。
作為服務端,如果想獲得到使用微信小程序的會員信息,就需要小程序作為客戶端把會員的基本信息傳過來。類似于手機號,openId可以作為當前小程序中用戶的唯一性標志。然而如果把會員的openId信息明文直接在服務端與小程序端來回傳輸的話,會有安全性的問題。萬一被別人得到這個openId,就相當于得到會員的手機號一樣,就可以做一些其他操作了,顯然是不安全的。
為了解決這一問題微信采用了相對安全的方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//app.js App({ onLaunch: function () { wx.login({ success: function (res) { if (res.code) { //發起網絡請求 wx.request({ url: 'https://test.com/onLogin' , data: { code: res.code } }) } else { console.log( '獲取用戶登錄態失敗!' + res.errMsg) } } }); } }) |
微信小程序端會調用wx.login的api,然后會得到一個code,這個code對外人來講是沒有任何意義的,可以放心的傳給服務端。服務端得到code以后,加上你申請小程序時的appId, app secret,去調微信的接口
就可以得到以下參數:
- openid 用戶唯一標識
- session_key 會話密鑰
- unionid 本字段在滿足一定條件的情況下才返回
其中openid 就是會員的唯一性標記,此時服務端可以保存下來。
session_key 以后解密 unionId(整個開放平臺會員的唯一性標識)時有用。
服務端得到openid以后,為了后邊的交互,要保存下來。一般來講有兩種方式:一種是直接入數據庫,一種是采用效率高一點的緩存。樓主采用的是后者,方式是redis。
按照微信的建議此時需要生成一個不重復值作為openId的唯一性標識。這里采用的是java的uuid。然后把這個uuid值作為key,把openid以及后面會用到的session_key作為value,存進redis。并且把uuid值返回給小程序。這樣小程序就可以直接拿uuid值跟服務端交互。
也許會有人問,如果有人得到uuid值其實跟得到openid沒什么區別啊,都相當于是會員的唯一性標志。
所以這里要對這個uuid值進行一個處理。首先存入redis時要有時效性。session_key在微信服務器有效期是30天,建議服務端緩存session_key不超過30天。當小程序傳過來的uuid值過期時,認為這是過期的uuid,則重新走wx.login步驟。
為了方便redis中不僅會寸uuid與openid的對應關系。還會再存一條openid對應uuid的記錄,目的是為了下一次重新wx.login步驟時根據openid找到之前老的uuid,如果存在的話就刪掉,然后查詢一條新的uuid值,并且把openid對應的這條記錄也更新掉。這樣redis服務器中就不會有多余的臟數據,減輕服務器的負擔。
以上就是我理解的整個登錄態的過程,當然還有wx.checkSession這些沒有講到,其實就是發現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
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
@ActionKey ( "/loginByWeixin" ) public void loginByWeixin() throws Exception { logger.info( "Start getSessionKey" ); String json = HttpKit.readData(getRequest()); JSONObject reqJson = JSON.parseObject(json); String jsCode = reqJson.getString( "code" ); if (jsCode == null || "" .equals(jsCode)) { logger.info( "缺少必要參數" ); renderJson( new OutRoot().setCode( "100" ).setMsg(SYS.PARAMETER_FAIL)); } else { List<Record> record = appInfoService.selectAppInfo(); String appId = record.get( 0 ).get( "app_id" ); String appSecret = record.get( 0 ).getStr( "app_secret" ); if (appId == null || "" .equals(appId) || appSecret == null || "" .equals(appSecret)) { logger.info( "缺少必要參數" ); renderJson( new OutRoot().setCode( "100" ).setMsg(SYS.PARAMETER_FAIL)); } else { String url = "https://api.weixin.qq.com/sns/jscode2session" ; String httpUrl = url + "?appid=" + appId + "&secret=" + appSecret + "&js_code=" + jsCode + "&grant_type=authorization_code" ; String ret = HttpRequest.sendGetRequest(httpUrl); logger.info( "微信返回的結果 {}" , ret); if (ret == null || "" .equals(ret)) { logger.info( "網絡超時" ); renderJson( new OutRoot().setCode( "101" ).setMsg(SYS.CONTACT_FAIL)); } else { JSONObject obj = JSONObject.parseObject(ret); if (obj.containsKey( "errcode" )) { String errcode = obj.get( "errcode" ).toString(); logger.info( "微信返回的錯誤碼{}" , errcode); renderJson( new OutRoot().setCode( "101" ).setMsg(SYS.CONTACT_FAIL)); } else if (obj.containsKey( "session_key" )) { logger.info( "調微信成功" ); // 開始處理userInfo String openId = obj.get( "openid" ).toString(); Record tbMember = new Record(); tbMember.set( "weixin_openid" , openId); System.out.println( "openId==" + openId); // 先查詢openId存在不存在,存在不入庫,不存在就入庫 List<Record> memberList = tbMemberService.selectMember(tbMember); if (memberList != null && memberList.size() > 0 ) { logger.info( "openId已經存在,不需要插入" ); } else { JSONObject rawDataJson = reqJson.getJSONObject( "userInfo" ); String nickName = rawDataJson.getString( "nickName" ); String avatarUrl = rawDataJson.getString( "avatarUrl" ); String gender = rawDataJson.getString( "gender" ); String province = rawDataJson.getString( "province" ); String city = rawDataJson.getString( "city" ); String country = rawDataJson.getString( "country" ); tbMember.set( "gender" , gender); tbMember.set( "nick_name" , nickName); tbMember.set( "avatar_url" , avatarUrl); Long openId2 = tbMemberService.addMember(tbMember); logger.info( "openId不存在,插入數據庫" ); } // (1) 獲得sessionkey String sessionKey = obj.get( "session_key" ).toString(); logger.info( "sessionKey==" + sessionKey); logger.info( "openId==" + openId); // (2) 得到sessionkey以后存到緩存,key值采用不會重復的uuid String rsession = UUID.randomUUID().toString(); Cache tokenCache = Redis.use( "redis_00" ); // (3) 首先根據openId,取出來之前存的openId對應的sessionKey的值。 String oldSeesionKey = tokenCache.getJedis().get(openId); if (oldSeesionKey != null && ! "" .equals(oldSeesionKey)) { logger.info( "oldSeesionKey==" + oldSeesionKey); // (4) 刪除之前openId對應的緩存 tokenCache.getJedis().del(oldSeesionKey); logger.info( "老的openId刪除以后==" + tokenCache.getJedis().get(oldSeesionKey)); } // (5) 開始緩存新的sessionKey: key --> uuid, value --> sessionObj JSONObject sessionObj = new JSONObject(); sessionObj.put( "openId" , openId); sessionObj.put( "sessionKey" , sessionKey); tokenCache.getJedis().set(rsession, sessionObj.toJSONString()); // (6) 開始緩存新的openId與session對應關系 : key --> openId , value --> rsession tokenCache.getJedis().set(openId, rsession); String newOpenId = tokenCache.getJedis().get(openId); String newrSession = tokenCache.getJedis().get(rsession); logger.info( "新的openId==" + newOpenId); logger.info( "新的newrSession==" + newrSession); // (7) 把新的sessionKey返回給小程序 JSONObject objret = new JSONObject(); objret.put( "rdSessionKey" , rsession); objret.put( "errno" , 0 ); renderJson(objret); } } } } } |
項目框架是我比較喜歡Jfinal,java輕量級急速開發框架,非常高效,也推薦給大家。可能有哪些遺漏的地方歡迎大家積極提出意見和批評。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.jianshu.com/p/19c5b3931b0e?utm_source=tuicool&utm_medium=referral