接入【微信JS-SDK】
用于实现自定义朋友圈分享的标题和图标。
开始之前需要在公众平台进行一些设置:
请求 access_token 的服务ip 要添加进【后台》基本设置》公众号开发信息》IP白名单】
接入JS-SDK的页面域名要添加进【后台》公众号设置》功能设置》JS接口安全域名】列表(最多只能加三个,每个月最多改三次)
js-sdk使用需要获取签名,验证通过后方能调用各种【微信接口】
前台将当前页面(调用微信接口的页面)url发给我方服务器,或我方后台接到页面请求后自己取出请求的url
我方服务器向微信方发送 AppID、AppSecret(可登录公众平台查) 请求 access_token
再用 access_token 请求 ticket
拿到 ticket 如果按微信给的算法进行签名,这一步要用到1中提到的url(也就是签名对页面是唯一的,参数变化什么的就得重新签名了,想想我之前在地址后面加的那些 rand=math.random() 【想哭】)
将签名返回给前端
前端页面加载后,通过config接口注入权限验证配置
wx. config ( {
debug: true ,
appId: '${configParam.appId}' ,
timestamp: '${configParam.timestamp}' ,
nonceStr: '${configParam.nonceStr}' ,
signature: '${configParam.signature}' ,
jsApiList: [
'checkJsApi' ,
'onMenuShareTimeline' ,
'onMenuShareAppMessage'
]
} ) ;
7、如果通过验证,调用 ready 接口
wx. ready ( function ( ) {
} ) ;
8、否则调用 error接口
wx. error ( function ( res) {
} ) ;
详情见文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
是不是觉得 So Easy ? 那你就图羊图身破了
============================ 理想与现实的分割线 ============================
理想自然是美好的。但现实。。。。。。。。。。
1、新接口没卵用
腾讯说,我们升级了,给了两个新接口,快快用它吧!
废了九牛二虎之力,把他那个变态的签名拿到手。结果这两个新接口根本没卵用。 【今天是 2018-11-29】
验证通过,权限拿到。
2、旧接口,瞎JB乱取值
然后到网上看到一个解决方案,牛B的方案。
放弃新接口,用回原来的。
结果~~~~~~~~ 标题和图片明明传给它了,但它就是自己瞎JB乱取一通。。。
我TMD搞这么多鬼事,就是为了让你来放飞自我的?(我TMD随便找个浏览器分享过来都能带图啊,瞎JB取,我还用你啊)
【取的是页面的 title 和 body 下的第一张>=300x300的图,道听途说的】
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('获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)');
}
});
});
总之面对
我只想说一个大写的
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 ;
}
} ;
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) { }
}
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,
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) ) ;
}
} ) ;
} ) ;
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;
public class WeChatUtil {
private static final Logger log = Logger. getLogger ( WeChatUtil. class ) ;
private static final String APPID = SystemConfigUtil. readConfig ( "wxpay.app_id" ) ;
private static final String APPSECRET = SystemConfigUtil. readConfig ( "wxpay.transfer_api_password" ) ;
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 ;
private static final long TICKET_MAX_TIME = 7000 * 1000 ;
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) {
strResult = EntityUtils. toString ( response. getEntity ( ) ) ;
log. info ( strResult) ;
} else {
log. error ( "get请求提交失败" ) ;
}
} catch ( IOException e) {
log. error ( "get请求提交失败:" + e. getMessage ( ) , e) ;
}
return strResult;
}
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) {
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;
}
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 ;
}
private static ApiTicket getApiTicket ( String accessToken, String ticketUrl) {
ApiTicket ticket = new ApiTicket ( ) ;
try {
String responseText = httpGet ( String. format ( ticketUrl, accessToken) ) ;
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;
}
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;
}
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 ;
}
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 ( "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 ) ;
}
public static synchronized AccessToken getAccessToken ( ) {
AccessToken token = ( AccessToken) ServletContextUtil. get ( ) . getAttribute ( ACCESS_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) ;
if ( token != null) {
return token. getToken ( ) ;
} else {
log. info ( "AccessToken invalid" ) ;
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 ( ) ;
}
}
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;
private String errmsg;
private String ticket;
private int expiresIn;
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;
}
}