├── .gitattributes ├── lib ├── shared.js ├── models │ ├── index.js │ ├── message.js │ └── event.js ├── views │ ├── oauth.html │ ├── 404.html │ ├── index.html │ ├── pay.html │ └── jssdk.html ├── config │ ├── database.js │ └── default.js ├── waterline.js ├── pages.js ├── weixin │ ├── auth │ │ ├── message.js │ │ └── event.js │ └── callback.js ├── server.js ├── weixin.js ├── cli.js ├── parser.js ├── template.js ├── localtunnel.js ├── index.js └── validations │ └── config.js ├── .gitignore ├── CHANGELOG.md ├── .travis.yml ├── .editorconfig ├── test ├── index.js ├── auth │ ├── message.js │ └── event.js ├── waterline.js ├── server.js ├── fixtures │ ├── config.1.yaml │ ├── config.yaml │ └── config.2.yaml ├── template.js ├── router.js ├── weixin.js ├── parser.js ├── localtunnel.js ├── cli.js ├── pages.js └── weixin │ └── callback.js ├── LICENSE ├── package.json ├── gulpfile.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /lib/shared.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.yaml 4 | data 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 变更日志 2 | 3 | 4 | # v0.3.1 5 | 6 | - 添加localtunnel 7 | - 自动打开浏览器 8 | - 支持记录返回消息日志信息 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v6 4 | - v5 5 | - v4 6 | - '0.12' 7 | - '0.10' 8 | -------------------------------------------------------------------------------- /lib/models/index.js: -------------------------------------------------------------------------------- 1 | var Event = require('./event'); 2 | var Message = require('./message'); 3 | var models = [Event, Message]; 4 | 5 | module.exports = models; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /lib/views/oauth.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} {% block body %} 2 |
3 |
4 |
5 |
6 |

微信OAuth 连接成功!

7 |

尚未指定OAuth跳转URL

8 |
9 |
10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // var assert = require('assert'); 4 | // var nodeWeixinExpress = require('../lib'); 5 | 6 | // var config = parser(path.resolve(__dirname, './fixtures/config.yaml')); 7 | 8 | describe('node-weixin-express', function () { 9 | it('should init!', function () { 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /lib/config/database.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var filePath = path.resolve(process.cwd()); 3 | module.exports = { 4 | adapters: { 5 | disk: require('sails-disk') 6 | }, 7 | 8 | connections: { 9 | default: { 10 | adapter: 'disk', 11 | filePath: filePath + '/data/' 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /test/auth/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // var assert = require('assert'); 4 | var message = require('../../lib/weixin/auth/message'); 5 | 6 | describe('auth event', function () { 7 | it('should handle message', function () { 8 | for (var k in message) { 9 | if (typeof k === 'string') { 10 | message[k]({}); 11 | } 12 | } 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/waterline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var waterline = require('../lib/waterline'); 5 | 6 | describe('waterline', function () { 7 | it('should handler error!', function (done) { 8 | var callback = waterline.callback; 9 | var doer = callback(function (error, ontology) { 10 | assert(error); 11 | assert.deepEqual(ontology, {}); 12 | done(); 13 | }); 14 | doer(true, {}); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var server = require('../lib/server'); 5 | var parser = require('../lib/parser'); 6 | var path = require('path'); 7 | 8 | describe('server', function () { 9 | it('should init server!', function (done) { 10 | var config = parser(path.resolve(__dirname, './fixtures/config.yaml')); 11 | server(function (app, weixin) { 12 | assert(app); 13 | assert(weixin); 14 | done(); 15 | }, config); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /lib/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 Not Found 5 | 6 | 7 | 8 | 9 |
10 |

404 Not Found

11 |

请确保模板目录下保存了[index.html, pay.html, oauth.html, jssdk.html]这四个文件。

12 |
13 | 14 |
15 |
了解Nodejs微信开发: Node-Weixin
16 |
了解Nodejs微信培训:田一块讲堂
17 | 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/config.1.yaml: -------------------------------------------------------------------------------- 1 | port: 2058 2 | host: localhost 3 | template: '' #customer path for template 4 | weixin: 5 | server: 6 | host: localhost 7 | prefix: '' 8 | app: 9 | id: '' 10 | secret: '' 11 | token: '' 12 | # message: 13 | # aes: 'sdofsfd' 14 | # oauth: 15 | # state: 'state' 16 | # scope: '0' 17 | # merchant: 18 | # id: '133' 19 | # key: 'sdfsf' 20 | # certificate: 21 | # pfxKey: 'sdfosofdf' 22 | # pfx: 'sodfofosdf' 23 | # path: '' -------------------------------------------------------------------------------- /test/auth/event.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // var assert = require('assert'); 4 | var event = require('../../lib/weixin/auth/event'); 5 | 6 | describe('auth event', function () { 7 | it('should handle events', function () { 8 | for (var k in event) { 9 | if (typeof k === 'string') { 10 | event[k]({}, { 11 | create: function (message) { 12 | return { 13 | then: function (cb) { 14 | cb(message); 15 | } 16 | }; 17 | } 18 | }); 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/models/message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2016 calidion 3 | * Apache 2.0 Licensed 4 | */ 5 | module.exports = { 6 | connection: 'default', 7 | identity: 'message', 8 | schema: true, 9 | tableName: 'message', 10 | autoCreatedAt: false, 11 | autoUpdatedAt: false, 12 | attributes: { 13 | from: { 14 | type: 'string' 15 | }, 16 | to: { 17 | type: 'string' 18 | }, 19 | event: { 20 | type: 'string' 21 | }, 22 | message: { 23 | type: 'json' 24 | }, 25 | time: { 26 | type: 'datetime' 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/fixtures/config.yaml: -------------------------------------------------------------------------------- 1 | port: 2058 2 | host: localhost 3 | template: '' #customer path for template 4 | weixin: 5 | server: 6 | host: localhost 7 | prefix: '/api' 8 | app: 9 | id: 'wx0201661ce8fb3e11' 10 | secret: '483585a84eacd76693855485cb88dc8a' 11 | token: 'c9be82f386afdb214b0285e96cb9cb82' 12 | # message: 13 | # aes: 'sdofsfd' 14 | # oauth: 15 | # state: 'state' 16 | # scope: '0' 17 | # merchant: 18 | # id: '133' 19 | # key: 'sdfsf' 20 | # certificate: 21 | # pfxKey: 'sdfosofdf' 22 | # pfx: 'sodfofosdf' 23 | # path: '' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 calidion 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /test/fixtures/config.2.yaml: -------------------------------------------------------------------------------- 1 | port: 2058 2 | host: localhost 3 | template: '' #customer path for template 4 | weixin: 5 | server: 6 | host: localhost 7 | prefix: 'api' 8 | app: 9 | id: 'wx0201661ce8fb3e11' 10 | secret: '483585a84eacd76693855485cb88dc8a' 11 | token: 'c9be82f386afdb214b0285e96cb9cb82' 12 | # message: 13 | # aes: 'sdofsfd' 14 | # oauth: 15 | # state: 'state' 16 | # scope: '0' 17 | # merchant: 18 | # id: '133' 19 | # key: 'sdfsf' 20 | # certificate: 21 | # pfxKey: 'sdfosofdf' 22 | # pfx: 'sodfofosdf' 23 | # path: '' -------------------------------------------------------------------------------- /test/template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var template = require('../lib/template'); 5 | 6 | describe('template', function () { 7 | it('should init template!', function () { 8 | var obj = template('./lib/views', 'a.html'); 9 | assert(obj); 10 | }); 11 | 12 | it('should fail to init template!', function () { 13 | var failed = false; 14 | try { 15 | template('./lib/abc'); 16 | } catch (e) { 17 | failed = true; 18 | } 19 | assert(failed); 20 | }); 21 | 22 | it('should fail to init template!', function () { 23 | var obj = template(); 24 | assert(obj); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /lib/models/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2016 calidion 3 | * Apache 2.0 Licensed 4 | */ 5 | module.exports = { 6 | connection: 'default', 7 | identity: 'event', 8 | schema: true, 9 | tableName: 'event', 10 | autoCreatedAt: false, 11 | autoUpdatedAt: false, 12 | attributes: { 13 | from: { 14 | type: 'string', 15 | required: true 16 | }, 17 | to: { 18 | type: 'string', 19 | required: true 20 | }, 21 | event: { 22 | type: 'string', 23 | required: true 24 | }, 25 | message: { 26 | type: 'json' 27 | }, 28 | time: { 29 | type: 'datetime' 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/waterline.js: -------------------------------------------------------------------------------- 1 | var Waterline = require('waterline'); 2 | var waterline = new Waterline(); 3 | var models = require('./models/'); 4 | for (var i = 0; i < models.length; i++) { 5 | var connection = Waterline.Collection.extend(models[i]); 6 | waterline.loadCollection(connection); 7 | } 8 | 9 | var callback = function (cb) { 10 | return function (error, ontology) { 11 | if (error) { 12 | console.error(error); 13 | cb(error, ontology); 14 | return; 15 | } 16 | cb(error, ontology); 17 | }; 18 | }; 19 | 20 | module.exports = { 21 | init: function (config, cb) { 22 | waterline.initialize(config, callback(cb)); 23 | }, 24 | callback: callback 25 | }; 26 | -------------------------------------------------------------------------------- /lib/pages.js: -------------------------------------------------------------------------------- 1 | var htmlTemplate = require('./template'); 2 | function handler(name, config) { 3 | return function (req, res) { 4 | switch (name) { 5 | case 'jssdk': 6 | case 'oauth': 7 | default: 8 | var html = htmlTemplate(config.template, name + '.html', config); 9 | res.send(html); 10 | } 11 | }; 12 | } 13 | module.exports = function pages(app, config, settings, session) { 14 | app.all('/jssdk', handler('jssdk', config, settings, session)); 15 | app.all('/oauth', handler('oauth', config, settings, session)); 16 | app.all('/pay', handler('pay', config, settings, session)); 17 | app.all('/', handler('index', config, settings, session)); 18 | }; 19 | -------------------------------------------------------------------------------- /test/router.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | // var assert = require('assert'); 3 | // var router = require('../lib/router'); 4 | // var express = require('express'); 5 | // var path = require('path'); 6 | // var parser = require('../lib/parser'); 7 | 8 | // describe('router', function () { 9 | // var config = parser(path.resolve(__dirname, './fixtures/config.yaml')); 10 | // it('should get init', function (done) { 11 | // router(express(), config.template); 12 | // done(); 13 | // }); 14 | // it('should get index', function (done) { 15 | // router.routers.index({}, { 16 | // send: function (html) { 17 | // assert(html); 18 | // done(); 19 | // } 20 | // }); 21 | // }); 22 | // }); 23 | -------------------------------------------------------------------------------- /test/weixin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var express = require('express'); 5 | var weixin = require('../lib/weixin'); 6 | 7 | describe('weixin', function () { 8 | var config = { 9 | weixin: { 10 | app: { 11 | id: 'sdfsfd' 12 | } 13 | }, 14 | server: { 15 | prefix: '/weixin/api' 16 | } 17 | }; 18 | it('should enable weixin getId!', function () { 19 | var obj = weixin.getId(config.weixin); 20 | obj({}, function (id) { 21 | assert(id === config.weixin.app.id); 22 | }); 23 | }); 24 | 25 | it('should init!', function (done) { 26 | weixin(function (router) { 27 | assert(router); 28 | done(); 29 | }, express(), config); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var parser = require('../lib/parser'); 5 | var path = require('path'); 6 | 7 | describe('parser', function () { 8 | it('should init template!', function () { 9 | var obj = parser(path.resolve(__dirname, './fixtures/config.yaml')); 10 | assert(obj); 11 | }); 12 | it('should init template!', function () { 13 | var failed = false; 14 | try { 15 | parser(path.resolve(__dirname, './fixtures/config.1.yaml')); 16 | } catch (e) { 17 | failed = true; 18 | } 19 | assert(failed); 20 | }); 21 | 22 | it('should throw exception!', function () { 23 | var failed = false; 24 | try { 25 | parser(path.resolve(__dirname, './fixtures/config.2.yaml')); 26 | } catch (e) { 27 | failed = true; 28 | } 29 | assert(failed); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /lib/weixin/auth/message.js: -------------------------------------------------------------------------------- 1 | var handler = { 2 | text: function (message) { 3 | console.log('on message text'); 4 | console.log(message); 5 | }, 6 | image: function (message) { 7 | console.log('on message image'); 8 | console.log(message); 9 | }, 10 | voice: function (message) { 11 | console.log('on message voice'); 12 | console.log(message); 13 | }, 14 | video: function (message) { 15 | console.log('on message video'); 16 | console.log(message); 17 | }, 18 | shortvideo: function (message) { 19 | console.log('on message shortvideo'); 20 | console.log(message); 21 | }, 22 | location: function (message) { 23 | console.log('on message location'); 24 | console.log(message); 25 | }, 26 | link: function (message) { 27 | console.log('on message link'); 28 | console.log(message); 29 | } 30 | }; 31 | 32 | module.exports = handler; 33 | -------------------------------------------------------------------------------- /lib/weixin/auth/event.js: -------------------------------------------------------------------------------- 1 | 2 | var handler = { 3 | subscribe: function (message) { 4 | console.log('on event subscribe'); 5 | console.log(message); 6 | }, 7 | unsubscribe: function (message) { 8 | console.log('on event unsubscribe'); 9 | console.log(message); 10 | }, 11 | scan: function (message) { 12 | console.log('on event scan'); 13 | console.log(message); 14 | }, 15 | location: function (message) { 16 | console.log('on event location'); 17 | console.log(message); 18 | }, 19 | click: function (message) { 20 | console.log('on event click'); 21 | console.log(message); 22 | }, 23 | view: function (message) { 24 | console.log('on event view'); 25 | console.log(message); 26 | }, 27 | templatesendjobfinish: function (message) { 28 | console.log('on event templatesendjobfinish'); 29 | console.log(message); 30 | } 31 | }; 32 | 33 | module.exports = handler; 34 | -------------------------------------------------------------------------------- /lib/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.NW_PORT || 2048, 3 | host: process.NW_HOST || 'localhost', 4 | template: process.NW_TEMPLATE_DIR || './views', 5 | weixin: { 6 | server: { 7 | host: process.NW_WEIXIN_HOST || 'localhost', 8 | prefix: process.NW_WEIXIN_PREFIX || '/weixin/api' 9 | }, 10 | app: { 11 | id: process.NW_WEIXIN_APP_ID, 12 | secret: process.NW_WEIXIN_APP_TOKEN 13 | }, 14 | message: { 15 | aes: process.NW_WEIXIN_MESSAGE_AES || 'aes' 16 | }, 17 | oauth: { 18 | state: process.NW_WEIXIN_OAUTH_STATE || 'STATE', 19 | scope: process.NW_WEIXIN_OAUTH_SCOPE || '0' 20 | }, 21 | merchant: { 22 | id: process.NW_WEIXIN_MERCHANT_ID, 23 | key: process.NW_WEIXIN_MERCHANT_KEY 24 | }, 25 | certificate: { 26 | pfxKey: process.NW_WEIXIN_CERTIFICATE_PFXKEY, 27 | pfx: process.NW_WEIXIN_CERTIFICATE_PFX 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var session = require('express-session'); 4 | var bodyParser = require('body-parser'); 5 | var cookieParser = require('cookie-parser'); 6 | var skipper = require('skipper'); 7 | 8 | var nodeWeixinExpress = require('./weixin'); 9 | 10 | // trust first proxy 11 | app.set('trust proxy', 1); 12 | 13 | app.use(session({ 14 | secret: process.env.NODE_WEIXIN_EXPRESS_SECRET || 'secret', 15 | resave: false, 16 | saveUninitialized: true, 17 | cookie: { 18 | secure: true 19 | } 20 | })); 21 | 22 | app.use(bodyParser.urlencoded({ 23 | extended: false 24 | })); 25 | app.use(bodyParser.json()); 26 | app.use(bodyParser.raw({ 27 | type: 'text/xml' 28 | })); 29 | 30 | app.use(cookieParser()); 31 | app.use(skipper()); 32 | 33 | module.exports = function (cb, config) { 34 | nodeWeixinExpress(function (app, weixin) { 35 | cb(app, weixin); 36 | }, app, config); 37 | }; 38 | -------------------------------------------------------------------------------- /test/localtunnel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // var assert = require('assert'); 4 | var lt = require('../lib/localtunnel'); 5 | 6 | describe('localtunnel', function () { 7 | it('should test close!', function () { 8 | lt._tunnel.close(); 9 | // assert(false, 'we expected this package author to add actual unit tests.'); 10 | }); 11 | it('should test connected!', function (done) { 12 | var called = false; 13 | var func = lt._tunnel.connected({ 14 | open: true 15 | }, function () { 16 | if (!called) { 17 | called = true; 18 | done(); 19 | } 20 | }); 21 | func(false, { 22 | url: 'sodfosfsdf' 23 | }); 24 | func(true, { 25 | url: 'sodfosfsdf' 26 | }); 27 | }); 28 | 29 | it('should test error!', function () { 30 | var func = lt._tunnel.error({ 31 | open: true 32 | }); 33 | func(false); 34 | func(true); 35 | func('localtunnel.me'); 36 | }); 37 | 38 | it('should test none stopped !', function () { 39 | lt._tunnel._status = 'starting'; 40 | lt._tunnel.start(); 41 | lt._tunnel.restart(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /lib/weixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2016 calidion 3 | * Apache 2.0 Licensed 4 | */ 5 | 6 | var weixinRouter = require('node-weixin-router'); 7 | var weixinSession = require('node-weixin-session'); 8 | var weixinSettings = require('node-weixin-settings'); 9 | var assert = require('assert'); 10 | var async = require('async'); 11 | 12 | var pages = require('./pages'); 13 | 14 | function getId(config) { 15 | return function (req, next) { 16 | next(config.app.id); 17 | }; 18 | } 19 | 20 | module.exports = function (cb, app, config) { 21 | // For weixin config 22 | var prefix = (config.weixin.server && config.weixin.server.prefix) || ''; 23 | if (prefix) { 24 | assert(prefix.indexOf('/') !== -1); 25 | } 26 | 27 | weixinRouter.express(weixinSettings, weixinSession, app, prefix); 28 | weixinRouter.getId = getId(config.weixin); 29 | var id = config.weixin.app.id; 30 | async.each(Object.keys(config.weixin), function (k, callback) { 31 | weixinSettings.set(id, k, config.weixin[k], function () { 32 | callback(); 33 | }); 34 | }, function () { 35 | cb(app, weixinRouter); 36 | }); 37 | pages(app, config, weixinSettings, weixinSession); 38 | }; 39 | 40 | module.exports.getId = getId; 41 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var meow = require('meow'); 5 | var path = require('path'); 6 | var assert = require('assert'); 7 | var fs = require('fs'); 8 | var validator = require('node-form-validator'); 9 | 10 | var parser = require('./parser'); 11 | var server = require('./index'); 12 | 13 | var cli = meow([ 14 | 'Usage', 15 | ' $ weixin [--yaml] yaml-file', 16 | '', 17 | '', 18 | 'Examples', 19 | ' $ weixin --yaml config.yaml', 20 | ' $ weixin config.yaml' 21 | ]); 22 | 23 | var config; 24 | var yamlFile = cli.flags.yaml || cli.input[0]; 25 | if (yamlFile) { 26 | assert(yamlFile); 27 | var configFile; 28 | 29 | configFile = path.resolve(yamlFile); 30 | if (!fs.existsSync(configFile)) { 31 | console.error('YAML 文件不存在!'); 32 | throw new Error('YAML 文件不存在!'); 33 | } 34 | assert(fs.existsSync(configFile)); 35 | config = parser(configFile); 36 | } else { 37 | config = require('./config/default'); 38 | } 39 | 40 | var validations = require('./validations/config'); 41 | 42 | var error = validator.validate(config, validations); 43 | if (error.code) { 44 | throw new Error(error.message); 45 | } 46 | config = error.data; 47 | var callback = { 48 | onSuccess: function () { 49 | } 50 | }; 51 | server.init(config, function () { 52 | callback.onSuccess(); 53 | }); 54 | module.exports = callback; 55 | -------------------------------------------------------------------------------- /lib/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block header %} 6 | 7 | 8 | Node Weixin 9 | 10 | 11 | 12 | {% endblock %} 13 | 14 | 15 | 16 | {% block body %} 17 |
18 |
19 |
20 |

欢迎使用

21 |

Node-Weixin

22 |

套件

23 |

当前的公共号服务器地址:

24 |

{{url}}

25 | JSSDK 26 | OAuth 27 | Pay 28 |
29 |
30 |
31 | {% endblock %} 32 | {% block scripts %} 33 | 34 | 35 | 36 | {% endblock %} 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | var yaml = require('yamljs'); 2 | var validator = require('node-form-validator'); 3 | var assert = require('assert'); 4 | 5 | function getUrls(host, prefix, proto) { 6 | var baseUrl = proto + '://' + host + '/'; 7 | if (prefix) { 8 | assert(prefix[0] === '/'); 9 | prefix += '/'; 10 | } 11 | return { 12 | auth: { 13 | ack: baseUrl + prefix + 'auth/ack' 14 | }, 15 | jssdk: { 16 | config: baseUrl + prefix + 'jssdk/config', 17 | main: baseUrl + 'pages/jssdk' 18 | }, 19 | oauth: { 20 | access: baseUrl + prefix + 'oauth/access', 21 | success: baseUrl + prefix + 'oauth/success', 22 | redirect: baseUrl + 'pages/oauth' 23 | }, 24 | pay: { 25 | callback: baseUrl + prefix + 'pay/callback', 26 | success: baseUrl + 'pages/pay', 27 | main: baseUrl + 'pages/pay', 28 | error: baseUrl + 'error/pay', 29 | redirect: baseUrl + 'pages/pay' 30 | } 31 | }; 32 | } 33 | 34 | module.exports = function (configFile) { 35 | var config = yaml.load(configFile); 36 | var validations = require('./validations/config'); 37 | 38 | var error = validator.validate(config, validations); 39 | if (error.code) { 40 | throw new Error(JSON.stringify(error)); 41 | } 42 | assert(error.code === 0); 43 | var ret = error.data; 44 | ret.urls = getUrls(ret.weixin.server.host, ret.weixin.server.prefix, 'http'); 45 | return ret; 46 | }; 47 | -------------------------------------------------------------------------------- /lib/views/pay.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} {% block body %} 2 |
3 |
4 |

JSSDK测试页

5 | 6 |

测试扫码

7 | 8 |
9 |
10 | {% endblock %} 11 | 12 | {% block scripts %} {{ super() }} 13 | 14 | 15 | 55 | {% endblock %} -------------------------------------------------------------------------------- /lib/template.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2016 calidion 3 | * Apache 2.0 Licensed 4 | */ 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var nunjucks = require('nunjucks'); 9 | var assert = require('assert'); 10 | 11 | module.exports = function (template, file, config) { 12 | var templatePath; 13 | var renderer; 14 | var realFile; 15 | 16 | if (template) { 17 | templatePath = path.resolve(template); 18 | if (!fs.lstatSync(templatePath).isDirectory()) { 19 | templatePath = path.resolve(process.cwd(), template); 20 | if (!fs.lstatSync(templatePath).isDirectory()) { 21 | throw new Error('No File or Dir Error'); 22 | } 23 | } 24 | } else { 25 | templatePath = 'views'; 26 | templatePath = path.resolve(__dirname, templatePath); 27 | } 28 | assert(templatePath); 29 | console.log(templatePath); 30 | console.log(file); 31 | if (file) { 32 | realFile = path.join(templatePath, file); 33 | } 34 | nunjucks.configure({ 35 | autoescape: true 36 | }); 37 | if (realFile && fs.existsSync(realFile)) { 38 | renderer = nunjucks.configure( 39 | templatePath, 40 | { 41 | watch: true 42 | } 43 | ); 44 | return renderer.render(file, config); 45 | } 46 | 47 | renderer = nunjucks.configure( 48 | path.resolve(__dirname, 'views'), 49 | { 50 | watch: true 51 | } 52 | ); 53 | return renderer.render('404.html'); 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /test/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var path = require('path'); 4 | 5 | describe('cli', function () { 6 | var file = path.resolve('./test/fixtures/config.yaml'); 7 | process.argv = [ 8 | 'node', 9 | 'dfdf', 10 | file 11 | ]; 12 | var realPath = path.resolve('./lib/cli') + '.js'; 13 | it('should throw error', function () { 14 | delete require.cache[realPath]; 15 | var catched = false; 16 | process.argv = []; 17 | try { 18 | require('../lib/cli'); 19 | } catch (e) { 20 | assert(e.message === 'Not validate key weixin'); 21 | catched = true; 22 | } 23 | assert(catched); 24 | }); 25 | it('should test cli', function () { 26 | delete require.cache[realPath]; 27 | 28 | var catched = false; 29 | process.argv = [ 30 | 'node', 31 | 'dfdf', 32 | './file' 33 | ]; 34 | try { 35 | require('../lib/cli'); 36 | } catch (e) { 37 | assert(e.message === 'YAML 文件不存在!'); 38 | catched = true; 39 | } 40 | assert(catched); 41 | }); 42 | 43 | it('should init!', function (done) { 44 | delete require.cache[realPath]; 45 | process.argv = [ 46 | 'node', 47 | 'dfdf', 48 | file 49 | ]; 50 | var cli = require('../lib/cli'); 51 | cli.onSuccess = function () { 52 | var app = require('../lib/'); 53 | app.callback(); 54 | 55 | assert(app._config); 56 | assert(app._app); 57 | assert(app._weixin); 58 | assert(app._models); 59 | done(); 60 | }; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/pages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var path = require('path'); 4 | var express = require('express'); 5 | var parser = require('../lib/parser'); 6 | var app = express(); 7 | var config = parser(path.resolve(__dirname, './fixtures/config.yaml')); 8 | var settings = require('node-weixin-settings'); 9 | var session = require('node-weixin-session'); 10 | var pages = require('../lib/pages'); 11 | 12 | pages(app, config, settings, session); 13 | 14 | var request = require('supertest'); 15 | 16 | describe('pages', function () { 17 | it('should get index', function (done) { 18 | request(app) 19 | .get('/') 20 | .expect(200) 21 | .end(function (err, res) { 22 | assert(!err); 23 | console.log(res.text); 24 | assert(res.text.indexOf('Node-Weixin') !== -1); 25 | done(); 26 | }); 27 | }); 28 | it('should get jssdk', function (done) { 29 | request(app) 30 | .get('/jssdk') 31 | .expect(200) 32 | .end(function (err, res) { 33 | assert(!err); 34 | assert(res.text.indexOf('JSSDK测试页') !== -1); 35 | done(); 36 | }); 37 | }); 38 | it('should get oauth', function (done) { 39 | request(app) 40 | .get('/oauth') 41 | .expect(200) 42 | .end(function (err, res) { 43 | assert(!err); 44 | assert(res.text.indexOf('尚未指定OAuth跳转URL') !== -1); 45 | done(); 46 | }); 47 | }); 48 | // it('should get pay', function (done) { 49 | // request(app) 50 | // .get('/pay') 51 | // .expect(200) 52 | // .end(function (err, res) { 53 | // assert(!err); 54 | // done(); 55 | // }); 56 | // }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/weixin/callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var callback = require('../../lib/weixin/callback'); 5 | 6 | describe('weixin callback', function () { 7 | it('should init callback!', function () { 8 | callback.init({ 9 | template: '' 10 | }, 11 | { 12 | event: { 13 | create: function (message) { 14 | return { 15 | then: function (cb) { 16 | cb(message); 17 | } 18 | }; 19 | } 20 | }, 21 | message: { 22 | create: function (message) { 23 | return { 24 | then: function (cb) { 25 | cb(message); 26 | } 27 | }; 28 | } 29 | } 30 | }, 31 | { 32 | onAuthEvent: function () { 33 | 34 | }, 35 | onAuthMessage: function () { 36 | 37 | }, 38 | onOauthAccess: function () { 39 | 40 | }, 41 | onOauthSuccess: function () { 42 | 43 | } 44 | } 45 | ); 46 | }); 47 | 48 | it('should test onAuthEvent', function () { 49 | callback.auth.event({ 50 | Event: 'subscribe' 51 | }); 52 | }); 53 | it('should test onAuthMessage', function () { 54 | callback.auth.message({ 55 | MsgType: 'event' 56 | }); 57 | }); 58 | 59 | it('should test onOauthAccess', function () { 60 | callback.oauth.access(); 61 | }); 62 | it('should test onOAuthSuccess', function (done) { 63 | callback.oauth.success({}, { 64 | send: function (html) { 65 | assert(html.indexOf('尚未指定OAuth跳转URL') !== -1); 66 | done(); 67 | } 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /lib/localtunnel.js: -------------------------------------------------------------------------------- 1 | var localtunnel = require('localtunnel'); 2 | var open = require('open'); 3 | var shared = require('./shared'); 4 | 5 | var tunnel = { 6 | _status: 'stopped', 7 | _t: null, 8 | _config: null, 9 | _options: null, 10 | _cb: null, 11 | start: function (config, options, cb) { 12 | if (tunnel._status !== 'stopped') { 13 | return; 14 | } 15 | tunnel._status = 'starting'; 16 | var t = localtunnel(config.port, options, tunnel.connected(config, cb)); 17 | t.on('close', tunnel.close); 18 | tunnel._t = t; 19 | tunnel._options = options; 20 | tunnel._cb = cb; 21 | }, 22 | close: function () { 23 | // tunnels are closed 24 | console.log('Tunnels are closed'); 25 | }, 26 | connected: function (config, cb) { 27 | return function (err, t) { 28 | if (err) { 29 | console.log('Fail to create a tunnel'); 30 | } 31 | console.log('Succeeded creating a tunnel'); 32 | console.log('Your tunnel url is ' + t.url); 33 | shared.url = t.url; 34 | if (config.open) { 35 | open(t.url); 36 | } 37 | if (cb) { 38 | cb(t.url); 39 | } 40 | tunnel._status = 'started'; 41 | }; 42 | }, 43 | restart: function () { 44 | tunnel.start(tunnel._config, tunnel._options, tunnel._cb); 45 | }, 46 | error: function () { 47 | return function (err) { 48 | // handle the error safely 49 | tunnel._status = 'stopped'; 50 | if (String(err).indexOf('localtunnel.me') !== -1) { 51 | tunnel._t.close(); 52 | setTimeout(tunnel.restart, 1000); 53 | } 54 | }; 55 | } 56 | }; 57 | 58 | module.exports = function (config, options, cb) { 59 | tunnel.start(config, options, cb); 60 | process.on('uncaughtException', tunnel.error()); 61 | }; 62 | 63 | module.exports._tunnel = tunnel; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-weixin-express", 3 | "version": "0.3.5", 4 | "description": "A Node Weixin Server Implementation", 5 | "homepage": "", 6 | "author": { 7 | "name": "calidion", 8 | "email": "caldiion@gmail.com", 9 | "url": "" 10 | }, 11 | "files": [ 12 | "lib" 13 | ], 14 | "main": "lib/index.js", 15 | "keywords": [ 16 | "node", 17 | "weixin", 18 | "wechat" 19 | ], 20 | "devDependencies": { 21 | "supertest": "^1.2.0", 22 | "eslint": "^2.1.0", 23 | "eslint-config-xo-space": "^0.13.0", 24 | "gulp": "^3.9.0", 25 | "gulp-coveralls": "^0.1.0", 26 | "gulp-eslint": "^2.0.0", 27 | "gulp-exclude-gitignore": "^1.0.0", 28 | "gulp-istanbul": "^0.10.3", 29 | "gulp-line-ending-corrector": "^1.0.1", 30 | "gulp-mocha": "^2.0.0", 31 | "gulp-nsp": "^2.1.0", 32 | "gulp-plumber": "^1.0.0" 33 | }, 34 | "eslintConfig": { 35 | "extends": "xo-space", 36 | "env": { 37 | "mocha": true 38 | } 39 | }, 40 | "repository": "node-weixin/node-weixin-express", 41 | "scripts": { 42 | "prepublish": "gulp prepublish", 43 | "test": "gulp" 44 | }, 45 | "license": "Apache-2.0", 46 | "dependencies": { 47 | "async": "^2.0.0", 48 | "body-parser": "^1.15.2", 49 | "cookie-parser": "^1.4.3", 50 | "express": "^4.14.0", 51 | "express-session": "^1.14.0", 52 | "localtunnel": "^1.8.1", 53 | "meow": "^3.7.0", 54 | "node-form-validator": "^1.1.0", 55 | "node-weixin-router": "^0.5.8", 56 | "node-weixin-session": "0.0.2", 57 | "node-weixin-settings": "^0.2.1", 58 | "nunjucks": "^2.4.2", 59 | "open": "0.0.5", 60 | "sails-disk": "^0.10.10", 61 | "skipper": "^0.6.4", 62 | "waterline": "^0.12.2", 63 | "yamljs": "^0.2.8" 64 | }, 65 | "bin": { 66 | "weixin": "lib/cli.js" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var gulp = require('gulp'); 4 | var eslint = require('gulp-eslint'); 5 | var excludeGitignore = require('gulp-exclude-gitignore'); 6 | var mocha = require('gulp-mocha'); 7 | var istanbul = require('gulp-istanbul'); 8 | var nsp = require('gulp-nsp'); 9 | var plumber = require('gulp-plumber'); 10 | var coveralls = require('gulp-coveralls'); 11 | 12 | gulp.task('static', function () { 13 | return gulp.src('**/*.js') 14 | .pipe(excludeGitignore()) 15 | .pipe(eslint()) 16 | .pipe(eslint.format()) 17 | .pipe(eslint.failAfterError()); 18 | }); 19 | 20 | gulp.task('nsp', function (cb) { 21 | nsp({package: path.resolve('package.json')}, cb); 22 | }); 23 | 24 | gulp.task('pre-test', function () { 25 | return gulp.src('lib/**/*.js') 26 | .pipe(excludeGitignore()) 27 | .pipe(istanbul({ 28 | includeUntested: true 29 | })) 30 | .pipe(istanbul.hookRequire()); 31 | }); 32 | 33 | gulp.task('test', ['pre-test'], function (cb) { 34 | var mochaErr; 35 | 36 | gulp.src('test/**/*.js') 37 | .pipe(plumber()) 38 | .pipe(mocha({reporter: 'spec', timeout: 10000})) 39 | .on('error', function (err) { 40 | mochaErr = err; 41 | throw err; 42 | }) 43 | .pipe(istanbul.writeReports()) 44 | .on('end', function () { 45 | cb(mochaErr); 46 | }); 47 | }); 48 | 49 | gulp.task('watch', function () { 50 | gulp.watch(['lib/**/*.js', 'test/**'], ['test']); 51 | }); 52 | 53 | gulp.task('coveralls', ['test'], function () { 54 | if (!process.env.CI) { 55 | return; 56 | } 57 | return gulp.src(path.join(__dirname, 'coverage/lcov.info')) 58 | .pipe(coveralls()); 59 | }); 60 | 61 | // gulp.task('prepublish', ['nsp']); 62 | gulp.task('prepublish'); 63 | gulp.task('default', ['static', 'test', 'coveralls'], function () { 64 | process.exit(); 65 | }); 66 | -------------------------------------------------------------------------------- /lib/views/jssdk.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} {% block body %} 2 |
3 |
4 |

JSSDK测试页

5 | 6 |

测试扫码

7 | 8 |
9 |
10 | {% endblock %} {% block scripts %} 11 | 12 | {{ super() }} 13 | 14 | 61 | {% endblock %} -------------------------------------------------------------------------------- /lib/weixin/callback.js: -------------------------------------------------------------------------------- 1 | 2 | var htmlTemplate = require('../template'); 3 | 4 | // Message Handlers 5 | var authMessage = require('./auth/message'); 6 | var authEvent = require('./auth/event'); 7 | 8 | function parseMessage(message, type, handler, model) { 9 | var func = handler[type]; 10 | if (typeof func === 'function') { 11 | func(message, model); 12 | } else { 13 | console.error(type + ' Not Found!'); 14 | } 15 | } 16 | 17 | var callback = { 18 | _config: null, 19 | _models: null, 20 | _weixin: null, 21 | init: function (config, models, weixin) { 22 | callback._config = config; 23 | callback._models = models; 24 | weixin.onAuthEvent(callback.auth.event); 25 | weixin.onAuthMessage(callback.auth.message); 26 | weixin.onOauthAccess(callback.oauth.access); 27 | weixin.onOauthSuccess(callback.oauth.success); 28 | }, 29 | auth: { 30 | event: function (message) { 31 | console.info('on Auth Event'); 32 | var model = callback._models.event; 33 | var data = { 34 | message: message, 35 | from: message.FromUserName, 36 | to: message.ToUserName, 37 | event: message.Event, 38 | time: new Date() 39 | }; 40 | model.create(data).then(function () { 41 | parseMessage(message, message.Event.toLowerCase(), authEvent, 42 | model); 43 | }); 44 | }, 45 | message: function (message) { 46 | console.info('on Auth Message'); 47 | var model = callback._models.message; 48 | var data = { 49 | message: message, 50 | from: message.FromUserName, 51 | to: message.ToUserName, 52 | event: message.MsgType, 53 | time: new Date() 54 | }; 55 | model.create(data).then(function () { 56 | parseMessage(data, message.MsgType.toLowerCase(), authMessage, 57 | model); 58 | }); 59 | } 60 | }, 61 | oauth: { 62 | access: function (/* req */) { 63 | console.info('on OAuth access'); 64 | }, 65 | success: function (req, res, data) { 66 | console.info('on OAuth Success'); 67 | var html = htmlTemplate(callback._config.template, 'oauth.html', data); 68 | res.send(html); 69 | } 70 | } 71 | }; 72 | 73 | module.exports = callback; 74 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var server = require('./server'); 3 | 4 | var waterline = require('./waterline'); 5 | 6 | var weixinCallback = require('./weixin/callback'); 7 | 8 | var localTunnel = require('./localtunnel'); 9 | 10 | var dbConfig = require('./config/database'); 11 | 12 | var running = false; 13 | 14 | var index = { 15 | _config: null, 16 | _app: null, 17 | _weixin: null, 18 | _models: null, 19 | _info: function info(data, name) { 20 | if (data) { 21 | console.info(name + ' initialized'); 22 | } else { 23 | console.info(name + ' not initialized'); 24 | } 25 | }, 26 | info: function (config) { 27 | var dataTypes = ['port', 'host', 'server', 'template', 'message', 'app', 'merchant', 'oauth', 'cerfiticated']; 28 | for (var i = 0; i < dataTypes.length; i++) { 29 | var k = dataTypes[i]; 30 | index._info(config[k], k); 31 | } 32 | }, 33 | init: function (config, cb) { 34 | index._config = config; 35 | index._db = config.db; 36 | index.info(config); 37 | index.server(config, cb); 38 | }, 39 | server: function (config, cb) { 40 | server(index.onServerUp(cb), config); 41 | }, 42 | onServerUp: function (cb) { 43 | return function (app, weixin) { 44 | index._app = app; 45 | index._weixin = weixin; 46 | waterline.init(index._db || dbConfig, index.onOrmReady(app, weixin, cb)); 47 | }; 48 | }, 49 | onOrmReady: function (app, weixin, cb) { 50 | return function (error, ontology) { 51 | var models = ontology.collections; 52 | index._models = models; 53 | index.callback(app, weixin, models, cb); 54 | }; 55 | }, 56 | callback: function (app, weixin, models, cb) { 57 | var config = index._config; 58 | if (running) { 59 | return; 60 | } 61 | running = true; 62 | weixinCallback.init(config, models, weixin); 63 | app.listen(config.port, config.host, index.onListenEnd(cb)); 64 | }, 65 | onListenEnd: function (cb) { 66 | var config = index._config; 67 | console.info('Server listening on port', config.port); 68 | console.info('Your local address is http://' + config.host + ':' + config.port); 69 | localTunnel(config, config.localtunnel, cb); 70 | index._tunnel = localTunnel._tunnel; 71 | } 72 | }; 73 | module.exports = index; 74 | -------------------------------------------------------------------------------- /lib/validations/config.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: ["error", {properties: "never"}] */ 2 | module.exports = { 3 | port: { 4 | type: 'int', 5 | required: true 6 | }, 7 | host: { 8 | type: 'string', 9 | required: true 10 | }, 11 | template: { 12 | type: 'string' 13 | }, 14 | open: { 15 | type: 'bool' 16 | }, 17 | localtunnel: { 18 | type: 'object', 19 | validate: { 20 | subdomain: { 21 | type: 'string' 22 | }, 23 | local_host: { 24 | type: 'string' 25 | } 26 | } 27 | }, 28 | weixin: { 29 | type: 'object', 30 | required: true, 31 | validate: { 32 | server: { 33 | type: 'object', 34 | required: true, 35 | validate: { 36 | prefix: { 37 | type: 'string' 38 | }, 39 | host: { 40 | type: 'string', 41 | required: true 42 | } 43 | } 44 | }, 45 | app: { 46 | type: 'object', 47 | required: true, 48 | validate: { 49 | id: { 50 | type: 'string', 51 | maxLength: 64, 52 | required: true 53 | }, 54 | secret: { 55 | type: 'string', 56 | maxLength: 64, 57 | required: true 58 | }, 59 | token: { 60 | type: 'string', 61 | maxLength: 64, 62 | required: true 63 | } 64 | } 65 | }, 66 | merchant: { 67 | type: 'object', 68 | validate: { 69 | id: { 70 | type: 'string', 71 | maxLength: 64, 72 | required: true 73 | }, 74 | key: { 75 | type: 'string', 76 | maxLength: 64, 77 | required: true 78 | } 79 | } 80 | }, 81 | certificate: { 82 | type: 'object', 83 | validate: { 84 | pfxKey: { 85 | type: 'string', 86 | maxLength: 64, 87 | required: true 88 | }, 89 | pfx: { 90 | type: 'text', 91 | maxLength: 64, 92 | required: true 93 | } 94 | } 95 | }, 96 | message: { 97 | type: 'object', 98 | validate: { 99 | aes: { 100 | type: 'string', 101 | maxLength: 64, 102 | required: true 103 | } 104 | } 105 | }, 106 | oauth: { 107 | type: 'object', 108 | validate: { 109 | state: { 110 | type: 'string', 111 | maxLength: 64, 112 | required: true 113 | }, 114 | scope: { 115 | type: 'int', 116 | required: true 117 | } 118 | } 119 | } 120 | } 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-weixin-express [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url][![Beerpay](https://beerpay.io/node-weixin/node-weixin-express/badge.svg?style=flat-square)](https://beerpay.io/node-weixin/node-weixin-express) 2 | 3 | 4 | ## 功能说明 5 | node-weixin-express是一个基于nodejs为基础,以expressjs作为首选http服务器框架的微信公共账号服务器。 6 | 他旨在降低开发微信公共账号时的门槛,节约开发时间。 7 | 8 | 9 | ## 主要功能与计划 10 | 11 | 1. 可以直接通过一个命令运行微信公共账号服务 12 | 2. 基本的微信功能: 13 | - 验证服务器 14 | - OAuth 验证API 15 | // - 微信支付API 16 | - 消息接口API 17 | 3. 可以任意基于express的框架沟通协作(基本完成) 18 | 4. 模块化机制采用Unix开发哲学:KISS 19 | 5. 建立一个可以方便安装数据库,并且将配置信息存放在数据库里的机制(计划中) 20 | 6. 通过express可以无需任何开发就可以自己建设一个功能全面的微信服务器(计划中) 21 | 22 | 23 | ## 重要子模块 24 | 1. [node-weixin-api](https://github.com/node-weixin/node-weixin-api): 25 | 提供所有基础的微信api 26 | 2. [node-weixin-router](https://github.com/node-weixin/node-weixin-router): 27 | 提供所有的基于web框架的默认路由与回调机制 28 | 3. [node-weixin-session](https://github.com/node-weixin/node-weixin-session): 29 | 提供所有基于用户登录的session数据的存储机制,通过修改get/set/all来实现自定义化 30 | 4. [node-weixin-settings](https://github.com/node-weixin/node-weixin-settings): 31 | 提供所有基于微信的app.id的数据存储机制,通过修改get/set/all来实现自定义化 32 | 33 | 34 | ## 问题、反馈与帮助 35 | 36 | - 论坛交流 37 | [node-weixin交流论坛](http://forum.node-weixin.com/) 38 | 39 | - 官方网站 40 | [node-weixin](http://www.node-weixin.com/) 用于快速检索更新, 帮助,导航等 41 | 42 | - 官方QQ群 43 | 39287176 44 | 45 | - 关注公共账号了解最新动态   46 | ![](http://res.cloudinary.com/dawjytvkn/image/upload/v1464858605/qrcode_for_gh_6f66da401fef_430_b1rr96.jpg) 47 | 48 | 49 | 50 | ## node-weixin 51 | 52 | [node-weixin-express](https://github.com/node-weixin/node-weixin-express)是基于node-weixin-*的服务器端参考实现。 53 | 54 | [node-weixin-api](https://github.com/node-weixin/node-weixin-api)是基于node-weixin-*的API接口SDK。 55 | 56 | 它们都是由下列子项目组合而成: 57 | 58 | 1. [node-weixin-config](https://github.com/node-weixin/node-weixin-config) 59 | 用于微信配置信息的校验 60 | 61 | 2. [node-weixin-auth](https://github.com/node-weixin/node-weixin-auth) 62 | 用于与微信服务器握手检验 63 | 64 | 3. [node-weixin-util](https://github.com/node-weixin/node-weixin-util) 65 | 一些常用的微信请求,加密,解密,检验的功能与处理 66 | 67 | 4. [node-weixin-request](https://github.com/node-weixin/node-weixin-request) 68 | 微信的各类服务的HTTP请求的抽象集合 69 | 70 | 5. [node-weixin-oauth](https://github.com/node-weixin/node-weixin-oauth) 71 | 微信OAuth相关的操作 72 | 73 | 6. [node-weixin-pay](https://github.com/node-weixin/node-weixin-pay) 74 | 微信支付的服务器接口 75 | 76 | 7. [node-weixin-jssdk](https://github.com/node-weixin/node-weixin-jssdk) 77 | 微信JSSDK相关的服务器接口 78 | 79 | 8. [node-weixin-menu](https://github.com/node-weixin/node-weixin-menu) 80 | 微信菜单相关的操作与命令 81 | 82 | 9. [node-weixin-user](https://github.com/node-weixin/node-weixin-user) 83 | 微信用户API 84 | 85 | 10. [node-weixin-media](https://github.com/node-weixin/node-weixin-media) 86 | 微信多媒体API 87 | 88 | 11. [node-weixin-link](https://github.com/node-weixin/node-weixin-link) 89 | 微信推广(二维码,URL)API 90 | 91 | 12. [node-weixin-message](https://github.com/node-weixin/node-weixin-message) 92 | 微信消息API 93 | 94 | ## 安装 95 | 96 | ```sh 97 | $ npm install --g node-weixin-express 98 | ``` 99 | 100 | ### 说明 101 | 102 | 安装后在命令行会多出一个命令: 103 | 104 | ```sh 105 | weixin 106 | ``` 107 | 108 | > 注意:这里的命令名是weixin,不是node-weixin-express 109 | 110 | ## 查看命令 111 | 112 | ```sh 113 | $ weixin --help 114 | ``` 115 | 116 | ## 运行 117 | 118 | ``` 119 | $ weixin [--yaml] a.yaml 120 | ``` 121 | 后面接一个描述性的yaml文件 122 | 123 | > 不需要再写代码,可以直接通过命令执行。 124 | 125 | ## yaml文件格式 126 | 127 | ```yaml 128 | ### ----必填项---- ### 129 | port: 2048 # 服务器端口号 130 | host: localhost # 本地的IP或者主机地址 131 | template: '' # 可以替换的模板的位置,放入自己的模板,格式是nunjunck 132 | weixin: 133 | # 微信服务器配置 134 | server: 135 | host: localhost # 远程的服务器名, 需要与JSSDK的授权域名一致 136 | prefix: '/api' # 格式是'/xxx',必须带'/' 137 | # 微信公共号的基本配置信息 138 | app: 139 | id: 'xxx' # 必须换成自己的 140 | secret: 'xxx' # 必须换成自己的 141 | token: 'xxx' 142 | # Oauth 相关 143 | oauth: 144 | state: 'state' 145 | scope: '0' 146 | ### ----结束---- ### 147 | 148 | # 加密消息 149 | message: 150 | aes: 'sdofsfd' 151 | 152 | # 支付相关,暂时不开放 153 | # merchant: 154 | # id: '133' 155 | # key: 'sdfsf' 156 | # certificate: 157 | # pfxKey: 'sdfosofdf' 158 | # pfx: 'sodfofosdf' 159 | # path: '' 160 | ``` 161 | 162 | 163 | ## URLs 164 | 165 | 服务器校验地址 166 | ``` 167 | 'http://' + 域名 + '/' + 前缀 + '/auth/ack 168 | ``` 169 | JSSDK配置请求地址 170 | ``` 171 | 'http://' + 域名 + '/' + 前缀 + '/jssdk/config 172 | ``` 173 | Oauth访问地址 174 | ``` 175 | 'http://' + 域名 + '/' + 前缀 + '/oauth/access 176 | ``` 177 | 支付回调地址 178 | ``` 179 | 'http://' + 域名 + '/' + 前缀 + '/pay/callback 180 | ``` 181 | 182 | ## 模板说明 183 | 184 | 目前支持的模板是nunjucks:https://mozilla.github.io/nunjucks/ 185 | 可能是目前javascript下最完善的模板。 186 | 暂时不支持其它的模板。 187 | 188 | 189 | ## 特色 190 | 191 | 1. 通过一个命令就可以对接好公共帐号的主要功能 192 | 2. 可以自定义模板,方便前端测试开发 193 | 3. 配合ngrok, localtunnel等软件将会更加方便 194 | 195 | ## License 196 | 197 | Apache-2.0 © [node-weixin](www.node-weixin.com) 198 | 199 | [npm-image]: https://badge.fury.io/js/node-weixin-express.svg 200 | [npm-url]: https://npmjs.org/package/node-weixin-express 201 | [travis-image]: https://travis-ci.org/node-weixin/node-weixin-express.svg?branch=master 202 | [travis-url]: https://travis-ci.org/node-weixin/node-weixin-express 203 | [daviddm-image]: https://david-dm.org/node-weixin/node-weixin-express.svg?theme=shields.io 204 | [daviddm-url]: https://david-dm.org/node-weixin/node-weixin-express 205 | [coveralls-image]: https://coveralls.io/repos/node-weixin/node-weixin-express/badge.svg 206 | [coveralls-url]: https://coveralls.io/r/node-weixin/node-weixin-express 207 | --------------------------------------------------------------------------------