好久沒有寫點東西了,最近手里做了一個小小的H5項目,其中用到了微信jssdk。
一提到微信開發,大家肯定很容易想到微信那復雜的文檔,我也遇到了同樣的問題。
接入jssdk的過程是比較曲折的,所以在這里寫一篇文章記錄一下接入過程,也希望能夠給大家提供一點幫助。
一、微信官方文檔閱讀
首先,我們要接入微信jssdk,那么第一步就是要閱讀微信開發文檔:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
打開這個網頁,在左邊的菜單欄,依次點擊:微信網頁開發 - 微信JS-SDK說明文檔。
打開之后,我們就可以看到微信JS-SDK接入說明了。
概述,我就不多說了,我們直接來看使用步驟。
二、微信JSSDK使用步驟
微信JSSDK使用步驟有以下五步:
步驟一:綁定域名
這里需要我們先登錄微信公眾平台進入“公眾號設置”的“功能設置”里填寫“JS接口安全域名”。也就是這樣:
點擊設置之后,彈出這樣一個輸入框:
這里,大家可能就會有疑問了?需要備案的域名,這我怎么辦呢?別急,這里我交大家用內網穿透的方式,來搭建本地開發環境。
內網穿透傳送門:https://blog.csdn.net/RabitMountain/article/details/85298819
好了,大家實現了內網穿透,那么這個txt文件我們放到哪里呢?tomcat的默認訪問路徑是ROOT,大家直接丟進去就好,但要注意把原來的東西刪掉,要不然會因為示例中可能使用的springmvc造成請求轉發,進而無法訪問到這個txt文件。
步驟二:引入js文件
這個我也就不多說了,直接在你的網頁里引入js文件就行
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
步驟三:通過config接口注入權限驗證配置
這個是前端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,這個有點東西啊,但也別急,微信有文檔,我們慢慢看。
JS-SDK使用權限簽名算法
在生成簽名之前我們必須先了解一下jsapi_ticket。
jsapi_ticket是公眾號用於調用微信JS接口的臨時票據。正常情況下,jsapi_ticket的有效期為7200秒,通過access_token來獲取。由於獲取jsapi_ticket的api調用次數非常有限,頻繁刷新jsapi_ticket會導致api調用受限,影響自身業務,開發者必須在自己的服務全局緩存jsapi_ticket。
步驟如下:
1. 獲取access_token
我們據先獲取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;
}
}
2. 用access_token換取jsapi_ticket
用第一步拿到的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;
}
}
3. 簽名
獲得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
- 對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)后,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
- 對string1進行sha1簽名,得到signature:0f9de62fce790f9a083d5c99e95740ceb90c27ed
注意事項
- 簽名用的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']
})
}
});
});
步驟四:通過ready接口處理成功驗證
wx.ready(function(){
// config信息驗證后會執行ready方法,所有接口調用都必須在config接口獲得結果之后,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});
步驟五:通過error接口處理失敗驗證
wx.error(function(res){
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這里更新簽名。
});
三、總結
好了,基本接入的過程,也就上面那些了。
微信開發這塊,配置確實是個比較煩的事情,我也是花了好多天才成功配置起來,不是這有問題就是那有問題。
我也想通過這篇文章,來詳細系統的總結一下怎么接入jssdk,一方面方便自己以后查看,另一方面也希望能夠幫助到大家,少走彎路,畢竟網上的博文太多太多,東看看,西看看,就會比較混亂。
最后,新的一年,祝大家新年快樂,沖沖沖!!!
老樣子,歡迎大家!!!