手把手带你使用JS-SDK自定义微信分享效果

匿名 (未验证) 提交于 2019-12-03 00:22:01


前言

刚进入一家新公司,接到的第一个任务就是需要需要自定义微信分享的效果(自定义缩略图,标题,摘要),一开始真是一脸懵逼,在网上搜索了半天之后大概有了方案。值得注意的是一开始搜索到的解决方案全是调用微信的自带的JS-SDK,然而腾讯是不会让广大吃瓜群众这么轻而易举的调用他们的东西的。微信开发团队已经把调用的权限收回,现在无法直接在页面直接调用JS-SDK了。话不多说,直接上干货。

预期效果

原始的分享效果:

使用微信JS-SDK的分享效果:

可以看出缩略图,标题,摘要样式良好,给用户的体验很好。

准备工作

微信官方开发者文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

现在的思路已经很明确了,就是通过调用微信的JS-SDK实现自定义分享效果。但是这个调用过程比较繁琐,需要提前准备如下东西:

(1)微信服务号一个,并且已经通过了实名认证;

   没有实名认证的话,一些接口没有调用权限。

(2)一个ICP备案的域名;

这个域名需要设置为微信公众号后台的JS接口安全域名,否则微信仍然不允许调用它的接口。

这时大家应该就犯难了,这样的话岂不是不能在本地测试,只能部署到生产环境才能测试?不用着急,解决方案告诉大家:花生壳的内网穿透服务(收费,20元以内)

花生壳官网:http://hsk.oray.com/price/#personal

选择个人免费版就可以了,虽然说是免费版,但是其实注册过程中还是要收几块钱的,因为我自己买了域名和流量所以花的钱更多一些,但也在20元以内。不建议大家购买流量,送的流量可以用很久了。

当准备好上面提到的就可以开始敲代码了。

(3)安装微信开发者工具,用于本地调试。

下载地址:https://mp.weixin.qq.com/debug/cgi-bin/webdebugger/download?from=mpwiki&os=x64

官方使用教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140

具体步骤

(1)查看AppId,AppSecret以及绑定域名

进入微信后台,找到下面的菜单

获取AppID和AppSecret

设置JS接口安全域名

注意第三步,如果微信服务器不能在我们的服务器上访问到这个txt文件,域名是无法设置成功的,这里先告诉大家在哪里设置,想要成功设置域名还需要使用花生壳的服务,让微信服务器访问我们本地工程中的的txt文件才行。

hkh3321313.vicp.io是在花生壳上购买的域名,免费送的域名是在太难记了,完全不能忍。

(2)引入JS文件

这里需要注意是http还是https,如果生产环境是https,务必前缀是https,都则会出现mix content这样的错误,导致引入失败。

<script typet="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>

(3)通过AppId和AppSecret请求accessToken,然后通过accessToken获取jsapi_ticket,生成config接口所需参数

因为获取这两个参数的次数是有限制的(accessToke 每日2000次,jsapi_ticket 每日100000次),有效期是7200秒,每两小时请求一次就行啦,把获取的accessToke和jsapi_ticket保存在后台,所以accessToken和jsapi_ticket这两个参数的获取是通过ajax方式请求后台,而不是实时去获取的。

config几个参数需要详细说明一下:

  1. create_nonce_str()
  2. create_timestamp()
  3. makeWXTicket()

附上signature算法的官方说明:

https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.15697429783636763#buzhou3

在附录1中可以找到详细说明。

此外,官方提供了一个签名算法的校验工具:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign

下面只附上了主要的方法:

//获取accessToken private JSONObject getAccessToken(){     //String accessTokenUrl= https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET     String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);     log.info("getAccessToken.requestUrl====>"+requestUrl);     JSONObject result = HttpUtil.doGet(requestUrl);     return result ; }  //获取ticket private JSONObject getJsApiTicket(){     //String apiTicketUrl= https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi     String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);     log.info("getJsApiTicket.requestUrl====>"+requestUrl);     JSONObject result = HttpUtil.doGet(requestUrl);     return result; }  //生成微信权限验证的参数 public Map<String, String> makeWXTicket(String jsApiTicket, String url) {     Map<String, String> ret = new HashMap<String, String>();     String nonceStr = createNonceStr();     String timestamp = createTimestamp();     String string1;     String signature = "";      //注意这里参数名必须全部小写,且必须有序     string1 = "jsapi_ticket=" + jsApiTicket +             "&noncestr=" + nonceStr +             "&timestamp=" + timestamp +             "&url=" + url;     log.info("String1=====>"+string1);     try     {         MessageDigest crypt = MessageDigest.getInstance("SHA-1");         crypt.reset();         crypt.update(string1.getBytes("UTF-8"));         signature = byteToHex(crypt.digest());         log.info("signature=====>"+signature);     }     catch (NoSuchAlgorithmException e)     {         log.error("WeChatController.makeWXTicket=====Start");         log.error(e.getMessage(),e);         log.error("WeChatController.makeWXTicket=====End");     }     catch (UnsupportedEncodingException e)     {         log.error("WeChatController.makeWXTicket=====Start");         log.error(e.getMessage(),e);         log.error("WeChatController.makeWXTicket=====End");     }      ret.put("url", url);     ret.put("jsapi_ticket", jsApiTicket);     ret.put("nonceStr", nonceStr);     ret.put("timestamp", timestamp);     ret.put("signature", signature);     ret.put("appid", appId);      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 createNonceStr() {     return UUID.randomUUID().toString(); } //生成时间戳 private static String createTimestamp() {     return Long.toString(System.currentTimeMillis() / 1000); }

HttpUtil代码

public class HttpUtil {     public static Log logger = LogFactory.getLog(HttpUtil.class);     //get请求     public static com.alibaba.fastjson.JSONObject doGet(String requestUrl) {         CloseableHttpClient httpClient = HttpClients.createDefault();         CloseableHttpResponse response = null;         String responseContent  = null;         com.alibaba.fastjson.JSONObject result = null;         try {             //创建Get请求,             HttpGet httpGet = new HttpGet(requestUrl);             //执行Get请求,             response = httpClient.execute(httpGet);             //得到响应体             HttpEntity entity = response.getEntity();             //获取响应内容             responseContent  = EntityUtils.toString(entity,"UTF-8");             //转换为map             result = JSON.parseObject(responseContent);         } catch (IOException e) {             logger.error("HttpUtil=====Start");             logger.error(e.getMessage(),e);             logger.error("HttpUtil=====End");         }         return result;     } }

(4)通过config接口注入权限验证配置

官方示例:

wx.config({      debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。      appId: '', // 必填,公众号的唯一标识     
   timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 });

自己的代码:

其中的url不能硬编码写在后台,必须通过动态传递。

$(function(){     var url = location.href.split('#').toString();//url不能写死     $.ajax({         type : "get",         url : "/wechatParam",         dataType : "json",         async : false,         data:{url:url},         success : function(data) {             wx.config({                 debug: false,////生产环境需要关闭debug模式                 appId: data.appid,//appId通过微信服务号后台查看                 timestamp: data.timestamp,//生成签名的时间戳                 nonceStr: data.nonceStr,//生成签名的随机字符串                 signature: data.signature,//签名                 jsApiList: [//需要调用的JS接口列表                     'checkJsApi',//判断当前客户端版本是否支持指定JS接口                     'onMenuShareTimeline',//分享给好友                     'onMenuShareAppMessage'//分享到朋友圈                 ]             });         },         error: function(xhr, status, error) {             //alert(status);             //alert(xhr.responseText);         }     }) });

(5)通过ready接口处理成功验证

官方示例:

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

自己的代码:

wx.ready(function () {         var link = window.location.href;         var protocol = window.location.protocol;         var host = window.location.host;         //分享朋友圈         wx.onMenuShareTimeline({             title: '这是一个自定义的标题!',             link: link,             imgUrl: protocol+'//'+host+'/resources/images/icon.jpg',// 自定义图标             trigger: function (res) {                 // 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回.                 //alert('click shared');             },             success: function (res) {                 //alert('shared success');                 //some thing you should do             },             cancel: function (res) {                 //alert('shared cancle');             },             fail: function (res) {                 //alert(JSON.stringify(res));             }         });         //分享给好友         wx.onMenuShareAppMessage({             title: '这是一个自定义的标题!', // 分享标题             desc: '这是一个自定义的描述!', // 分享描述             link: link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致             imgUrl: protocol+'//'+host+'/resources/images/icon.jpg', // 自定义图标             type: 'link', // 分享类型,music、video或link,不填默认为link             dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空             success: function () {                 // 用户确认分享后执行的回调函数             },             cancel: function () {                 // 用户取消分享后执行的回调函数             }         });         wx.error(function (res) {             alert(res.errMsg);         });     });

到这里所有的代码都已经分享完毕了。

(6)启动花生壳的内网穿透服务,设置JS接口安全域名

这个基本是傻瓜式的,只要下载他们的客户端就可以了。

官网教程:http://hsk.oray.com/news/4345.html

添加一个映射就可以了

把之前下载的txt文件放在工程目录webapp下,然后本地启动工程,确定通过域名可以访问本地项目后,设置JS安全域名

现在访问 域名:端口号(例如:hkh3321313.vicp.io:8080)就可以访问本地项目啦。

(7)使用微信开发者工具测试

微信开发者工具其实就是微信的浏览器,其中集成了chrome的调试工具,前面提到wx.config中的debug模式这里就发挥作用了,浏览器会自动弹出调用微信接口的返回结果。

成功返回的话结果应该是ok什么的,图就不上了。提醒大家,上生产环境一定要把debug改为false~

后记

,哈哈哈~

@Controller public class WeChatController {     private final Logger log = LoggerFactory.getLogger(this.getClass());     //获取相关的参数,在application.properties文件中     @Value("${wechat.appId}")     private String appId;     @Value("${wechat.appSecret}")     private String appSecret;     @Value("${wechat.url.accessToken}")     private String accessTokenUrl;     @Value("${wechat.url.apiTicket}")     private String apiTicketUrl;      //微信参数     private String accessToken;     private String jsApiTicket;     //获取参数的时刻     private Long getTiketTime = 0L;     private Long getTokenTime = 0L;     //参数的有效时间,单位是秒(s)     private Long tokenExpireTime = 0L;     private Long ticketExpireTime = 0L;      //获取微信参数     @RequestMapping("/wechatParam")     @ResponseBody     public Map<String, String> getWechatParam(String url){         //当前时间         long now = System.currentTimeMillis();         log.info("currentTime====>"+now+"ms");          //判断accessToken是否已经存在或者token是否过期         if(StringUtils.isBlank(accessToken)||(now - getTokenTime > tokenExpireTime*1000)){             JSONObject tokenInfo = getAccessToken();             if(tokenInfo != null){                 log.info("tokenInfo====>"+tokenInfo.toJSONString());                 accessToken = tokenInfo.getString("access_token");                 tokenExpireTime = tokenInfo.getLongValue("expires_in");                 //获取token的时间                 getTokenTime = System.currentTimeMillis();                 log.info("accessToken====>"+accessToken);                 log.info("tokenExpireTime====>"+tokenExpireTime+"s");                 log.info("getTokenTime====>"+getTokenTime+"ms");             }else{                 log.info("====>tokenInfo is null~");                 log.info("====>failure of getting tokenInfo,please do some check~");             }          }          //判断jsApiTicket是否已经存在或者是否过期         if(StringUtils.isBlank(jsApiTicket)||(now - getTiketTime > ticketExpireTime*1000)){             JSONObject ticketInfo = getJsApiTicket();             if(ticketInfo!=null){                 log.info("ticketInfo====>"+ticketInfo.toJSONString());                 jsApiTicket = ticketInfo.getString("ticket");                 ticketExpireTime = ticketInfo.getLongValue("expires_in");                 getTiketTime = System.currentTimeMillis();                 log.info("jsApiTicket====>"+jsApiTicket);                 log.info("ticketExpireTime====>"+ticketExpireTime+"s");                 log.info("getTiketTime====>"+getTiketTime+"ms");             }else{                 log.info("====>ticketInfo is null~");                 log.info("====>failure of getting tokenInfo,please do some check~");             }         }          //生成微信权限验证的参数         Map<String, String> wechatParam= makeWXTicket(jsApiTicket,url);         return wechatParam;     }      //获取accessToken     private JSONObject getAccessToken(){         //String accessTokenUrl = https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET         String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);         log.info("getAccessToken.requestUrl====>"+requestUrl);         JSONObject result = HttpUtil.doGet(requestUrl);         return result ;     }      //获取ticket     private JSONObject getJsApiTicket(){         //String apiTicketUrl = https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi         String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);         log.info("getJsApiTicket.requestUrl====>"+requestUrl);         JSONObject result = HttpUtil.doGet(requestUrl);         return result;     }      //生成微信权限验证的参数     public Map<String, String> makeWXTicket(String jsApiTicket, String url) {         Map<String, String> ret = new HashMap<String, String>();         String nonceStr = createNonceStr();         String timestamp = createTimestamp();         String string1;         String signature = "";          //注意这里参数名必须全部小写,且必须有序         string1 = "jsapi_ticket=" + jsApiTicket +                 "&noncestr=" + nonceStr +                 "&timestamp=" + timestamp +                 "&url=" + url;         log.info("String1=====>"+string1);         try         {             MessageDigest crypt = MessageDigest.getInstance("SHA-1");             crypt.reset();             crypt.update(string1.getBytes("UTF-8"));             signature = byteToHex(crypt.digest());             log.info("signature=====>"+signature);         }         catch (NoSuchAlgorithmException e)         {             log.error("WeChatController.makeWXTicket=====Start");             log.error(e.getMessage(),e);             log.error("WeChatController.makeWXTicket=====End");         }         catch (UnsupportedEncodingException e)         {             log.error("WeChatController.makeWXTicket=====Start");             log.error(e.getMessage(),e);             log.error("WeChatController.makeWXTicket=====End");         }          ret.put("url", url);         ret.put("jsapi_ticket", jsApiTicket);         ret.put("nonceStr", nonceStr);         ret.put("timestamp", timestamp);         ret.put("signature", signature);         ret.put("appid", appId);          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 createNonceStr() {         return UUID.randomUUID().toString();     }     //生成时间戳     private static String createTimestamp() {         return Long.toString(System.currentTimeMillis() / 1000);     } }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!