用于实现自定义朋友圈分享的标题和图标。
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
理想自然是美好的。但现实。。。。。。。。。。
腾讯说,我们升级了,给了两个新接口,快快用它吧!
废了九牛二虎之力,把他那个变态的签名拿到手。结果这两个新接口根本没卵用。 【今天是 2018-11-29】
验证通过,权限拿到。
然后到网上看到一个解决方案,牛B的方案。
放弃新接口,用回原来的。
结果~~~~~~~~ 标题和图片明明传给它了,但它就是自己瞎JB乱取一通。。。
我TMD搞这么多鬼事,就是为了让你来放飞自我的?(我TMD随便找个浏览器分享过来都能带图啊,瞎JB取,我还用你啊)
【取的是页面的 title 和 body 下的第一张>=300x300的图,道听途说的】
wx.error
任你配置参数怎么错,死活不调用然后是那个诡异的 wx.error
这段中文的意思难道不就是 wx.config
验证失败会执行它吗?
但无论我怎么乱填配置参数,它任你错,就是不走这里。
分享给好友加这个
https://mp.csdn.net/mdeditor/84640526?from=groupmessage
分享到朋友圈加这个
https://mp.csdn.net/mdeditor/84640526?from=timeline
你自己搞个JB签名要用url 你自己心里没点B数吗?全中国的网络上,因为你这JB设计要浪费多少请求?
放到 wx.ready 里面外面都一样,没卵用。只有手动执行它才有反应。
wx.checkJsApi({
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData',
],
success: function (res) {
alert(JSON.stringify(res));
}
});
每当我打开页面,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('获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)');
}
});
});
确保后台生成的 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>
见的有点匆忙,并且整个过程笼罩在被忽悠的阴云之下,所以代码还应该优化一下的。
如果是前端动态用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;
}
}