接入【微信JS-SDK】 坑多多

接入【微信JS-SDK】

用于实现自定义朋友圈分享的标题和图标。

开始之前需要在公众平台进行一些设置:

  1. 请求 access_token 的服务ip 要添加进【后台》基本设置》公众号开发信息》IP白名单】
  2. 接入JS-SDK的页面域名要添加进【后台》公众号设置》功能设置》JS接口安全域名】列表(最多只能加三个,每个月最多改三次)

js-sdk使用需要获取签名,验证通过后方能调用各种【微信接口】

  1. 前台将当前页面(调用微信接口的页面)url发给我方服务器,或我方后台接到页面请求后自己取出请求的url
  2. 我方服务器向微信方发送 AppID、AppSecret(可登录公众平台查) 请求 access_token
  3. 再用 access_token 请求 ticket
  4. 拿到 ticket 如果按微信给的算法进行签名,这一步要用到1中提到的url(也就是签名对页面是唯一的,参数变化什么的就得重新签名了,想想我之前在地址后面加的那些 rand=math.random() 【想哭】)
  5. 将签名返回给前端
  6. 前端页面加载后,通过config接口注入权限验证配置
wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '${configParam.appId}', // 必填,公众号的唯一标识
    timestamp: '${configParam.timestamp}', // 必填,生成签名的时间戳
    nonceStr: '${configParam.nonceStr}', // 必填,生成签名的随机串
    signature: '${configParam.signature}',// 必填,签名
    jsApiList: [		// 必填,需要使用的JS接口列表	
        'checkJsApi',
        'onMenuShareTimeline',
        'onMenuShareAppMessage'
    ] 
});

7、如果通过验证,调用 ready 接口

wx.ready(function(){
    // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,
    // 所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,
    // 则可以直接调用,不需要放在ready函数中。
});

8、否则调用 error接口

wx.error(function(res){
    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,
    // 也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});

详情见文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

是不是觉得 So Easy ? 那你就图羊图身破了

============================ 理想与现实的分割线 ============================

理想自然是美好的。但现实。。。。。。。。。。

1、新接口没卵用

腾讯说,我们升级了,给了两个新接口,快快用它吧!
废了九牛二虎之力,把他那个变态的签名拿到手。结果这两个新接口根本没卵用。 【今天是 2018-11-29】
验证通过,权限拿到。
接入【微信JS-SDK】 坑多多_第1张图片
接入【微信JS-SDK】 坑多多_第2张图片
接入【微信JS-SDK】 坑多多_第3张图片

2、旧接口,瞎JB乱取值

然后到网上看到一个解决方案,牛B的方案。
放弃新接口,用回原来的。
结果~~~~~~~~ 标题和图片明明传给它了,但它就是自己瞎JB乱取一通。。。
我TMD搞这么多鬼事,就是为了让你来放飞自我的?(我TMD随便找个浏览器分享过来都能带图啊,瞎JB取,我还用你啊)
【取的是页面的 title 和 body 下的第一张>=300x300的图,道听途说的】
接入【微信JS-SDK】 坑多多_第4张图片
接入【微信JS-SDK】 坑多多_第5张图片

3、诡异的 wx.error 任你配置参数怎么错,死活不调用

然后是那个诡异的 wx.error
这段中文的意思难道不就是 wx.config 验证失败会执行它吗?
但无论我怎么乱填配置参数,它任你错,就是不走这里。
在这里插入图片描述

4、官方的QQ尾巴 我fuuuuuuuuuuuuuuuck

分享给好友加这个
https://mp.csdn.net/mdeditor/84640526?from=groupmessage
分享到朋友圈加这个
https://mp.csdn.net/mdeditor/84640526?from=timeline
你自己搞个JB签名要用url 你自己心里没点B数吗?全中国的网络上,因为你这JB设计要浪费多少请求?

5、这破玩意也是死鱼。只有手动执行它才有反应。

放到 wx.ready 里面外面都一样,没卵用。只有手动执行它才有反应。

    wx.checkJsApi({
        jsApiList: [
                'updateAppMessageShareData',
                'updateTimelineShareData',
         ],
        success: function (res) {
                alert(JSON.stringify(res));
        }
    });

6、另外的另外,下面诡异的 success

每当我打开页面,success 的内容就输出了。我都搞不懂,你是怎么侧漏的。
这不是要等我执行了分享操作才执行的吗?(另外在网上看到别人说官方把回调规则调整了,但是文档竟然丝毫没有提及。我不知道他们是不是还给开发者搞了个VIP,要充钱才能看到最新的文档么?)

	wx.ready(function () {
		wx.onMenuShareTimeline({ 
			title: '【'+ document.title+'】', // 分享标题
			link: window.location.href, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
			imgUrl: $('#weChatTimelineIco').attr('src') || "<%=basePath%>images/icon/logo/site_${USER_SITE.siteId}.jpg", // 分享图标
			success: function () {
				console && console.info && console.info('获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)');
			}
		});
	});

总之面对
接入【微信JS-SDK】 坑多多_第6张图片
我只想说一个大写的

FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK

最后的最后,还是乖乖用了旧接口。耐心调调还是能用的

确保后台生成的 configParam 数据正确,前端代码如下:

<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"> script>
<script>
	//判断是否微信登陆
	function isWeChat() {
		var ua = window.navigator.userAgent.toLowerCase();
		if (ua.match(/MicroMessenger/i) == 'micromessenger') {
			return true;
		} else {
			return false;
		}
	};
	//剪掉微信尾巴,转发给微信好友或朋友圈的URL打开后会加尾巴
	function cutWeChatTail(){
		try{
			var url = window.location.href;
			if(url.indexOf('from=timeline') > -1 ||  url.indexOf('from=groupmessage') > -1){
				window.location.href = url.replace(/[?&]from=.*/,'');
			}
		}catch(e){}
	}
	/*
	 * 注意:
	 * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
	 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。
	 * 3. 常见问题及完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
	 *
	 * 开发中遇到问题详见文档“附录5-常见错误及解决办法”解决,如仍未能解决可通过以下渠道反馈:
	 * 邮箱地址:[email protected]
	 * 邮件主题:【微信JS-SDK反馈】具体问题
	 * 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。
	*/
	wx.jerryParam = {
		debug: false,
		appId: '${configParam.appId}',
		timestamp: '${configParam.timestamp}',
		nonceStr: '${configParam.nonceStr}',
		signature: '${configParam.signature}',
		jsApiList: [
			'checkJsApi',
			'onMenuShareTimeline',
			'onMenuShareAppMessage',
			'onMenuShareQZone',
			'onMenuShareQQ'
		]
	};
	
	if(isWeChat()){
		cutWeChatTail();
		wx.config(wx.jerryParam);
	}
	
	wx.ready(function () {
		console && console.info && console.info('------------- 成功调用 wx.ready() -------------');
		var title = '【'+ document.title+'】';
		var desc = $('[name="description"]').attr("content") || "大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!";
		var link = window.location.href;
		var imgUrl = $('#weChatTimelineIco').attr('src') || "https://avatar.csdn.net/9/7/4/1_jx520.jpg";
		//获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)
		var paramDataOld = { 
			title: title, // 分享标题
			link: link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
			imgUrl: imgUrl, // 分享图标
			success: function () {
				console && console.info && console.info('获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)');
			}
		}
		wx.onMenuShareTimeline(paramDataOld);
		
		paramDataOld.desc = desc; // 分享描述
		wx.onMenuShareAppMessage(paramDataOld);
		wx.onMenuShareQQ(paramDataOld);
		wx.onMenuShareQZone(paramDataOld);
		
		//----------------------------------------------------------
		wx.checkJsApi({
			jsApiList: [
				'checkJsApi',
				'onMenuShareTimeline',
				'onMenuShareAppMessage',
				'onMenuShareQZone',
				'onMenuShareQQ'
			],
			success: function (res) {
				console && console.info && console.info(JSON.stringify(res));
			}
		});
	});
	// 我这里对访问的url规则是有控制的,所以用了简单粗暴的方式来减除微信尾巴。
	wx.error(function(res){
		console && console.info && console.info('config出错:' + JSON.stringify(res,null,4));
		if(isWeChat()){
			cutWeChatTail();
		}
	});
script>

Java 后台。希望哪天我失忆了,这段代码拿来就能直接用。233333

见的有点匆忙,并且整个过程笼罩在被忽悠的阴云之下,所以代码还应该优化一下的。
如果是前端动态用ajax获取签名,我看到别人用个单例来实现。
不过我这里只要签名不过期,就不需要总请求服务器。所以将就用咯。。。

package com.jerry.web.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import net.sf.json.JSONObject;

/**
 * 微信工具类
 * ACCESS_TOKEN 和 JS_API_TICKET 由定时作业每隔 MAX_TIME 刷新一次。
 * by JerryJin 2018-11-29
 */
public class WeChatUtil {
	
	private static final Logger log = Logger.getLogger(WeChatUtil.class); 
	
	//--------------------------------------------------------------------------------------------------
	private static final String APPID = SystemConfigUtil.readConfig("wxpay.app_id");//公司公众号的APPID(已通过认证)
	
	private static final String APPSECRET = SystemConfigUtil.readConfig("wxpay.transfer_api_password");//公司公众号的APPSECRET(已通过认证)
	
	private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
	
	private static final String JS_API_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";
	
	private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
	
	private static final String JS_API_TICKET = "JS_API_TICKET";
	
	private static final long TOKEN_MAX_TIME = 7000 * 1000;// 微信允许最长Access_token有效时间为7200秒,这里设置为7000秒
	
	private static final long TICKET_MAX_TIME = 7000 * 1000;// 微信允许最长js_api_ticket有效时间为7200秒,这里设置为7000秒
	
	//--------------------------------------------------------------------------------------------------
	
	private static JSONObject doGetStr(String url) throws ParseException, IOException{
		CloseableHttpClient client = HttpClients.createDefault();
		HttpGet httpGet = new HttpGet(url);
		JSONObject jsonObject = null;
		HttpResponse httpResponse = client.execute(httpGet);
		HttpEntity entity = httpResponse.getEntity();
		if(entity != null){
			String result = EntityUtils.toString(entity,"UTF-8");
			jsonObject = JSONObject.fromObject(result);
			log.info(result);
		}
		return jsonObject;
	}

	private static String httpGet(String url){
		String strResult = null;
		try {
			CloseableHttpClient client = HttpClients.createDefault();
			HttpGet request = new HttpGet(url);
			HttpResponse response = client.execute(request);
			
			/**请求发送成功,并得到响应**/
			if (response.getStatusLine().getStatusCode() == org.apache.http.HttpStatus.SC_OK) {
				/**读取服务器返回过来的json字符串数据**/
				strResult = EntityUtils.toString(response.getEntity());
				log.info(strResult);
			} else {
				log.error("get请求提交失败");
			}
		} catch (IOException e) {
			log.error("get请求提交失败:" + e.getMessage(), e);
		}
		return strResult;
	}
	
	/**
	 * 向微信接口请求 access_token
* 文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183 * @param appid * @param appsecret * @return */
private static AccessToken getAccessToken(String appid,String appsecret) { log.info("获取 AccessToken 开始"); AccessToken token = new AccessToken(); String url = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret); JSONObject jsonObject = null; try { jsonObject = doGetStr(url); } catch (ParseException e) { log.error(e.getMessage(), e); } catch (IOException e) { log.error(e.getMessage(), e); } if(jsonObject!=null){ // 成功: {"access_token":"ACCESS_TOKEN","expires_in":7200} // 失败: {"errcode":40013,"errmsg":"invalid appid"} token.setToken(jsonObject.optString("access_token")); token.setExpiresIn(jsonObject.optInt("expires_in")); token.setErrcode(jsonObject.optString("errcode")); token.setErrmsg(jsonObject.optString("errmsg")); token.setCreateDate(new Date());//获取时间 } if (checkAccessToken(token)) { ServletContextUtil.get().setAttribute(ACCESS_TOKEN, token);// 缓存全局变量 log.info("获取 AccessToken 成功!"); } return token; } /** * 验证本地缓存的 AccessToken,如果有效,就返回 true 否则 false * @return */ private static boolean checkAccessToken(AccessToken accessToken){ if (accessToken != null) { return accessToken.getToken() != null && !"".equals(accessToken.getToken()) && System.currentTimeMillis() - accessToken.getCreateDate().getTime() < TOKEN_MAX_TIME; } return false; } /** * 向微信接口请求 ticket *
文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 *
附录1-JS-SDK使用权限签名算法 * @param accessToken * @return */
private static ApiTicket getApiTicket(String accessToken, String ticketUrl) { // String jsapi_ticket = null; ApiTicket ticket = new ApiTicket(); try { String responseText = httpGet(String.format(ticketUrl, accessToken)); // jsapi_ticket = null; JSONObject object = JSONObject.fromObject(responseText); if (object.containsKey("ticket")) { ticket.setTicket(object.optString("ticket")); ticket.setExpiresIn(object.optInt("expires_in")); ticket.setErrcode(object.optString("errcode")); ticket.setErrmsg(object.optString("errmsg")); ticket.setCreateDate(new Date()); } } catch (Exception e) { log.error(e.getMessage(), e); } return ticket; } /** * 向微信接口请求 api_ticket *
文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 *
附录1-JS-SDK使用权限签名算法 * @param accessToken * @return */
private static ApiTicket getJsApiTicket(String accessToken) { log.info("获取 JsApiTicket 开始"); ApiTicket ticket = getApiTicket(accessToken, JS_API_TICKET_URL); if (checkJsApiTicket(ticket)) { log.info("获取 JsApiTicket 成功!"); ServletContextUtil.get().setAttribute(JS_API_TICKET, ticket);// 缓存全局变量 } return ticket; } /** * 验证本地缓存的 JsApiTicket,如果有效,就返回 true 否则 false * @param api_ticket * @return */ private static boolean checkJsApiTicket(ApiTicket api_ticket){ if (api_ticket != null) { return "ok".equals(api_ticket.getErrmsg()) && System.currentTimeMillis() - api_ticket.getCreateDate().getTime() < TICKET_MAX_TIME; } return false; } /** * 签名:这个就是官方给的例子代码。直接用就行了。 *
文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 *
【附录6-DEMO页面和示例代码】 *
签名算法 * 签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), * url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后, * 使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均 * 为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。 * @param jsapi_ticket * @param url * @return */
private static Map<String, String> sign(String jsapi_ticket, String url) { Map<String, String> ret = new HashMap<String, String>(); String nonce_str = create_nonce_str(); String timestamp = create_timestamp(); String string1; String signature = ""; //注意这里参数名必须全部小写,且必须有序 string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url; try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { log.error(e.getMessage(), e); } catch (UnsupportedEncodingException e) { log.error(e.getMessage(), e); } //ret.put("url", url); //ret.put("jsapi_ticket", jsapi_ticket); ret.put("appId", APPID); ret.put("nonceStr", nonce_str); ret.put("timestamp", timestamp); ret.put("signature", signature); ret.put("string1", string1); ret.put("jsapi_ticket", new StringBuffer(jsapi_ticket).reverse().toString()); log.debug(ret); return ret; } 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; } private static String create_nonce_str() { return UUID.randomUUID().toString(); } private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); } //-------------------------------------------------------------------------------------------------- /** * 首先尝试从缓存获取 access_token,如果token无效就向服务器请求重新生成
* 文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183 * @return */
public static synchronized AccessToken getAccessToken() { AccessToken token = (AccessToken)ServletContextUtil.get().getAttribute(ACCESS_TOKEN); //判断 token 如果过期就重新生成,否则直接返回。 if(checkAccessToken(token)){ return token; }else{ log.info("AccessToken invalid"); return getAccessToken(APPID, APPSECRET); } } public static String getAccessTokenStr() { AccessToken token = (AccessToken)ServletContextUtil.get().getAttribute(ACCESS_TOKEN); //判断 token 如果过期就重新生成,否则直接返回。 if(token != null){ return token.getToken(); }else{ log.info("AccessToken invalid"); return ""; } } /** * 首先尝试从缓存获取 api_ticket,如果token无效就向服务器请求重新生成
*
文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 *
附录1-JS-SDK使用权限签名算法 * @return */
public static synchronized String getJsApiTicket() { ApiTicket jsApi_ticket = (ApiTicket)ServletContextUtil.get().getAttribute(JS_API_TICKET); if (checkJsApiTicket(jsApi_ticket)) { return jsApi_ticket.getTicket(); }else{ log.info("JsApiTicket invalid"); return getJsApiTicket(getAccessToken().getToken()).getTicket(); } } /** * JSSDK使用步骤,步骤三:通过config接口注入权限验证配置 * 获取 wx.config({此处所需要的参数}); * @param url * @return */ public static Map<String, String> getWxConfigParam(String url) { String jsApiTicket = getJsApiTicket(); return sign(jsApiTicket == null ? "" : jsApiTicket, url); } } class AccessToken { private String token; // 成功时返回 private int expiresIn; // 成功时返回 private String errcode; // 失败时返回 private String errmsg; // 失败时返回 private Date createDate; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } public String getErrcode() { return errcode; } public void setErrcode(String errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } } class ApiTicket { private String errcode; //"errcode":0, private String errmsg; //"errmsg":"ok", private String ticket; private int expiresIn; //"expires_in":7200 private Date createDate; public String getErrcode() { return errcode; } public void setErrcode(String errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public String getTicket() { return ticket; } public void setTicket(String ticket) { this.ticket = ticket; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } }

你可能感兴趣的:(WeChat微信,wechat,微信,自定义分享,微信网页开发)