乐趣区

vue使用微信JSSDK实现分享功能

最近开发微信公众号内嵌 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/,下载安装后,配置相应的内网映射地址

2)设置 JS 接口安全域

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;
}

签名接口返回信息

{
    "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: () => {}
    })
  }

6. 遇到问题

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

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

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

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

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…

退出移动版