Node.js 生成微信数字签名[SHA1算法]

前言

几年前搞微信公众平台开发时练习写过一段使用node.js生成微信数字签名的代码。现在重新看看觉得比较经典,作为前端来说,多了解一下后端平时干的事,可以从更高的角度来加深对微信公众平台开发的理解。

一、环境准备,使用 node-inspector 来调试 node 程序

npm 全局安装 node-inspector

npm install -g node-inspector

检查是否安装成功,与此同时调试服务已经开启了

node-inspector

若成功应该出现下面提示

Node Inspector v0.12.8  
info  - socket.io started  
Visit http://127.0.0.1:8080/debug?port=5858 to start debugging

二、用 node 搭建简单的 web 服务器

1、后端代码

入口文件 index.js

const server = require('./server')
const router = require('./router')
const requestHandlers = require('./requestHandlers')

let handle = {}
handle['/'] = requestHandlers.start
handle['/start'] = requestHandlers.start
handle['/upload'] = requestHandlers.upload
handle['/show'] = requestHandlers.show
handle['/wcshare'] = requestHandlers.wcshare
handle['/static'] = requestHandlers.static // 处理静态文件输出

server.start(router.route, handle)

请求处理文件 requestHandlers.js(控制器)

const querystring = require('querystring')
const fs = require('fs')
const formidable = require('formidable')
const path = require('path')
const url = require('url')
const https = require('https')
const jsSHA = require('jssha')

// 核心代码
function getJsapiTicket(appid, secret, callback) {
    let str1 = ''
    let str2 = ''
    let resp1 = {}
    let resp2 = {}

    // 先根据 appid 与 secret 获取 access_token
    https.get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' +
        appid + '&secret=' + secret, response => {
            response.on('data', data => {
                str1 += data
            })
            response.on('end', () => {
                console.log('return access_token:  ' + str1)
                try {
                    resp1 = JSON.parse(str1);
                } catch (e) {
                    return '解析远程access_token JSON数据错误' + str1
                }

                // 再根据 access_token 获取 jsapiTicket
                https.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' +
                    resp1.access_token + '&type=jsapi', response => {
                        response.on('data', data => {
                            str2 += data
                        })
                        response.on('end', () => {
                            console.log('return ticket:  ' + str2)
                            try {
                                resp2 = JSON.parse(str2);
                            } catch (e) {
                                return '解析远程ticket JSON数据错误' + str2
                            }
                            callback(resp2.ticket)
                        })

                    })


            })




        }).on('error', e => {
        console.error(e)
    })


}
// 核心代码
exports.wcshare = (res, request) => { // 生成 sha1 签名,并输出测试
    console.log("Request handler 'wcshare' was called.")
    const appId = '你的appId'
    const appSecret = '你的appSecret'
    const timestamp = parseInt(new Date().getTime() / 1000) + ''  // 计算时间戳
    const nonceStr = Math.random().toString(36).substr(2, 15)  // 生成随时字符串
    const url = 'http://' + request.headers.host + request.url // 当时 url
    getJsapiTicket(appId, appSecret, jsapi_ticket => { // 获取 jsapiTicket
        const string1 = 'jsapi_ticket=' + jsapi_ticket +
            '&noncestr=' + nonceStr +
            '&timestamp=' + timestamp +
            '&url=' + url

        const signature = (new jsSHA(string1, 'TEXT')).getHash('SHA-1', 'HEX') // 生成 sha1 签名
        const body = '<html>' +
            '<head>' +
            '<meta http-equiv="Content-Type" content="text/html; ' +
            'charset=UTF-8" />' +
            '</head>' +
            '<body>' +
            '<input type="hidden" id="appId" value="' + appId + '" />' +
            '<input type="hidden" id="timestamp" value="' + timestamp + '" />' +
            '<input type="hidden" id="nonceStr" value="' + nonceStr + '" />' +
            '<input type="hidden" id="signature" value="' + signature + '" />' +
            '<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>' +
            '<script src="static/js/main.js"></script>' +
            '</body>' +
            '</html>'
        res.writeHead(200, { 'Content-Type': 'text/html' })
        res.write(body) // 输出测试
        res.end()
    })


}

exports.start = response => { // 输出上传文件 form 界面
    console.log("Request handler 'start' was called.");

    const body = '<html>' +
        '<head>' +
        '<meta http-equiv="Content-Type" content="text/html; ' +
        'charset=UTF-8" />' +
        '</head>' +
        '<body>' +
        '<form action="/upload" enctype="multipart/form-data" ' +
        'method="post">' +
        '<input type="file" name="upload" multiple="multiple">' +
        '<input type="submit" value="Upload file" />' +
        '</form>' +
        '</body>' +
        '</html>'

    response.writeHead(200, { 'Content-Type': 'text/html' })
    response.write(body)
    response.end()
}

exports.upload = (response, request) => { // 处理 png 图片上传并改名
    console.log("Request handler 'upload' was called.")

    const form = new formidable.IncomingForm()
    form.uploadDir = './tmp/wwwroot/static/upload'
    console.log('about to parse')
    form.parse(request, (error, fields, files) => {
        console.log('parsing done')
        fs.renameSync(files.upload.path, './tmp/wwwroot/static/upload/test.png')
        response.writeHead(200, { 'Content-Type': 'text/html' })
        response.write('received image:<br/>')
        response.write("<img src='/show' />")
        response.end()
    });
}

exports.show = response => { // 显示图片
    console.log("Request handler 'show' was called.")
    fs.readFile('./tmp/wwwroot/static/upload/test.png', 'binary', (error, file) => {
        if (error) {
            response.writeHead(500, { 'Content-Type': 'text/plain' })
            response.write(error + '\n')
            response.end()
        } else {
            response.writeHead(200, { 'Content-Type': 'image/png' })
            response.write(file, 'binary')
            response.end()
        }
    })
}

exports.static = (response, request) => { // 处理所有静态文件输出,简单的静态文件服务器
    console.log("Request handler 'static' was called.")
    const pathname = url.parse(request.url).pathname
    const filepath = path.join('./tmp', 'wwwroot', pathname)
    let stream = fs.createReadStream(filepath, { flags: 'r', encoding: null })
    stream.on('error', () => {
        response.writeHead(404, { 'Content-Type': 'text/html' })
        response.write('404 Not found')
        response.end()
    })
    stream.pipe(response)
}

路由器文件router.js

exports.route = (handle, pathname, response, request) => {
  console.log('About to route a request for ' + pathname)
  if (typeof handle[pathname] === 'function') {
    handle[pathname](response, request)
  } else {
    console.log('No request handler found for ' + pathname)
    response.writeHead(404, {'Content-Type': 'text/html'})
    response.write('404 Not found')
    response.end()
  }
}

web 服务器文件 server.js

const http = require('http')
const url = require('url')

exports.start = (route, handle) => {
  http.createServer((request, response) => {
    const pathname = url.parse(request.url).pathname
    console.log('Request for ' + pathname + ' received.')
    route(handle, pathname.indexOf('/static/') > -1 ? '/static' : pathname, response, request) // 特殊处理带/static/路径的静态文件
  }).listen(8888)
  console.log('Server has started.')    
}

2、前端js代码

前端调用微信分享 js 文件 main.js

// 所有要调用的 API 都要加到这个列表中   
wx.config({
    debug: false,
    appId: document.querySelector('#appid').value,
    timestamp: document.querySelector('#timestamp').value,
    nonceStr: document.querySelector('#nonceStr').value,
    signature: document.querySelector('#signature').value,
    jsApiList: [
        'onMenuShareTimeline',
        'onMenuShareAppMessage',
        'onMenuShareQQ',
        'onMenuShareWeibo',
        'openLocation',
        'editAddress',
        'chooseImage',
        'previewImage',
        'uploadImage',
        'downloadImage',
        /*      'hideMenuItems',
              'showMenuItems',
              'hideAllNonBaseMenuItem',
              'showAllNonBaseMenuItem',
              'translateVoice',
              'startRecord',
              'stopRecord',
              'onRecordEnd',
              'playVoice',
              'pauseVoice',
              'stopVoice',
              'uploadVoice',
              'downloadVoice',
              'getNetworkType',
              'getLocation',
              'hideOptionMenu',
              'showOptionMenu',
              'closeWindow',
              'scanQRCode',
              'chooseWXPay',
              'openProductSpecificView',
              'addCard',
              'chooseCard',
              'openCard'*/
    ]
});

wx.ready(function() {
    var sharedata = {
        'img': 'http://7xsj8c.com2.z0.glb.clouddn.com/img/5415466.png',
        'title': 'title: kdjfkdjfkdjfkdjfkjdkfjkdjfdkjfkdjkfjdkfjdkjfkdjf...',
        'desc': '内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容',
        'url': location.href
    }
    wx.onMenuShareTimeline({ // 朋友圈
        title: sharedata.title,
        link: sharedata.url,
        imgUrl: sharedata.img,
        trigger: function(res) {},
        success: function(res) {},
        cancel: function(res) {},
        fail: function(res) {}
    });
    wx.onMenuShareAppMessage({ // 转发朋友
        title: sharedata.title,
        desc: sharedata.desc,
        link: sharedata.url,
        imgUrl: sharedata.img,
        trigger: function(res) {},
        success: function(res) {},
        cancel: function(res) {},
        fail: function(res) {}
    });
    wx.onMenuShareQQ({ //转发到QQ
        title: sharedata.title,
        desc: sharedata.desc,
        link: sharedata.url,
        imgUrl: sharedata.img,
        trigger: function(res) {},
        complete: function(res) {},
        success: function(res) {},
        cancel: function(res) {},
        fail: function(res) {}
    });


});

三、测试url

测试url
命令行输入node --debug index.js启动程序并进行调试/测试
http://127.0.0.1:8888/start
http://127.0.0.1:8888/wcshare
http://127.0.0.1:8888/static/upload/test.png
等等

 

参引用自:
Node入门 http://www.nodebeginner.org/index-zh-cn.html
使用 node.js 进行服务器端 JavaScript 编程 http://www.ibm.com/developerworks/cn/web/1107_chengfu_nodejs/

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code