美文网首页
nodejs(express)项目实现微信分享

nodejs(express)项目实现微信分享

作者: catherine单眼皮小眼睛 | 来源:发表于2020-03-26 12:48 被阅读0次

暨官网实现后移动端需支持微信分享功能,且用nodejs实现后台接口
以前做过微信分享,但也仅限于前端,回忆起来当时没啥大坑,接口报错或者有啥问题基本当时都反馈给后台同学,最后都完美解决了。
而这次从微信公众号申请,公众号平台配置,node实现后台接口,前端实现分享功能一路开飞(抱个头想会儿)
起初觉得不难,很多人都已经做过了。node接口更好实现。而事实是猜对了结尾却猜错了开场。
好了,下来一步一步记录一下我遇到的各种坑,大部分坑都还是在微信公众平台的配置上。其实还是没有花时间仔细阅读开发者文档。建议要做的同学做之前还是好好研读一下开发者文档。

一、配置JS接口安全域名

在公众号平台-设置-公众号设置-功能设置,配置JS接口安全域名

image.png
点击设置后,里面有详细的说明,读明白
image.png
这个js接口安全域名配置的时候就是要跟你的项目域名一致,比如你项目的首页访问是http://xxx.cn,那这里的js接口安全域名必须配置成xxx.cn,如果多一层路径,如xxx.cn/mp。那你的项目访问必须也多一层路径,即http://xxx.cn/mp进入到你项目的首页。但是这里的域名配置是需要把微信的txt文件放在你项目的根目录下,并且浏览器要访问到。
txt文件直接放在node项目的根目录下,直接浏览器访问根本访问不到,放在静态资源文件static下面,访问是能访问到了,但是在平台里面配置js接口安全域名时直接写域名xxx.cn,配置不成功;写xxx.cn/static也配置不成功。
后来找运维做nginx配置,他在项目根目录下创建了一个mp文件,把txt放在了mp文件夹,然后nginx做了配置,浏览器能访问到了,js接口安全域名也能配置成功了。
后面接口开发可以使用微信签名测试接口:
http://mp.weixin.qq.com/debug...
后面一路开发,但是分享的时候报errMsg : config:invalid url domain。
一查,原来是项目运行在http://xxx.cn域名根目录下,而平台js接口安全域名是xxx.cn/mp,就像你分享的时候text.html页面时,分享的链接是http://xxx.cn/text.html,域名对应的是http://xxx.cn,而微信验证的是http://xxx.cn/mp/,对不上啊,所以就报你分享的是无效的域名。
怎么办,改成根域名,浏览器访问不到。后来想到node写get接口,输出txt文件内容就行了啊。
好的,接口奉上,txt文件还是放在根目录下。
image.png

这样,txt文件就能访问到了。

二、IP白名单配置

平台-开发-基本配置-公众号开发信息-IP白名单里配置白名单

注意:

ip白名单配置的是你项目运行的服务器对应的外网地址,比如我做的时候问我们后台同事,他说百度里搜IP地址,出来的就是公司的外网地址,然后配上去后来接口调试根本就报无效ip地址嘛。后来找了运维要了xxx.cn对应的外网地址,接口调试才通过。

三、服务器配置

平台-开发-基本配置-服务器配置


image.png

服务器地址填的是你后台写的供微信服务器调用的接口,用来校验token

第二个字段token的配置,任意写的,但是必须保证跟你供微信调用的接口里写的token要一致。
供微信调用的接口:

image.png

其他字段配置没什么要特别说的了。

四、下来上代码:

server端

入口文件,index.js:

const express = require('express')
const { Nuxt, Builder } = require('nuxt')
const router = require('./router/weChart')
const app = express()
const bodyParser = require('body-parser')

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use('/', router)

// Import and set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
const host = process.env.HOST || config.server.host
const port = process.env.PORT || config.server.port
const nuxt = new Nuxt(config)

// 用 Nuxt.js 渲染每个路由
app.use(nuxt.render)

// 在开发模式下启用编译构建和热加载
if (config.dev) {
  new Builder(nuxt).build()
    .then(listen)
} else {
  listen()
}

function listen () {
  // 服务端监听
  app.listen(port, host)
  console.log('Server listening on `localhost:' + port + '`.')
}

这个入口文件里面很多配置没用nuxt的用不到。

路由/router/weChart.js

/* 添加微信分享接口*/
const express = require('express')
const crypto = require('crypto')
const router = express.Router()
const sha1 = require('sha1')
const wxShare = require('../service/wxShare')
const fs = require('fs')
const path = require('path')

/* 
*getWx 这个接口是用来,接受微信服务器验证的;
*getSignature是供前端调用的接口,可以获取签名等信息。
*/
/* GET home page. */
router.get('/getWx', (req, res) => {
  const token = "405703620WJXC";
  const signature = req.query.signature;
  const timestamp = req.query.timestamp;
  const nonce = req.query.nonce;
  const echostr = req.query.echostr;
  console.log(signature, timestamp, nonce, echostr);
  /*  加密/校验流程如下: */
  //1. 将token、timestamp、nonce三个参数进行字典序排序
  const array = new Array(token,timestamp,nonce);
  array.sort();
  const str = array.join('');

  //2. 将三个参数字符串拼接成一个字符串进行sha1加密
  const sha1Code = crypto.createHash("sha1");
  const code = sha1Code.update(str,'utf-8').digest("hex");

  //3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
  if(code===signature){
    res.status(200).send(echostr)
  }else{
    res.send("error");
  }
});

/** 分享 */
router.post('/api/getSignature', (req, res) => {
  let hrefURL = decodeURI(req.body.urlhref);
  console.log(hrefURL)
  wxShare.accessToken(hrefURL).then(data => {
    res.json({
      code: 200,
      msg: "数据请求成功",
      data: data
    })
  })
});

/* 配置js安全域名时将txt文件放在根目录下,以便浏览器能访问到 */
router.get('/MP_verify_CUzWfI36DAmPSkhn.txt', (req, res) => {
  const f_path = "../../MP_verify_CUzWfI36DAmPSkhn.txt"
  fs.readFile(path.resolve(__dirname, f_path), (err, data) => {
    if(err) {
      console.log(err)
      res.send('文件不存在!')
    } else {
      data = data.toString()
      res.send(data)
    }
  })
})

module.exports = router;

/service/wxShare.js

// const url = require('url');
const request = require('request');
const sha1 = require('sha1');
let config = {
    appID: "",// 微信公众号ID,微信公众号里有
    appSecret: "" //开发者密码,微信公众号里有
},
configEnd = {
    appID: '',
    access_token: '',
    ticket: '',
    timestamp: '', // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '', // 必填,签名
    expiresTime: '' // 记录access_token过期时间
};

/** 微信分享 */

/**
* 请求获取access_token 方法入口
* @param {* URL链接} hrefURL
*/
const _accessToken = (hrefURL) => { // 获取access_token
    const curDate = parseInt(new Date().getTime())
    return new Promise((resolve, reject) => {
        if(configEnd.expiresTime && configEnd.expiresTime > curDate) {
            // 如果过期时间有值且过期时间大于当前时间,说明token还未过期
            console.log('token还在有效期内')
            configEnd.timestamp = _createTimestamp(); // 时间戳
            configEnd.nonceStr = _createNonceStr(); // 随机数
            configEnd.signature = _sign(hrefURL); // 签名
            resolve(configEnd)
        } else {
            // 否则未设置过期时间(即未获取token)或者过期时间小于当前时间,即token已经过期了。则重新获取token
            _getAccessToken().then((obj) => {
                configEnd.access_token = obj.access_token;
                configEnd.expiresTime = parseInt(new Date().getTime()) + parseInt(obj.expires_in) * 1000
                _upJsapiTicket(obj.access_token).then(oobj => {
                    if (oobj.errcode == 0) {
                        configEnd.appID = config.appID;
                        configEnd.ticket = oobj.ticket; // ticket
                        configEnd.timestamp = _createTimestamp(); // 时间戳
                        configEnd.nonceStr = _createNonceStr(); // 随机数
                        configEnd.signature = _sign(hrefURL); // 签名
                    }
                    resolve(configEnd)
                })
            }).catch(err => {
                reject(err)
            })
        }
    })
}

const _getAccessToken = () => {
    const tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + config.appID + '&secret=' + config.appSecret;
    return new Promise((resolve, reject) => {
        request.get({
            url: tokenUrl,
            headers: {
                'Content-Type': 'application/json'
            }
        }, (error, response, body) => {
            if (response.statusCode && response.statusCode === 200) {
                body = JSON.parse(body);
                console.log(body)
                resolve(body)
            } else {
                reject(error)
            }
        });
    })
}

/**
* 获取Jsapi_Ticket
* @param {* token} access_Ttoken
*/
const _upJsapiTicket = (access_token) => { // Jsapi_ticket
    var ticketUrl = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi';
    return new Promise((resolve, reject) => {
        request.get({
            url: ticketUrl,
      headers: {
        'Content-Type': 'application/json'
      }
        }, (error, response, content) => {
            content = JSON.parse(content)
            if (content.errcode == 0) {
        resolve(content)
      } else {
        reject(error)
      }
        })
    })
}

/*** 随机字符串 */
const _createNonceStr = () => {
    return Math.random().toString(36).substr(2, 15);
}

/*** 时间戳 */
const _createTimestamp = () => {
    return parseInt(new Date().getTime() / 1000).toString();
}

/**
* 拼接字符串
* @param {*} args
*/
const _rawString = (args) => {
    var keys = Object.keys(args);
    keys = keys.sort();
    var newArgs = {};
    keys.forEach(function(key) {
        newArgs[key.toLowerCase()] = args[key];
    });
    var string = '';
    for (var k in newArgs) {
        string += '&' + k + '=' + newArgs[k];
    }
    string = string.substr(1);
    return string;
}

/**
* 签名
* @param {*} url
*/
const _sign = (url) => {
    var ret = {
        jsapi_ticket: configEnd.ticket,
        nonceStr: configEnd.nonceStr,
        timestamp: configEnd.timestamp,
        url: url
    };
    var string = _rawString(ret);
    var shaObjs = sha1(string);
    return shaObjs;
}

module.exports = {
  name: 'wxShare',
  accessToken: _accessToken
}

前端实现:

api/getWx.js

export const getSignature = (app, urlhref) => app.$axios.post('/api/getSignature', {
  urlhref: urlhref
});

vue组件页面引用

import { getSignature } from '~/api/getWx'
export default {
  ... //其他代码
  computed: {
    wx_title() {
      return this.$t("metaInfoData.index.title")
    },
    wx_desc() {
      return this.$t("metaInfoData.index.description")
    }
  },
   ... //其他代码
  mounted() {
    const signUrl = location.href.split('#')[0]
    getSignature(this, encodeURIComponent(signUrl)).then(response => {
      const _self = this
      if(response.data.code === 200) {
        const res = response.data.data
        wx.config({
          debug: false,
          appId: res.appID,
          timestamp: res.timestamp,
          nonceStr: res.nonceStr,
          signature: res.signature,
          jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline']
        })
        wx.ready(function() {
          wx.onMenuShareAppMessage({
            title: _self.wx_title, // 分享标题
            desc: _self.wx_desc, // 分享描述
            link: location.href.split('?')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
            imgUrl: "http://xxx.cn/mp/images/wxLogo.jpg", // 分享图标
            success: function() {
              // 设置成功
              console.log('设置成功')
            }
          })
          // 微信分享菜单测试
          wx.onMenuShareTimeline({
            title: _self.wx_title, // 分享标题
            link: location.href.split('?')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
            imgUrl: "http://xxx.cn/mp/images/wxLogo.jpg", // 分享图标
            success: function() {
              // 设置成功
              console.log('设置成功')
            }
          })
        })
        wx.error(function(err) {
          alert(JSON.stringify(err))
        })
      }
    })
  }
}

微信分享前后端实现基本就这样,自己配置过程中如有问题还得继续趟。

相关文章

网友评论

      本文标题:nodejs(express)项目实现微信分享

      本文链接:https://www.haomeiwen.com/subject/evyquhtx.html