├── .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 |
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][](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 | 
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 |
--------------------------------------------------------------------------------