需求描述:公司通過APP產(chǎn)品分享出去的需求和簡歷是做了一個H5頁面作為分享的鏈接,通過APP分享出去自然是沒問題,也是第一次分享,之后通過微信打開H5頁面后想再次分享出去時候就變成了一個鏈接了,而不是自己定制的卡片模式,初次分享后如下:



但是打開以后的H5頁面再分享出去就變成這個樣子了:




也就是說需要在H5頁面做微信分享的相關工作,JS-SDK上場了,首先看看JS-SDK的官方說明文檔:https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
按照文檔說明一步一步的做下去就可以出結果了,在這里詳細說一下每一步如何操作以及如何避坑,重點在于如何避坑。
- 綁定域名
- 先登錄微信公眾平臺進入“公眾號設置”的“功能設置”里填寫“JS接口安全域名”。
- 上面是說明文檔的原話,這里我介紹一下在開發(fā)階段如何測試。
- 首先你得有一個微信公眾平臺測試賬號,總不能用公司的公眾賬號進行開發(fā)測試吧,當然你有自己的公眾號是最好的,沒有的話就快速的申請一個接口測試號吧
- 訪問微信公眾平臺測試版系統(tǒng):http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
- 主要是獲取到測試號的appID和appsecret以及綁定J接口的安全域名:



- 綁定域名這塊其實有蠻多坑的,就將我遇到的坑給大家說一下,希望遇到的人能夠很好的解決:
- 域名不能加http://或者https://前綴,直接test.wxwx.com好了,否則回報invalid domain url無效的域
- 可以加端口號比如:test.wxwx.com:8888
- 也可以是一個ip地址比如:123.45.24.37
- 如何在本地進行測試?請下載一個代理服務器工具Fiddler:https://www.telerik.com/download/fiddler然后選擇選項Tools->HOSTS

- 前面是你自己機器的Ip地址,后面是你自己定義的一個地址,然后在公眾號里面添加wxwuwei.ceshiweixin.com:8888,F(xiàn)iddler的端口是8888,然后保證你的機器和你的手機在同一個局域網(wǎng)下,進入手機設置你的網(wǎng)絡連接如下圖所示:

- 這個手機設置的ip一定要和你的PC的ip一樣,這樣前期環(huán)境準備工作就完成了,就可以開始編碼部分了~
- 頁面引入JS文件http://res.wx.qq.com/open/js/jweixin-1.0.0.js,這個就不多說了
- 通過config接口注入配置信息,微信需要去驗證的, 如果驗證通過了會執(zhí)行wx.ready方法,這個JS-SDK文檔有詳細說明,這里主要說一下后臺如何生成config中需要的配置信息:1 wx.config({
2 debug: true, // 開啟調(diào)試模式,調(diào)用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數(shù),可以在pc端打開,參數(shù)信息會通過log打出,僅在pc端時才會打印。
3 appId: '', // 必填,公眾號的唯一標識
4 timestamp: , // 必填,生成簽名的時間戳
5 nonceStr: '', // 必填,生成簽名的隨機串
6 signature: '',// 必填,簽名,見附錄1
7 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
8 });
- appId我們已經(jīng)獲取到了,timestamp時間戳是到秒的,千萬別到毫秒,可以new Date().getTime()/1000獲取一個就可以了,nonceStr是咱們自己定義的一個串,可以是隨機串也可以是一個固定的字符串,signature是根據(jù)當前的jsapi_ticket、nonceStr、timestamp、url(當前網(wǎng)頁的url,不包含#及其后面部分)四個字段拼串(需要按照ASCII碼從小到大排序進行拼串a(chǎn)bcdefg的順序)進行SHA1加密生成的,這些都是需要在服務器端實現(xiàn)。
- access_token的獲取:https://mp.weixin.qq.com/wiki/15/54ce45d8d30b6bf6758f68d2e95bc627.html
- 需要緩存,因為這個接口的調(diào)用是有次數(shù)限制的,可以放在redis中
- 卡券 api_ticket的獲取:根據(jù)獲取到的access_token來獲取api_ticket
- 獲取url:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=wx_card
- 需要緩存,也是有次數(shù)限制的,可以放在redis中
- 常見錯誤及解決方法:見說明文檔的附錄5-常見錯誤及解決方法,其中的紅字部分是你需要注意的坑,也是你解決微信多次分享的關鍵之處:確保你獲取用來簽名的url是動態(tài)獲取的,動態(tài)頁面可參見實例代碼中php的實現(xiàn)方式。如果是html的靜態(tài)頁面在前端通過ajax將url傳到后臺簽名,前端需要用js獲取當前頁面除去'#'hash部分的鏈接(可用location.href.split('#')[0]獲取,而且需要encodeURIComponent),因為頁面一旦分享,微信客戶端會在你的鏈接末尾加入其它參數(shù),如果不是動態(tài)獲取當前鏈接,將導致分享后的頁面簽名失敗。
下面根據(jù)上面描述的步驟貼出部分代碼(Java版的),僅供參考!
首先寫一個WeixinUtils.java類:
1 package com.freekeer.c.util;
2
3 import java.security.MessageDigest;
4 import java.util.Formatter;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.Properties;
8
9 import org.apache.commons.lang3.StringUtils;
10
11 import com.alibaba.fastjson.JSONObject;
12
13 public class WeiXinUtils {
14
15 // 微信appId
16 private static String APPID;
17 // 微信公眾號唯一密鑰
18 private static String APPSECRET;
19 // 獲取acc_token的接口
20 private static String ACC_TOKEN_URL;
21 // 獲取jsapi_ticket url
22 private static String JSAPI_TICKET_URL;
23 // 生成簽名的隨機串
24 private static String NONCE_STR;
25
26 static {
27 Properties prop = AppPropTools.getProperties("/weixin.properties");
28 APPID = prop.getProperty("APPID");
29 APPSECRET = prop.getProperty("APPSECRET");
30 ACC_TOKEN_URL = prop.getProperty("ACC_TOKEN_URL") + "&appid=" + APPID
31 + "&secret=" + APPSECRET;
32 JSAPI_TICKET_URL = prop.getProperty("JSAPI_TICKET_URL");
33 NONCE_STR = prop.getProperty("NONCE_STR");
34 }
35
36 // 私有構造方法
37 private WeiXinUtils() {
38
39 }
40
41 /**
42 * 獲取微信acc_token
43 *
44 * @return
45 * @throws Exception
46 */
47 public static String getAccToken() throws Exception {
48 // 先從redis取,取不到再從微信里面取
49 String weixin_acc_token = RedisHelper.getStringValue(
50 "weixin_acc_token", 2);
51 if (StringUtils.isEmpty(weixin_acc_token)) {
52 String resultStr = HttpURLConnectionUtil
53 .getWebHTMLCode(ACC_TOKEN_URL);
54 JSONObject resultObj = JSONObject.parseObject(resultStr);
55 String accToken = resultObj.getString("access_token");
56 int expiresIn = resultObj.getIntValue("expires_in");
57 // 寫進redis
58 RedisHelper.setStringValue("weixin_acc_token", accToken, expiresIn,
59 2);
60 return accToken;
61 }
62 return weixin_acc_token;
63
64 }
65
66 /**
67 * 獲取微信票據(jù)
68 *
69 * @return
70 * @throws Exception
71 */
72 public static String getTicket() throws Exception {
73 // 先從redis中取ticket,沒有再從這里取
74 String weixin_js_api_tiket = RedisHelper.getStringValue(
75 "weixin_js_api_tiket", 2);
76 if (StringUtils.isEmpty(weixin_js_api_tiket)) {
77
78 String accToken = getAccToken();
79 String resultStr = HttpURLConnectionUtil
80 .getWebHTMLCode(JSAPI_TICKET_URL + accToken);
81 JSONObject resultObj = JSONObject.parseObject(resultStr);
82 if (resultObj.getIntValue("errcode") == 0) {
83 String ticket = resultObj.getString("ticket");
84 int expires_in = resultObj.getIntValue("expires_in");
85 // 寫入redis
86 RedisHelper.setStringValue("weixin_js_api_tiket", ticket,
87 expires_in, 2);
88 return ticket;
89 }
90 return null;
91 }
92 return weixin_js_api_tiket;
93 }
94
95 /**
96 * 獲取簽名
97 *
98 * @param timeStamp
99 * @param requestUrl
100 * @return
101 * @throws Exception
102 */
103 public static String getSignature(Long timeStamp, String requestUrl)
104 throws Exception {
105 String ticket = getTicket();
106 StringBuffer allStr = new StringBuffer("jsapi_ticket=");
107 allStr.append(ticket).append("&noncestr=").append(NONCE_STR)
108 .append("×tamp=").append(timeStamp).append("&url=")
109 .append(requestUrl);
110 MessageDigest crypt = MessageDigest.getInstance("SHA-1");
111 crypt.reset();
112 crypt.update(allStr.toString().getBytes("UTF-8"));
113 String signature = byteToHex(crypt.digest());
114 return signature;
115 }
116
117 /**
118 * SHA1加密
119 *
120 * @param hash
121 * @return
122 */
123 private static String byteToHex(final byte[] hash) {
124 Formatter formatter = new Formatter();
125 for (byte b : hash) {
126 formatter.format("%02x", b);
127 }
128 String result = formatter.toString();
129 formatter.close();
130 return result;
131 }
132
133 /**
134 * 獲取微信JS頁面Config所需參數(shù)
135 *
136 * @param timeStamp
137 * @param requestUrl
138 * @return
139 * @throws Exception
140 */
141 public static Map<String, Object> getConfig(long timeStamp,
142 String requestUrl) throws Exception {
143 Map<String, Object> map = new HashMap<String, Object>();
144 String signature = getSignature(timeStamp, requestUrl);
145 map.put("signature", signature);
146 map.put("nonceStr", NONCE_STR);
147 map.put("timestamp", timeStamp);
148 map.put("appId", APPID);
149 return map;
150 }
151
152 }
153
2
3 import java.security.MessageDigest;
4 import java.util.Formatter;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.Properties;
8
9 import org.apache.commons.lang3.StringUtils;
10
11 import com.alibaba.fastjson.JSONObject;
12
13 public class WeiXinUtils {
14
15 // 微信appId
16 private static String APPID;
17 // 微信公眾號唯一密鑰
18 private static String APPSECRET;
19 // 獲取acc_token的接口
20 private static String ACC_TOKEN_URL;
21 // 獲取jsapi_ticket url
22 private static String JSAPI_TICKET_URL;
23 // 生成簽名的隨機串
24 private static String NONCE_STR;
25
26 static {
27 Properties prop = AppPropTools.getProperties("/weixin.properties");
28 APPID = prop.getProperty("APPID");
29 APPSECRET = prop.getProperty("APPSECRET");
30 ACC_TOKEN_URL = prop.getProperty("ACC_TOKEN_URL") + "&appid=" + APPID
31 + "&secret=" + APPSECRET;
32 JSAPI_TICKET_URL = prop.getProperty("JSAPI_TICKET_URL");
33 NONCE_STR = prop.getProperty("NONCE_STR");
34 }
35
36 // 私有構造方法
37 private WeiXinUtils() {
38
39 }
40
41 /**
42 * 獲取微信acc_token
43 *
44 * @return
45 * @throws Exception
46 */
47 public static String getAccToken() throws Exception {
48 // 先從redis取,取不到再從微信里面取
49 String weixin_acc_token = RedisHelper.getStringValue(
50 "weixin_acc_token", 2);
51 if (StringUtils.isEmpty(weixin_acc_token)) {
52 String resultStr = HttpURLConnectionUtil
53 .getWebHTMLCode(ACC_TOKEN_URL);
54 JSONObject resultObj = JSONObject.parseObject(resultStr);
55 String accToken = resultObj.getString("access_token");
56 int expiresIn = resultObj.getIntValue("expires_in");
57 // 寫進redis
58 RedisHelper.setStringValue("weixin_acc_token", accToken, expiresIn,
59 2);
60 return accToken;
61 }
62 return weixin_acc_token;
63
64 }
65
66 /**
67 * 獲取微信票據(jù)
68 *
69 * @return
70 * @throws Exception
71 */
72 public static String getTicket() throws Exception {
73 // 先從redis中取ticket,沒有再從這里取
74 String weixin_js_api_tiket = RedisHelper.getStringValue(
75 "weixin_js_api_tiket", 2);
76 if (StringUtils.isEmpty(weixin_js_api_tiket)) {
77
78 String accToken = getAccToken();
79 String resultStr = HttpURLConnectionUtil
80 .getWebHTMLCode(JSAPI_TICKET_URL + accToken);
81 JSONObject resultObj = JSONObject.parseObject(resultStr);
82 if (resultObj.getIntValue("errcode") == 0) {
83 String ticket = resultObj.getString("ticket");
84 int expires_in = resultObj.getIntValue("expires_in");
85 // 寫入redis
86 RedisHelper.setStringValue("weixin_js_api_tiket", ticket,
87 expires_in, 2);
88 return ticket;
89 }
90 return null;
91 }
92 return weixin_js_api_tiket;
93 }
94
95 /**
96 * 獲取簽名
97 *
98 * @param timeStamp
99 * @param requestUrl
100 * @return
101 * @throws Exception
102 */
103 public static String getSignature(Long timeStamp, String requestUrl)
104 throws Exception {
105 String ticket = getTicket();
106 StringBuffer allStr = new StringBuffer("jsapi_ticket=");
107 allStr.append(ticket).append("&noncestr=").append(NONCE_STR)
108 .append("×tamp=").append(timeStamp).append("&url=")
109 .append(requestUrl);
110 MessageDigest crypt = MessageDigest.getInstance("SHA-1");
111 crypt.reset();
112 crypt.update(allStr.toString().getBytes("UTF-8"));
113 String signature = byteToHex(crypt.digest());
114 return signature;
115 }
116
117 /**
118 * SHA1加密
119 *
120 * @param hash
121 * @return
122 */
123 private static String byteToHex(final byte[] hash) {
124 Formatter formatter = new Formatter();
125 for (byte b : hash) {
126 formatter.format("%02x", b);
127 }
128 String result = formatter.toString();
129 formatter.close();
130 return result;
131 }
132
133 /**
134 * 獲取微信JS頁面Config所需參數(shù)
135 *
136 * @param timeStamp
137 * @param requestUrl
138 * @return
139 * @throws Exception
140 */
141 public static Map<String, Object> getConfig(long timeStamp,
142 String requestUrl) throws Exception {
143 Map<String, Object> map = new HashMap<String, Object>();
144 String signature = getSignature(timeStamp, requestUrl);
145 map.put("signature", signature);
146 map.put("nonceStr", NONCE_STR);
147 map.put("timestamp", timeStamp);
148 map.put("appId", APPID);
149 return map;
150 }
151
152 }
153
再寫一個接口類返回config中的參數(shù):
1 package com.freekeer.c.controller;
2
3 import java.util.Date;
4 import java.util.Map;
5
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.springframework.web.bind.annotation.RequestMapping;
12 import org.springframework.web.bind.annotation.RequestMethod;
13 import org.springframework.web.bind.annotation.RequestParam;
14 import org.springframework.web.bind.annotation.RestController;
15
16 import com.freekeer.c.util.WeiXinUtils;
17
18 /**
19 * 熱搜詞控制層
20 *
21 * @author WuWei
22 */
23 @RestController
24 public class WeixinController extends BaseController {
25
26 @SuppressWarnings("unused")
27 private static Logger log = LoggerFactory.getLogger(WeixinController.class);
28
29 @RequestMapping(value = "/wx-config", method = { RequestMethod.GET })
30 public Map<String, Object> getConfig(HttpServletRequest request,
31 HttpServletResponse response, @RequestParam(value="url", required=true) String url) throws Exception {
32 long timeStamp = new Date().getTime()/1000;
33 Map<String, Object> result = WeiXinUtils.getConfig(timeStamp, url);
34 return result;
35 }
36
37 }
38
2
3 import java.util.Date;
4 import java.util.Map;
5
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.springframework.web.bind.annotation.RequestMapping;
12 import org.springframework.web.bind.annotation.RequestMethod;
13 import org.springframework.web.bind.annotation.RequestParam;
14 import org.springframework.web.bind.annotation.RestController;
15
16 import com.freekeer.c.util.WeiXinUtils;
17
18 /**
19 * 熱搜詞控制層
20 *
21 * @author WuWei
22 */
23 @RestController
24 public class WeixinController extends BaseController {
25
26 @SuppressWarnings("unused")
27 private static Logger log = LoggerFactory.getLogger(WeixinController.class);
28
29 @RequestMapping(value = "/wx-config", method = { RequestMethod.GET })
30 public Map<String, Object> getConfig(HttpServletRequest request,
31 HttpServletResponse response, @RequestParam(value="url", required=true) String url) throws Exception {
32 long timeStamp = new Date().getTime()/1000;
33 Map<String, Object> result = WeiXinUtils.getConfig(timeStamp, url);
34 return result;
35 }
36
37 }
38
JS端通過ajax調(diào)用
1 function weixinShareZp(demandData) {
2 var title = demandData.profession;
3 var desc = demandData.projectName+",在"+demandData.workCityName+"招聘"+title+demandData.requireStaffCount+"人";
4 var url = "../wx-config?url="+encodeURIComponent(window.location.href);
5 var logoUrl = "http://xxx-resource-public.oss-cn-beijing.aliyuncs.com/share_img/logo.png";
6 $.get(url,[],function(data) {
7 wx.config({
8 debug:true,
9 appId:data.appId,
10 timestamp:data.timestamp,
11 nonceStr:data.nonceStr,
12 signature:data.signature,
13 jsApiList: [
14 'onMenuShareTimeline',
15 'onMenuShareAppMessage',
16 'onMenuShareQQ',
17 'onMenuShareWeibo',
18 'onMenuShareQZone'
19 ]
20 });
21 wx.ready(function(){
22 // 獲取“分享到朋友圈”按鈕點擊狀態(tài)及自定義分享內(nèi)容接口
23 wx.onMenuShareTimeline({
24 imgUrl:logoUrl,
25 link:window.location.href,
26 desc:desc,
27 title:title,
28 type:"link"
29 });
30 // 獲取“分享給朋友”按鈕點擊狀態(tài)及自定義分享內(nèi)容接口
31 wx.onMenuShareAppMessage({
32 imgUrl: logoUrl,
33 link: window.location.href,
34 desc: desc,
35 title: title,
36 type: "link"
37 });
38 wx.onMenuShareQQ({
39 title: title,
40 desc: desc,
41 link: window.location.href,
42 imgUrl: logoUrl
43 });
44 wx.onMenuShareWeibo({
45 title: title,
46 desc: desc,
47 link: window.location.href,
48 imgUrl: logoUrl,
49 });
50 wx.onMenuShareQZone({
51 title: title,
52 desc: desc,
53 link: window.location.href,
54 imgUrl: logoUrl,
55 });
56 });
57
58 });
59 }
2 var title = demandData.profession;
3 var desc = demandData.projectName+",在"+demandData.workCityName+"招聘"+title+demandData.requireStaffCount+"人";
4 var url = "../wx-config?url="+encodeURIComponent(window.location.href);
5 var logoUrl = "http://xxx-resource-public.oss-cn-beijing.aliyuncs.com/share_img/logo.png";
6 $.get(url,[],function(data) {
7 wx.config({
8 debug:true,
9 appId:data.appId,
10 timestamp:data.timestamp,
11 nonceStr:data.nonceStr,
12 signature:data.signature,
13 jsApiList: [
14 'onMenuShareTimeline',
15 'onMenuShareAppMessage',
16 'onMenuShareQQ',
17 'onMenuShareWeibo',
18 'onMenuShareQZone'
19 ]
20 });
21 wx.ready(function(){
22 // 獲取“分享到朋友圈”按鈕點擊狀態(tài)及自定義分享內(nèi)容接口
23 wx.onMenuShareTimeline({
24 imgUrl:logoUrl,
25 link:window.location.href,
26 desc:desc,
27 title:title,
28 type:"link"
29 });
30 // 獲取“分享給朋友”按鈕點擊狀態(tài)及自定義分享內(nèi)容接口
31 wx.onMenuShareAppMessage({
32 imgUrl: logoUrl,
33 link: window.location.href,
34 desc: desc,
35 title: title,
36 type: "link"
37 });
38 wx.onMenuShareQQ({
39 title: title,
40 desc: desc,
41 link: window.location.href,
42 imgUrl: logoUrl
43 });
44 wx.onMenuShareWeibo({
45 title: title,
46 desc: desc,
47 link: window.location.href,
48 imgUrl: logoUrl,
49 });
50 wx.onMenuShareQZone({
51 title: title,
52 desc: desc,
53 link: window.location.href,
54 imgUrl: logoUrl,
55 });
56 });
57
58 });
59 }
開始通過微信分享進行測試吧!