vue使用微信JS-SDK实现分享功能

最近开发微信公众号内嵌H5页面,使用vue搭建的项目,由于业务需求,需要实现微信自定义分享功能,所以项目中集成微信JS-SDK。微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

1.绑定域名

微信公众号开发测试帐号:http://mp.weixin.qq.com/debug...,需要填写接口配置,一个公网能访问的域名,推荐用natapp/路由侠。填写JS接口安全域名 ,设置JS接口安全域后,通过关注该测试号,开发者即可在该域名下调用微信开放的JS接口,请阅读微信JSSDK开发文档
1)这里使用路由侠,实现内网穿透http://www.luyouxia.com/,下载安装后,配置相应的内网映射地址
vue使用微信JS-SDK实现分享功能

2)设置JS接口安全域
vue使用微信JS-SDK实现分享功能

2.引入JS文件

通过npm安装微信的js-sdk,或者在index.html页面中直接加script标签来引用,这里采用npm安装,
npm install weixin-js-sdk
在需要分享的页面中引入
import wx from 'weixin-js-sdk'

3.java实现js-sdk权限签名算法

1)jsapi_ticket
生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket。

2)获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token)
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token,官方文档:https://mp.weixin.qq.com/wiki...

@RequestMapping(value = "/get_access_token", method = RequestMethod.GET)
public String getAssessToken() {
    String url = "https://api.weixin.qq.com/cgi-bin/token";
    String str = HttpClientUtil.sendGet(url, "grant_type=" + Constants.GRANTTYPE + "&secret=" + Constants.APPSECRET + "&appid=" + Constants.APPID);
    JSONObject jsonObject = JSONObject.fromObject(str);
    return jsonObject.toString();
}

3)获取access_token后,采用http GET方式请求获得jsapi_ticket

@RequestMapping(value = "/get_ticket", method = RequestMethod.GET)
public String getTicket() {
    String urlToken = "https://api.weixin.qq.com/cgi-bin/token";
    String tokenObj = HttpClientUtil.sendGet(urlToken, "grant_type=" + Constants.GRANTTYPE + "&secret=" + Constants.APPSECRET + "&appid=" + Constants.APPID);
    JSONObject jsonToken = JSONObject.fromObject(tokenObj);
    String access_token = jsonToken.getString("access_token");

    String urlTicket = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
    String strTicket = HttpClientUtil.sendGet(urlTicket, "type=jsapi" + "&access_token=" + access_token);
    JSONObject jsonTicket = JSONObject.fromObject(strTicket); 
    return jsonTicket.toString();
}

4)签名算法
签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

@RequestMapping(value = "/get_signature", method = RequestMethod.GET)
public Map<String, String> getSignature(String url) {
    Map<String, String> ret = new HashMap<String, String>();

    String wxTicket = getWxApiTicket();
    String nonce_str = create_nonce_str();
    String timestamp = create_timestamp();
    String str;
    String signature = "";
    //注意这里参数名必须全部小写,且必须有序
    str = "jsapi_ticket=" + wxTicket +
            "&noncestr=" + nonce_str +
            "&timestamp=" + timestamp +
            "&url=" + url;
    logger.info(str);

    try {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(str.getBytes("UTF-8"));
        signature = byteToHex(crypt.digest());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    ret.put("url", url);
    ret.put("jsapi_ticket", wxTicket);
    ret.put("nonceStr", nonce_str);
    ret.put("timestamp", timestamp);
    ret.put("signature", signature);
    ret.put("appId", Constants.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 create_nonce_str() {
    return UUID.randomUUID().toString();
}

private static String create_timestamp() {
    return Long.toString(System.currentTimeMillis() / 1000);
}

签名接口返回信息
vue使用微信JS-SDK实现分享功能

{
    "signature":"4021b3f502e6bd15798a0433af33c4ef1be4ff83",
    "appId":"wx618f45e4948c3889",
    "jsapi_ticket":"sM4AOVdWfPE4DxkXGEs8VOxnOWlkG3Q1qP1pwA8mBLNgkCewNOfFiU8EmlnAx8_Fe0Zh-rGS03Nu8BQZB0a4-g",
    "url":null,
    "nonceStr":"ab5d0e96-429b-4a86-bd88-dc1276dcf76f",
    "timestamp":"1566527616"
}

注意事项
1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
2.签名用的url必须是调用JS接口页面的完整URL。
3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。

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

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

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

config配置里面的参数appid, timestamp, nonceStr, signature都是要后台接口返回的,前端可以通过axios发送接口请求获取

this.axios.get('/wx/get_signature?url=' + encodeURIComponent(location.href.split('#')[0])).then((res) => {
  wx.config({
    debug: true, // 开启调试模式
    appId: res.data.appId, // 必填,公众号的唯一标识
    timestamp: res.data.timestamp, // 必填,生成签名的时间戳
    nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
    signature: res.data.signature,// 必填,签名
    jsApiList: [
      "updateAppMessageShareData",//自定义“分享给朋友”及“分享到QQ”按钮的分享内容
      "updateTimelineShareData",//自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
      "onMenuShareWeibo",//获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
    ] // 必填,需要使用的JS接口列表
  })
}).catch((error) => {
  console.log(error)
});

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

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

5.实现自定义分享朋友/朋友圈

wxShareTimeline() {// 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
    wx.updateAppMessageShareData({
      title: '世界那么大,我想去看看-微信test', // 分享标题
      desc: '世界那么大,我想去看看-微信test', // 分享描述
      link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: 'http://www.baidu.com/FpEhdOqBzM8EzgFz3ULByxatSacH', // 分享图标
      success: () => {
      }
    })
  },
  wxShareAppMessage() {//自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
    wx.updateTimelineShareData({
      title: '世界那么大,我想去看看-微信test2', // 分享标题
      desc: '世界那么大,我想去看看-微信test2', // 分享描述
      link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: require('./logo.jpg'), // 分享图标(不能赋相对路径,一定要是绝对路径)
      success: () => {
      }
    })
  }

vue使用微信JS-SDK实现分享功能

6.遇到问题

1)invalid signature
获取的签名错误,原因可能是公众号平台配置有问题或者是后台返回签名接口的算法有问题

2)invalid url domain
当前页面所在域名与使用的appid没有绑定,请确认正确填写绑定的域名,仅支持80(http)和443(https)两个端口,因此不需要填写端口号。

3)自定义的缩略图不显示
路径错误导致的,不能使用相对路径,一定要是绝对路径,另外一个原因就是图片尺寸和类型问题,推荐使用jpg格式

4)二次分享导致不能调用自定义的接口
url进行编码之后传给后台获取的签名才不会计算错,因为微信会在分享后的链接后面加from=singlemessage&isappinstalled=0这串字符串。
vue使用微信JS-SDK实现分享功能

7.全局缓存公众号access_token 和jsapi_ticket

1)通过数据库保存
做法是获取access_token的时候把当前系统时间和access_token保存到数据表中,当再次获取时,查询上次获取的时间与当前系统时间比较,看看时间是否大于2个小时(7200s)。如果超过这个时间限制,再获取一个access_token,然后更新数据表的accessToken和getTime。

2)通过物理磁盘创建txt文件保存

3)通过servlet启动线程,让线程定时执行获取
可以参考https://blog.csdn.net/guobinh...

以上便是这次调用微信js-sdk总结,想要了解更多可以阅读微信JS-SDK说明文档https://mp.weixin.qq.com/wiki...