很久沒有寫點東西了,最近手裏作了一個小小的H5項目,其中用到了微信jssdk。javascript
一提到微信開發,你們確定很容易想到微信那複雜的文檔,我也遇到了一樣的問題。html
接入jssdk的過程是比較曲折的,因此在這裏寫一篇文章記錄一下接入過程,也但願可以給你們提供一點幫助。前端
首先,咱們要接入微信jssdk,那麼第一步就是要閱讀微信開發文檔:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432java
打開這個網頁,在左邊的菜單欄,依次點擊:微信網頁開發 - 微信JS-SDK說明文檔。web
打開以後,咱們就能夠看到微信JS-SDK接入說明了。ajax
概述,我就很少說了,咱們直接來看使用步驟。算法
微信JSSDK使用步驟有如下五步:spring
這裏須要咱們先登陸微信公衆平臺進入「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」。也就是這樣:apache
點擊設置以後,彈出這樣一個輸入框:json
這裏,你們可能就會有疑問了?須要備案的域名,這我怎麼辦呢?別急,這裏我交你們用內網穿透的方式,來搭建本地開發環境。
內網穿透傳送門:https://blog.csdn.net/RabitMountain/article/details/85298819
好了,你們實現了內網穿透,那麼這個txt文件咱們放到哪裏呢?tomcat的默認訪問路徑是ROOT,你們直接丟進去就好,但要注意把原來的東西刪掉,要否則會由於示例中可能使用的springmvc形成請求轉發,進而沒法訪問到這個txt文件。
這個我也就很少說了,直接在你的網頁裏引入js文件就行
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
這個是前端html頁面中須要咱們處理的,就是經過wx.config()注入欸之信息,要否則就會沒法調用其餘接口。
wx.config({ debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。 appId: '', // 必填,公衆號的惟一標識 timestamp: , // 必填,生成簽名的時間戳 nonceStr: '', // 必填,生成簽名的隨機串 signature: '',// 必填,簽名 jsApiList: [] // 必填,須要使用的JS接口列表 });
這些東西,除了jsApiList,咱們都須要從後端獲取,那咱們就向後臺發起一個請求,而後後臺封裝一下返回給前端是否是就行了,很簡單吧。
appId簡單,直接從微信後臺拿來用就行。
timestamp,後臺直接生成就行,但要注意要以秒爲單位。
nonceStr,java後臺隨便使用個uuid就能夠了。
signature,這個有點東西啊,但也別急,微信有文檔,咱們慢慢看。
在生成簽名以前咱們必須先了解一下jsapi_ticket。
jsapi_ticket是公衆號用於調用微信JS接口的臨時票據。正常狀況下,jsapi_ticket的有效期爲7200秒,經過access_token來獲取。因爲獲取jsapi_ticket的api調用次數很是有限,頻繁刷新jsapi_ticket會致使api調用受限,影響自身業務,開發者必須在本身的服務全局緩存jsapi_ticket。
步驟以下:
咱們據先獲取access_token:思路很簡單,就是咱們用代碼向微信服務器發一個請求,來換取它返回的access_token就好了。在寫代碼直接,咱們還要添加ip白名單。這是由於微信規定,經過開發者ID及密碼調用獲取access_token接口時,須要設置訪問來源IP爲白名單。
添加了這個以後,咱們來看一下代碼:
WechatUtil.java
import com.alibaba.fastjson.JSONObject; import java.security.MessageDigest; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class WechatUtil { private static String appId = "xxxxxxxxxx"; private static String appSecret = "xxxxxxxxxxxxxxxxxxxx"; public static JSONObject getAccessToken() { String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appId=APPID&secret=APPSECRET"; String requestUrl = accessTokenUrl.replace("APPID", appId).replace("APPSECRET", appSecret); return WebUtil.doGet(requestUrl); } // 後面還有部分代碼 }
WebUtil.java
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; /** * Web相關工具類 */ public class WebUtil { /** * 發起http get請求 */ public static JSONObject doGet(String requestUrl) { CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String responseContent = null; JSONObject result = null; try { HttpGet httpGet = new HttpGet(requestUrl); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); responseContent = EntityUtils.toString(entity, "UTF-8"); result = JSON.parseObject(responseContent); } catch (IOException e) { System.out.println("HTTP請求異常:" + e.getMessage()); } return result; } }
用第一步拿到的access_token 採用http GET方式請求得到jsapi_ticket(有效期7200秒,開發者必須在本身的服務全局緩存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
成功返回以下JSON:
{ "errcode":0, "errmsg":"ok", "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA", "expires_in":7200 }
這個就要我用access_token再向微信服務器發起請求,而後得到ticket就好了。可是,這個上面說了,調用次數有限,須要咱們緩存,這裏咱們直接採用單例來緩存。
TokenSingleton.java
import com.alibaba.fastjson.JSONObject; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 使用單例模式access_token全局緩存 */ public class TokenSingleton { private Map<String, String> map = new HashMap<>(); // 緩存accessToken的Map, map中包含一個accessToken和緩存的時間戳 private TokenSingleton() { } private static TokenSingleton single = null; public static TokenSingleton getInstance() { if (single == null) { single = new TokenSingleton(); } return single; } public Map<String, String> getMap() { String time = map.get("time"); String accessToken = map.get("access_token"); long nowDate = new Date().getTime() / 1000; if (accessToken != null && time != null && nowDate - Long.parseLong(time) < 5000 * 1000) { System.out.println("access_token存在,還沒有超時,返回單例!"); } else { System.out.println("access_token超時或者不存在,從新獲取!"); JSONObject jsonObject = WechatUtil.getAccessToken(); String newAccessToken = jsonObject.getString("access_token"); System.out.println("new access_token = " + newAccessToken); String jsApiTicket = getJsApiTicket(newAccessToken); map.put("time", nowDate + ""); map.put("access_token", newAccessToken); map.put("jsapi_ticket", jsApiTicket); } return map; } public String getJsApiTicket(String accessToken) { String apiTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken); System.out.println("getJsApiTicket.requestUrl ====> " + requestUrl); JSONObject result = WebUtil.doGet(requestUrl); System.out.println("getHsApiTicket.response ====> " + result); String jsApiTicket = null; if (null != result) { jsApiTicket = result.getString("ticket"); } return jsApiTicket; } }
得到jsapi_ticket以後,就能夠生成JS-SDK權限驗證的簽名了。微信上給出的簽名算法以下:
參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對全部待簽名參數按照字段名的ASCII碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這裏須要注意的是全部參數名均爲小寫字符。對string1做sha1加密,字段名和字段值都採用原始值,不進行URL 轉義。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYTPz0wzccnW jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg timestamp=1414587457 url=http://mp.weixin.qq.com?params=value
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
注意事項
- 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
- 簽名用的url必須是調用JS接口頁面的完整URL。
- 出於安全考慮,開發者必須在服務器端實現簽名的邏輯。
總結來看,就是按順序組裝參數,用SHA-1加密一下就好了。固然,這裏爲了先後端交互,咱們直接把全部須要的參數封裝起來,到時候經過controller直接返回給前端。代碼以下:
import com.alibaba.fastjson.JSONObject; import java.security.MessageDigest; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class WechatUtil { // 接上面的access_token獲取代碼 public static Map<String, String> generateWxTicket(String jsApiTicket, String url) { Map<String, String> ret = new HashMap<>(); String nonceStr = createNonceStr(); String timestamp = createTimestamp(); String string1; String signature = ""; string1 = "jsapi_ticket=" + jsApiTicket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + url; System.out.println("string1 ====> " + string1); try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); System.out.println("signature ====> " + signature); } catch (Exception e) { e.printStackTrace(); } ret.put("url", url); ret.put("jsapi_ticket", jsApiTicket); ret.put("nonceStr", nonceStr); ret.put("timestamp", timestamp); ret.put("signature", signature); ret.put("appid", appId); return ret; } /** * 字節數組轉換爲十六進制字符串 * * @param hash 字節數組 * @return 十六進制字符串 */ private static String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; } /** * 生成隨機字符串 * * @return 隨機字符串 */ private static String createNonceStr() { return UUID.randomUUID().toString(); } /** * 生成時間戳 * * @return 時間戳 */ private static String createTimestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
接下來咱們用springmvc組裝一個控制器,來接受前端的請求。
WechatController.java
import com.hbwomen.util.TokenSingleton; import com.hbwomen.util.WechatUtil; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/wechat") public class WechatController { @PostMapping("/config") @ResponseBody public Map<String, String> config(HttpServletRequest request) throws UnsupportedEncodingException { String signUrl = request.getParameter("signUrl"); Map<String, String> ret = new HashMap<>(); TokenSingleton tokenSingleton = TokenSingleton.getInstance(); Map<String, String> map = tokenSingleton.getMap(); String jsapi_ticket = map.get("jsapi_ticket"); // String newUrl = URLDecoder.decode(signUrl, "UTF-8"); ret = WechatUtil.generateWxTicket(jsapi_ticket, signUrl); return ret; } }
這裏咱們在補上前端的代碼,就能夠進行config了。
$(function () { var signUrl = window.location.href.split('#')[0]; $.ajax({ url: "/wechat/config", method: "post", data: { signUrl: signUrl }, success: function (data) { console.log("wx.config() ---> 接收後臺返回的參數"); wx.config({ debug: true, appId: data.appid, timestamp: data.timestamp, nonceStr: data.nonceStr, signature: data.signature, jsApiList: ['onMenuShareAppMessage'] }) } }); });
wx.ready(function(){ // config信息驗證後會執行ready方法,全部接口調用都必須在config接口得到結果以後,config是一個客戶端的異步操做,因此若是須要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則能夠直接調用,不須要放在ready函數中。 });
wx.error(function(res){ // config信息驗證失敗會執行error函數,如簽名過時致使驗證失敗,具體錯誤信息能夠打開config的debug模式查看,也能夠在返回的res參數中查看,對於SPA能夠在這裏更新簽名。 });
好了,基本接入的過程,也就上面那些了。
微信開發這塊,配置確實是個比較煩的事情,我也是花了好多天才成功配置起來,不是這有問題就是那有問題。
我也想經過這篇文章,來詳細系統的總結一下怎麼接入jssdk,一方面方便本身之後查看,另外一方面也但願可以幫助到你們,少走彎路,畢竟網上的博文太多太多,東看看,西看看,就會比較混亂。
最後,新的一年,祝你們新年快樂,沖沖衝!!!
老樣子,歡迎你們!!!