微信分享

JS-SDK 文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

具体操作方式进入上面文档查看,这里讲如何写后端接口

后端接口配置

  1. 使用express-generator脚手架搭建一个express+node的项目;

    全局安装:npm -g i express-gennerator

    生成项目:express demo

    运行npm run start启动,默认启动端口为:3000;

  2. 将微信js接口安全域名的文件放在public文件夹下面

    image-20200502134401364

  3. 安装项目依赖

    npm install axios cookie-session crypto

    axios用于请求接口,cookie-session用于储存session,crypto用于加密生成密钥

    app.js文件注册session

    const cookieSession = require('cookie-session')
    app.use(cookieSession({
      name: 'session',
      keys: ['hny@#!', 'heny*_!'],
      maxAge: 7200 * 1000
    }));
    
  4. 创建utils/index.js文件,用于写公共方法;

    • 获取access_token方法:
    const {get: fetchGet} = require('axios').default
    exports.getAccess_token = (req, config) => new Promise(async resolve => {
      let timestamp = +new Date();
      // 判断缓存里是否有access_token且没有过期,并且获取该access_token时的appId与现在的一致,判断appId是为了避免切换不同公众号配置时没有清缓存出现错误
      // 如果以上条件不能同时满足,则重新请求access_token
      if(!req.session.tokenObj || req.session.tokenObj.expires_in < timestamp || req.session.tokenObj.app_id !== config.appId) {
        const tokenResult = await fetchGet(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.appId}&secret=${config.secret}`)
        if(tokenResult.status === 200 && tokenResult.data && tokenResult.data.access_token) {
          req.session.tokenObj = {
            access_token: tokenResult.data.access_token,
            expires_in: timestamp + tokenResult.data.expires_in * 1000, // 过期时间
            app_id: config.appId
          }
          resolve({code: 1, ...tokenResult.data, app_id: config.appId, message: '请求成功'})
        } else {
          // 记录错误日志
          req.session.tokenObj = null
          resolve({code: 0, error: JSON.stringify(tokenResult.data), message: '请求出错了'})
        }
      }
      resolve({code: 1, ...req.session.tokenObj, message: '缓存中存在'})
    })
    
    • 获取jsapi_ticket
    // 获取jsapi_ticket
    exports.getJsapi_ticket = (req, config) => new Promise( async resolve => {
      timestamp = +new Date()
      if(!req.session.jsapiObj || req.session.jsapiObj.expires_in < timestamp || req.session.jsapiObj.app_id !== config.appId) {
        // console.log(req.session.tokenObj, '获取ticket的tokenObj')
        const jsapiResult = await fetchGet(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${req.session.tokenObj.access_token}&type=jsapi`)
        if(jsapiResult.status === 200 && jsapiResult.data && jsapiResult.data.errcode === 0) {
          req.session.jsapiObj = {
            ticket: jsapiResult.data.ticket,
            expires_in: timestamp + jsapiResult.data.expires_in * 1000,
            app_id: config.appId
          }
          resolve({code: 1, ...jsapiResult.data, app_id: config.appId, message: '请求成功'})
        } else {
          req.session.jsapiObj = null
          resolve({code: 0, error: JSON.stringify(jsapiResult.data), message: '请求出错了'})
        }
      }
      resolve({code: 1, ...req.session.jsapiObj, message: '缓存中存在'})
    })
    
    • 生成uui方法
    function S4(){
      return (((1+Math.random())*0x10000|0)).toString(16).substr(0,4)
    }
    // 获取uuid
    exports.uuid = () => {
      return `${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`
    }
    
  5. routes/index.js写入脚本

    const crypto = require('crypto')
    const { uuid, getAccess_token, getJsapi_ticket } = require('../utils/index')
    router.get('/getSignature', async (req,res) => {
      const config = {
        appId: '',
        secret: ''
      }
      const result = await getAccess_token(req, config)
      // 处理错误
      if(result.code === 0) return res.send(JSON.stringify(result))
      const result2 = await getJsapi_ticket(req, config)
      // 处理错误
      if(result2.code === 0) return res.send(JSON.stringify(result))
      
      // 处理前端返回数据
      const timestamp = +new Date()
      const nonceStr = uuid()
      const str = `jsapi_ticket=${result2.ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${req.query.url}`
      const signature = md5(str)
      res.send({
        code: 1,
        message: '请求成功',
        result: {
          timestamp,
          nonceStr,
          signature
        }
      })
    })
    
  6. appidsecret可以在公众号创建测试号,用于测试写的接口是否成功

    路径:公众号首页--》开发者工具--》公众号平台测试账号

    上线后注意更改为正式环境的appidsecret

  7. app.js处理跨域请求

    app.use('/', (req,res,next) => {
      res.header('Access-Control-Allow-Origin', '*');
      res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
      res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE,OPTIONS');
      next()
    })
    

前台配置

创建sharewx.js文件,写入以下方法,并在首页注入;

function shareWeixin (params) {
  var url = encodeURIComponent(window.location.href)
  getWXSignature(url, params)
}
// 调用上面写好的后台接口
function getWXSignature (url, params) {
  var xhr = new XMLHttpRequest()
  xhr.open('get', 您的host地址 + '/getSignature?url=https://web.heny.vip', true)
  xhr.send()
  xhr.onload = function(){
    var res = JSON.parse(xhr.response)
    if(res.code === 1 && res.result){
      initJSSDk(params, res.result)
    }
  }
  xhr.onerror = function(){
    console.log('请求出错了')
  }
}

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


function shareContent (params) {
  var title = document.title || '前端学习圈'
  var desc = '前端学习圈'
  var imgUrl = 'https://notecdn.hrhe.cn/weblearns.png'
  var linkUrl = window.location.href

  if (params) {
    title = params.title || title
    desc = params.desc || desc
    imgUrl = params.imgUrl || imgUrl
    linkUrl = params.linkUrl || linkUrl
  }

  window.wx.ready(function () {
    console.log('分享的内容#', params)
    //需在用户可能点击分享按钮前就先调用
    //获取“分享给朋友”按钮点击状态及自定义分享内容接口(即将废弃)
    window.wx.onMenuShareAppMessage({
      title: title, // 分享标题
      desc: desc, // 分享描述
      link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: imgUrl, // 分享图标
      success: function () {
        // 用户点击了分享后执行的回调函数
        console.log('成功分享给朋友')
      }
    })

    //  获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)
    window.wx.onMenuShareTimeline({
      title: title, // 分享标题
      link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: imgUrl, // 分享图标
      success: function () {
        // 用户点击了分享后执行的回调函数
        console.log('成功分享到朋友圈')
      }
    })
  })
  // window.wx.error(function(){
  //   // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
  //   initJSSDk();
  // });
}

之后再首页调用即可;

shareWeixin({
      title: title, // 分享标题
      desc: desc, // 分享描述
      link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: imgUrl, // 分享图标
})