JS-SDK的使用(微信多次分享)
需求描述:公司通过APP产品分享出去的需求和简历是做了一个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接口安全域名”。
- 上面是说明文档的原话,这里我介绍一下在开发阶段如何测试。
- 首先你得有一个微信公众平台测试账号,总不能用公司的公众账号进行开发测试吧,当然你有自己的公众号是最好的,没有的话就快速的申请一个接口测试号吧
- 访问微信公众平台测试版系统: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,Fiddler的端口是8888,然后保证你的机器和你的手机在同一个局域网下,进入手机设置你的网络连接如下图所示:
- 这个手机设置的ip一定要和你的PC的ip一样,这样前期环境准备工作就完成了,就可以开始编码部分了~
- 页面引入JS文件http://res.wx.qq.com/open/js/jweixin-1.0.0.js,这个就不多说了
- 通过config接口注入配置信息,微信需要去验证的, 如果验证通过了会执行wx.ready方法,这个JS-SDK文档有详细说明,这里主要说一下后台如何生成config中需要的配置信息:
1 wx.config({
2 debug: true , // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
3 appId: '', // 必填,公众号的唯一标识
4 timestamp: , // 必填,生成签名的时间戳
5 nonceStr: '', // 必填,生成签名的随机串
6 signature: '', // 必填,签名,见附录1
7 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
8 });
- appId我们已经获取到了,timestamp时间戳是到秒的,千万别到毫秒,可以new Date().getTime()/1000获取一个就可以了,nonceStr是咱们自己定义的一个串,可以是随机串也可以是一个固定的字符串,signature是根据当前的jsapi_ticket、nonceStr、timestamp、url(当前网页的url,不包含#及其后面部分)四个字段拼串(需要按照ASCII码从小到大排序进行拼串abcdefg的顺序)进行SHA1加密生成的,这些都是需要在服务器端实现。
- access_token的获取:https://mp.weixin.qq.com/wiki/15/54ce45d8d30b6bf6758f68d2e95bc627.html
- 需要缓存,因为这个接口的调用是有次数限制的,可以放在redis中
- 卡券 api_ticket的获取:根据获取到的access_token来获取api_ticket
- 获取url:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=wx_card
- 需要缓存,也是有次数限制的,可以放在redis中
- 常见错误及解决方法:见说明文档的附录5-常见错误及解决方法,其中的红字部分是你需要注意的坑,也是你解决微信多次分享的关键之处:确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
下面根据上面描述的步骤贴出部分代码(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 * 获取微信票据
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所需参数
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 * 获取微信票据
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所需参数
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中的参数:
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调用
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 // 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口
23 wx.onMenuShareTimeline({
24 imgUrl:logoUrl,
25 link:window.location.href,
26 desc:desc,
27 title:title,
28 type: " link "
29 });
30 // 获取“分享给朋友”按钮点击状态及自定义分享内容接口
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 // 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口
23 wx.onMenuShareTimeline({
24 imgUrl:logoUrl,
25 link:window.location.href,
26 desc:desc,
27 title:title,
28 type: " link "
29 });
30 // 获取“分享给朋友”按钮点击状态及自定义分享内容接口
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 }
开始通过微信分享进行测试吧!