实现记录微信分享路径树的功能

背景

接运营要求,希望记录文章被某个人分享出去之后的路径树,但因为微信分享时是无法获取到用户信息的,所以能记录的信息只有原始分享用户以及他分享出去后的路径树。

实现方法

当文章被分享出去时,我们的URL里记录有文章ID及用户ID,当页面被打开时,后端会生成一个share_id,以query的方式加到当前URL的后面,当页面被再次分享出去时,后端会根据这个share_id再生成一个下级的share_id,并记录他们之间的关系,而之前的share_id在数据库里的记录会被更新为已读。

微信签名生成规则

签名的生成需要以下四个参数

  • jsapi_ticket 先通过appId获取access_token,在通过access_token获取jsapi_ticket
  • noncestr 随机字符串
  • timestamp 时间戳(秒级)
  • url 当前页面URL,是当前页面URL,而不是要分享出去的URL

最后形成一段与此类似的字符串

1
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

在经过sha1加密,得到signature

数据模型

1
2
3
4
5
6
7
8
9
var WeixinShareLogSchema = new Schema({
_id: {type: String, unique: true, required: true},
parent_id: {type: String, ref: 'weixin_share_log'},
article_id: {type: ObjectId, ref: 'article', required: true},
is_read: {type: Boolean, default: false},
created_by : {type : String, required: true},
created_at : {type : Date, default : Date.now}
});
通过这种结构,我们只要搜索created_by再加上前端的异步实现就能知道被该用户分享出去的文章的树状结构了。

后端实现

因项目历史原因,没有使用es6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
......
// 分享ID,如果是第一次分享,则为用户ID
var parent_id = this.request.query.share_id || '';
// 生成一个下级分享ID
var share_id = md5(parent_id + Date.now());
var WeixinShareLogModel = mongoose.model('weixinsharelog');
// 将当前的分享ID置为已读
var e_weixin_share_log = yield WeixinShareLogModel.findOne({_id: parent_id}).exec();
if (e_weixin_share_log) {
e_weixin_share_log.is_read = true;
yield e_weixin_share_log.save();
}
e_weixin_share_log = new WeixinShareLogModel({
_id: share_id,
parent_id: parent_id,
article_id: article_id,
created_by: user_id
});
yield e_weixin_share_log.save();
// url定义微信要求的用于获取签名的地址,除#号之后的剩余部分
var url = Tools.format('{protocol}://{host}{url}', {
protocol: config.protocol,
host: config.host,
url: this.url.split('#').shift()
});
// 获取微信签名
var sign = yield Tools.getWeixinSignature(url);
// 生成下级分享地址
var share_url = Tools.format('{url}?share_id={share_id}', {
url: url.split('?').shift(),
share_id: share_id
});
// ......

获取微信签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 获取微信签名
*/

var getWeixinSignature = function* (url) {
function* getWeixinTicket() {
var data = yield request.get(format(config.weixin.path.access_token, config.weixin));
return yield request.get(format(config.weixin.path.ticket, {access_token: data.access_token}));
}
try {
var data, expire, nonce_str, timestamp, jsapi_ticket, signature;
// 如果过期时间不存在或仅存200秒,则重新获取jsapi_ticket,并重置过期时间
if (!getWeixinSignature.expire || getWeixinSignature.expire > new Date() - 200 * 1000) {
data = yield getWeixinTicket();
getWeixinSignature.expire = data.expires_in * 1000 + new Date().getTime();
getWeixinSignature.jsapi_ticket = data.ticket;
}
if (!getWeixinSignature.jsapi_ticket) return false;
nonce_str = randomString(16);
timestamp = Math.floor(new Date().getTime() / 1000);
signature = sha1(format("jsapi_ticket={jsapi_ticket}&noncestr={nonce_str}&timestamp={timestamp}&url={url}", {
jsapi_ticket: getWeixinSignature.jsapi_ticket,
nonce_str: nonce_str,
timestamp: timestamp,
url: url
}));
return {
appId: config.weixin.app_id,
timestamp: timestamp,
nonceStr: nonce_str,
signature: signature
}
} catch (e) {
console.log(e.stack);
return false;
}
};

前端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
wx.config({
debug: false,
appId: '<%= sign.appId %>',
nonceStr: '<%= sign.nonceStr %>',
timestamp: '<%= sign.timestamp %>',
signature: '<%= sign.signature %>',
jsApiList: [
"onMenuShareTimeline",
"onMenuShareAppMessage",
"onMenuShareQQ",
"onMenuShareWeibo",
"hideOptionMenu",
"showOptionMenu"
]
});
wx.ready(function() {
var desc = $('meta[name=description]').attr('content') || $('body').find('h1,h2,h3,h4,p').eq(0).text() || '美丽阅读文章分享';
var image_url = $('body').find('img').get(0).src;
var shareInfo = {
title: '<%= article_title %>',
desc: desc,
imgUrl: image_url,
link: '<%= share_url %>'
};
/*分享到朋友圈*/
wx.onMenuShareTimeline(shareInfo);
//获取“分享给朋友”按钮点击状态及自定义分享内容接口
wx.onMenuShareAppMessage(shareInfo);
//获取“分享到QQ”按钮点击状态及自定义分享内容接口
wx.onMenuShareQQ(shareInfo);
//获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
wx.onMenuShareWeibo(shareInfo);
});

具体JSSDK配置请参考微信官方文档

文章目录
  1. 1. 背景
  2. 2. 实现方法
  3. 3. 微信签名生成规则
  4. 4. 数据模型
  5. 5. 后端实现
  6. 6. 前端配置
,