├── .gitignore ├── Makefile ├── README.md ├── app.js ├── bin └── www ├── config.js ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── index.js ├── users.js └── wechat.js └── views ├── error.ejs └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | example 3 | .DS_Store 4 | coverage 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run : 2 | @PORT=80 ./node_modules/.bin/nodemon ./bin/www 3 | 4 | .PHONY: run -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nodejs微信公众平台服务器接入指南 2 | --- 3 | 4 | 5 | > 最近申请了一个阿里云服务器,尝试着在上面用Nodejs搞个微信公众号机器人玩玩,申请了个人公众账号【技术栈】,你可以加关注然后尝试机器人的效果,虽然它现在还只会回“你来我家接我吧”,而且你看到的时候也不知道这服务器还在不在=。=. 6 | 7 | 8 | 9 | 10 | # 搭建环境 11 | 首先在阿里云服务器上搭建Nodejs开发环境,参考[在阿里云上搭建Nodejs开发环境](http://mclspace.com/2015/12/09/aliyun-build-nodejs-environment/) 12 | 13 | # 接入过程 14 | 首先如果你没有[微信公众平台](https://mp.weixin.qq.com)账号,先去注册一个。 15 | 16 | 然后进入开发 -> 基本配置,这里会显示你的应用ID和应用密钥,记下这里你的AppID。 17 | 18 | 在服务器配置一栏中有3个参数: 19 | ``` 20 | URL是你的服务器的响应微信请求的地址 21 | Token是你和微信通信的凭证,可以任意设置(3-32字符),例如设置为123456 22 | EncodingAESKey 用于消息加密的密钥,我们点击后面的随机生成然后记下来就好。 23 | ``` 24 | 25 | Ok,现在我们有3个参数:AppID,Token和EncodingAESKey,然后我们在本地新建Nodejs服务器。 26 | 27 | 我们使用Express-generator快捷地生成一个可用的服务器,nodemon用来启动服务器: 28 | ``` 29 | $ express -e weixin_server 30 | $ cd weixin_server && npm install 31 | $ npm install --save nodemon 32 | ``` 33 | 在weixin_server中新建文件Makefile和config.js,分别用来自动化构建服务器以及配置服务器: 34 | ``` 35 | $ touch Makefile config.js 36 | ``` 37 | 38 | 建好的工程的目录如下: 39 | ``` 40 | . 41 | ├── app.js 42 | ├── bin 43 | ├── config.js 44 | ├── makefile 45 | ├── node_modules 46 | ├── package.json 47 | ├── public 48 | ├── routes 49 | └── views 50 | ``` 51 | 52 | 在config.js中写入刚刚我们获得的3个参数: 53 | ``` 54 | var config = { 55 | token: 'xxxxxxx', 56 | appid: 'xxxxxxxxxxx', 57 | encodingAESKey: 'xxxxxxxxxxxxxxxxxxxxxxxx' 58 | }; 59 | module.exports = config; 60 | ``` 61 | 62 | 在Makefile中写入运行命令,这里我们设置运行端口为80,目前微信只支持80: 63 | ``` 64 | run : 65 | @PORT=80 ./node_modules/.bin/nodemon ./bin/www 66 | .PHONY: run 67 | ``` 68 | 69 | 在routes文件夹中新建我们用来处理微信消息的路由wechat.js 70 | ``` 71 | $ cd routes && touch wechat.js 72 | ``` 73 | 74 | 在app.js中的routes和users变量下添加我们处理微信消息的路由wechat,并配置使用该路由 75 | ``` 76 | var routes = require('./routes/index'); 77 | var users = require('./routes/users'); 78 | var wechat = require('./routes/wechat'); 79 | 80 | ... 81 | 82 | app.use('/', routes); 83 | app.use('/users', users); 84 | app.use('/wechat',wechat); 85 | ``` 86 | 87 | 下面我们安装wechat模块,这是卜灵大叔写的用于和微信服务器进行通信的Nodejs模块 88 | ``` 89 | $ npm install --save wechat 90 | ``` 91 | 92 | 然后在我们的wechat.js中填入以下代码: 93 | ``` 94 | var express = require('express'); 95 | var router = express.Router(); 96 | 97 | var wechat = require('wechat'); 98 | var config = require('../config.js'); 99 | 100 | router.use('/', wechat(config, function (req, res, next) { 101 | // 微信输入信息都在req.weixin上 102 | var message = req.weixin; 103 | if (message.FromUserName === 'diaosi') { 104 | // 回复屌丝(普通回复) 105 | res.reply('hehe'); 106 | } else if (message.FromUserName === 'text') { 107 | //你也可以这样回复text类型的信息 108 | res.reply({ 109 | content: 'text object', 110 | type: 'text' 111 | }); 112 | } else if (message.FromUserName === 'hehe') { 113 | // 回复一段音乐 114 | res.reply({ 115 | type: "music", 116 | content: { 117 | title: "来段音乐吧", 118 | description: "一无所有", 119 | musicUrl: "http://mp3.com/xx.mp3", 120 | hqMusicUrl: "http://mp3.com/xx.mp3", 121 | thumbMediaId: "thisThumbMediaId" 122 | } 123 | }); 124 | } else { 125 | // 回复高富帅(图文回复) 126 | res.reply([ 127 | { 128 | title: '你来我家接我吧', 129 | description: '这是女神与高富帅之间的对话', 130 | picurl: 'http://nodeapi.cloudfoundry.com/qrcode.jpg', 131 | url: 'http://nodeapi.cloudfoundry.com/' 132 | } 133 | ]); 134 | } 135 | })); 136 | 137 | module.exports = router; 138 | ``` 139 | 这是wechat模块给的示例代码,我们暂时先用着,下面我们部署服务器,首先将该服务器上传到github或者任何能够从远程部署代码的代码托管服务商上。以github为例新增.gitignore防止将node_modules和.DS_Store上传到服务器: 140 | ``` 141 | node_modules 142 | example 143 | .DS_Store 144 | coverage 145 | ``` 146 | 然后在github新建工程上传,不赘述。 147 | 148 | 在阿里云上clone我们刚刚上传的工程, 然后用make命令运行 149 | ``` 150 | $ git clone git@github.com:rdmclin2/weixin_server.git 151 | $ cd wexin_server && npm install 152 | $ make run 153 | ``` 154 | 155 | ok,我们继续到微信公众平台 -> 开发 -> 基本配置,填入 156 | ``` 157 | URL : /wechat 158 | Token : 159 | EncodingAESKey: 160 | 消息加解密方式 : 安全模式 161 | ``` 162 | 点击提交,完成接入。 -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | var wechat = require('./routes/wechat'); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | app.use('/users', users); 28 | app.use('/wechat',wechat); 29 | 30 | // catch 404 and forward to error handler 31 | app.use(function(req, res, next) { 32 | var err = new Error('Not Found'); 33 | err.status = 404; 34 | next(err); 35 | }); 36 | 37 | // error handlers 38 | 39 | // development error handler 40 | // will print stacktrace 41 | if (app.get('env') === 'development') { 42 | app.use(function(err, req, res, next) { 43 | res.status(err.status || 500); 44 | res.render('error', { 45 | message: err.message, 46 | error: err 47 | }); 48 | }); 49 | } 50 | 51 | // production error handler 52 | // no stacktraces leaked to user 53 | app.use(function(err, req, res, next) { 54 | res.status(err.status || 500); 55 | res.render('error', { 56 | message: err.message, 57 | error: {} 58 | }); 59 | }); 60 | 61 | 62 | module.exports = app; 63 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('weixin_server:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | token: 'techstack123456', 3 | appid: 'wxd7ee10f6b3996719', 4 | encodingAESKey: 'SbLVvAzc3uS4oW1ZipsARMTdlPgAYOInHDf25FK15SR' 5 | }; 6 | 7 | module.exports = config; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weixin_server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "ejs": "~2.3.3", 13 | "express": "~4.13.1", 14 | "morgan": "~1.6.1", 15 | "nodemon": "^1.8.1", 16 | "serve-favicon": "~2.3.0", 17 | "wechat": "^2.0.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/wechat.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | var wechat = require('wechat'); 5 | var config = require('../config.js'); 6 | // console.log(config); 7 | 8 | router.use('/', wechat(config, function (req, res, next) { 9 | // 微信输入信息都在req.weixin上 10 | var message = req.weixin; 11 | if (message.FromUserName === 'diaosi') { 12 | // 回复屌丝(普通回复) 13 | res.reply('hehe'); 14 | } else if (message.FromUserName === 'text') { 15 | //你也可以这样回复text类型的信息 16 | res.reply({ 17 | content: 'text object', 18 | type: 'text' 19 | }); 20 | } else if (message.FromUserName === 'hehe') { 21 | // 回复一段音乐 22 | res.reply({ 23 | type: "music", 24 | content: { 25 | title: "来段音乐吧", 26 | description: "一无所有", 27 | musicUrl: "http://mp3.com/xx.mp3", 28 | hqMusicUrl: "http://mp3.com/xx.mp3", 29 | thumbMediaId: "thisThumbMediaId" 30 | } 31 | }); 32 | } else { 33 | // 回复高富帅(图文回复) 34 | res.reply([ 35 | { 36 | title: '你来我家接我吧', 37 | description: '这是女神与高富帅之间的对话', 38 | picurl: 'http://nodeapi.cloudfoundry.com/qrcode.jpg', 39 | url: 'http://nodeapi.cloudfoundry.com/' 40 | } 41 | ]); 42 | } 43 | })); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | 12 | --------------------------------------------------------------------------------