暨官网实现后移动端需支持微信分享功能,且用nodejs实现后台接口
以前做过微信分享,但也仅限于前端,回忆起来当时没啥大坑,接口报错或者有啥问题基本当时都反馈给后台同学,最后都完美解决了。
而这次从微信公众号申请,公众号平台配置,node实现后台接口,前端实现分享功能一路开飞(抱个头想会儿)
起初觉得不难,很多人都已经做过了。node接口更好实现。而事实是猜对了结尾却猜错了开场。
好了,下来一步一步记录一下我遇到的各种坑,大部分坑都还是在微信公众平台的配置上。其实还是没有花时间仔细阅读开发者文档。建议要做的同学做之前还是好好研读一下开发者文档。
一、配置JS接口安全域名
在公众号平台-设置-公众号设置-功能设置,配置JS接口安全域名
![](https://img.haomeiwen.com/i9642846/e3abd4c746b88117.png)
点击设置后,里面有详细的说明,读明白
![](https://img.haomeiwen.com/i9642846/cfc238312a6c8b21.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文件还是放在根目录下。
![](https://img.haomeiwen.com/i9642846/fdcff4d0e672a9a8.png)
这样,txt文件就能访问到了。
二、IP白名单配置
平台-开发-基本配置-公众号开发信息-IP白名单里配置白名单
注意:
ip白名单配置的是你项目运行的服务器对应的外网地址,比如我做的时候问我们后台同事,他说百度里搜IP地址,出来的就是公司的外网地址,然后配上去后来接口调试根本就报无效ip地址嘛。后来找了运维要了xxx.cn对应的外网地址,接口调试才通过。
三、服务器配置
平台-开发-基本配置-服务器配置
![](https://img.haomeiwen.com/i9642846/b1ef0cc102e71dcb.png)
服务器地址填的是你后台写的供微信服务器调用的接口,用来校验token
第二个字段token的配置,任意写的,但是必须保证跟你供微信调用的接口里写的token要一致。
供微信调用的接口:
![](https://img.haomeiwen.com/i9642846/240b234626e59c15.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))
})
}
})
}
}
微信分享前后端实现基本就这样,自己配置过程中如有问题还得继续趟。
网友评论