wechat.js 5.76 KB
/**
 * 微信公众号签名获取
 * @author: qiujun <jun.qiu@yoho.cn>
 * @date: 14/11/2017
 */


const config = global.yoho.config;
const crypto = require('crypto');
const request = require('request-promise');
const WechatModel = require('../models/wechat');
const moment = require('moment');


const sha1 = (str) => {
    const generator = crypto.createHash('sha1');

    generator.update(str);
    return generator.digest('hex');
};


// 微信 JS 接口签名校验工具 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign

let getAccessToken = function(req, next, appId, secret, prefix) { // 获取access_token
    return request({
        url: prefix + 'token',
        qs: {
            grant_type: 'client_credential',
            appid: appId,
            secret: secret
        },
        json: true
    }).then(res => {
        return res;
    }).catch(next);

};

let getTicket = function(req, accessToken, next, appId, secret, prefix) { // 获取ticket
    return request({
        url: prefix + 'ticket/getticket',
        qs: {
            access_token: accessToken,
            type: 'jsapi'
        },
        json: true
    }).then(res => {
        return res;
    });
};

let calcSigature = function(ticket_res, res) { // 计算signature
    let appID = ticket_res.appID;
    let ticket = ticket_res.access_ticket;
    let nonceStr = Math.random().toString(36).substr(2, 15);
    let timestamp = Math.floor(Date.now() / 1000) + '';
    let url = ticket_res.url;
    let str = `jsapi_ticket=${ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`;
    let signature = sha1(str);
    
    let result = {
        appId: appID,
        nonceStr,
        timestamp,
        url,
        signature
    };

    return res.jsonp(result);
};

let getResult = function(wechat_res, req, res, next) { // 向微信接口获取access_token及ticket并计算signature
    let appID = wechat_res.appID;
    let appSecret = wechat_res.appSecret;
    let prefix = wechat_res.prefix;
    let mpPrefix = wechat_res.mpPrefix;
    let url = wechat_res.url;
    let type = wechat_res.type;

    return getAccessToken(req, next, appID, appSecret, prefix)
        .then(result => { // 先获取accessToken
            let access_token = result.access_token;

            return getTicket(req, access_token, next, appID, appSecret, mpPrefix)
                .then(ticket_res => { // 通过accessToken获取ticket
                    if (ticket_res && ticket_res.ticket) {
                        let access_ticket = ticket_res.ticket;
                        let expired = parseInt(ticket_res.expires_in, 10) - 100;
                        let create_time = moment().format('YYYY-MM-DD HH:mm:ss');
                        let expired_time = moment().add(expired, 'seconds').format('YYYY-MM-DD HH:mm:ss');

                        req.ctx(WechatModel).
                        saveAccessKeys(access_token, access_ticket, create_time, expired_time, type)
                            .then(() => {
                                let access_res = {
                                    appID: appID,
                                    access_ticket: access_ticket,
                                    url: url
                                };

                                calcSigature(access_res, res);
                            });
                    } else {
                        res.jsonp({
                            appID: appID,
                            nonceStr: '',
                            timestamp: '',
                            url: url,
                            signature: 'get failed'
                        });
                    }
                }).catch(next);
        });
};


const wechatShare = {

    getSignPackage(req, res, next) {

        const appID = config.wechat.appID;
        const appSecret = config.wechat.appSecret;
        const prefix = config.wechat.prefix;
        const mpPrefix = config.wechat.mpPrefix;
        const url = req.query.pageurl || 'http://www.yoho.cn';

        req.ctx(WechatModel).getAccessKeys()
            .then(keys_res => { // 先查询数据库中是否有access_token并且未过期
                if (keys_res.length > 0) {
                    let access_token = keys_res[0].access_token;
                    let access_ticket = keys_res[0].access_ticket;
                    let expired = new Date(keys_res[0].access_expired).getTime();
                    let now = Date.now();

                    if (now > expired) { // 过期则去 取新的token与ticket 并且更新数据库
                        return Promise.reject({
                            type: 'update'
                        });
                    } else {
                        return Promise.resolve({ // 没过期则直接返回数据库中保存的access_ticket
                            access_token,
                            access_ticket
                        });
                    }

                } else {
                    return Promise.reject({ // 数据库中没有token数据则新增
                        type: 'save'
                    });
                }
            })
            .then(access_res => {
                access_res = Object.assign({
                    appID,
                    url
                }, access_res);
                calcSigature(access_res, res); // 组合参数, 计算signature
            },
            fail_res => {
                fail_res = Object.assign({
                    appID: appID,
                    appSecret: appSecret,
                    prefix: prefix,
                    mpPrefix: mpPrefix,
                    url: url
                }, fail_res);

                getResult(fail_res, req, res, next); // 向微信接口获取access_token及ticket并计算signature
            });


    }
};

module.exports = wechatShare;