├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── business └── chat-tunnel-handler.js ├── config.js ├── globals.js ├── package.json ├── process.json ├── routes ├── index.js ├── login.js ├── tunnel.js ├── user.js └── welcome.js └── setup-qcloud-sdk.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | sftp-config.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE - "MIT License" 2 | 3 | Copyright (c) 2016 by Tencent Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wafer 服务端 Demo - Node.js 2 | 3 | 本项目是 [腾讯云微信小程序服务端 SDK - Node.js](https://github.com/tencentyun/wafer-node-server-sdk) 的使用示例。示例需要和 [微信小程序客户端示例](https://github.com/tencentyun/wafer-client-demo) 配合一起使用。 4 | 5 | ## 运行示例 6 | 7 | 按照[小程序创建资源配置指引](https://github.com/tencentyun/weapp-doc)进行操作,可以得到运行本示例所需的资源和服务,其中包括已部署好的示例代码及自动下发的 SDK 配置文件 `/etc/qcloud/sdk.config`。 8 | 9 | - 示例代码部署目录:`/data/release/node-weapp-demo` 10 | - 运行示例的 Node 版本:`v4.6.0` 11 | - Node 进程管理工具:`pm2` 12 | 13 | ## 项目结构 14 | 15 | ``` 16 | Demo 17 | ├── README.md 18 | ├── app.js 19 | ├── business 20 | │ └── chat-tunnel-handler.js 21 | ├── config.js 22 | ├── globals.js 23 | ├── package.json 24 | ├── process.json 25 | ├── routes 26 | │ ├── index.js 27 | │ ├── welcome.js 28 | │ ├── login.js 29 | │ ├── user.js 30 | │ └── tunnel.js 31 | └── setup-qcloud-sdk.js 32 | ``` 33 | 34 | 其中,`app.js` 是 启动文件,`config.js` 配置了启动服务监听的端口号,`process.json` 是运行本示例 的 `pm2` 配置文件。 35 | 36 | `setup-qcloud-sdk.js` 用于初始化 SDK 配置,配置从文件 `/etc/qcloud/sdk.config` 中读取。 配置文件包含如下配置项: 37 | 38 | ```json 39 | { 40 | "serverHost": "业务服务器的主机名", 41 | "authServerUrl": "鉴权服务器地址", 42 | "tunnelServerUrl": "信道服务器地址", 43 | "tunnelSignatureKey": "和信道服务器通信的签名密钥" 44 | } 45 | ``` 46 | 47 | `routes/` 目录包含了示例用到的4个路由,路由和处理文件映射关系如下: 48 | 49 | ``` 50 | // 首页指引 51 | / => routes/welcome.js 52 | 53 | // 登录 54 | /login => routes/login.js 55 | 56 | // 获取微信用户信息 57 | /user => routes/user.js 58 | 59 | // 处理信道请求 60 | /tunnel => routes/tunnel.js 61 | ``` 62 | 63 | `business/chat-tunnel-handler.js` 是业务处理信道请求的示例代码。 64 | 65 | ## 如何在demo基础上进行开发 66 | 进入目录 `/data/release/node-weapp-demo`,将写好的代码上传到routes目录下 67 | 68 | 重启服务: pm2 restart all 69 | 70 | 71 | ## 更新 SDK 版本 72 | 73 | 进入目录 `/data/release/node-weapp-demo`,然后先后执行命令 `npm update`、`pm2 restart process.json` 即可。 74 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./globals'); 4 | require('./setup-qcloud-sdk'); 5 | 6 | const http = require('http'); 7 | const express = require('express'); 8 | const bodyParser = require('body-parser'); 9 | const morgan = require('morgan'); 10 | const config = require('./config'); 11 | 12 | const app = express(); 13 | 14 | app.set('query parser', 'simple'); 15 | app.set('case sensitive routing', true); 16 | app.set('jsonp callback name', 'callback'); 17 | app.set('strict routing', true); 18 | app.set('trust proxy', true); 19 | 20 | app.disable('x-powered-by'); 21 | 22 | // 记录请求日志 23 | app.use(morgan('tiny')); 24 | 25 | // parse `application/x-www-form-urlencoded` 26 | app.use(bodyParser.urlencoded({ extended: true })); 27 | 28 | // parse `application/json` 29 | app.use(bodyParser.json()); 30 | 31 | app.use('/', require('./routes')); 32 | 33 | // 打印异常日志 34 | process.on('uncaughtException', error => { 35 | console.log(error); 36 | }); 37 | 38 | // 启动server 39 | http.createServer(app).listen(config.port, () => { 40 | console.log('Express server listening on port: %s', config.port); 41 | }); 42 | -------------------------------------------------------------------------------- /business/chat-tunnel-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TunnelService = require('qcloud-weapp-server-sdk').TunnelService; 4 | 5 | /** 6 | * 调用 TunnelService.broadcast() 进行广播 7 | * @param {String} type 消息类型 8 | * @param {String} content 消息内容 9 | */ 10 | const $broadcast = (type, content) => { 11 | TunnelService.broadcast(connectedTunnelIds, type, content) 12 | .then(result => { 13 | let invalidTunnelIds = result.data && result.data.invalidTunnelIds || []; 14 | 15 | if (invalidTunnelIds.length) { 16 | debug('检测到无效的信道 IDs =>', invalidTunnelIds); 17 | 18 | // 从`userMap`和`connectedTunnelIds`中将无效的信道记录移除 19 | invalidTunnelIds.forEach(tunnelId => { 20 | delete userMap[tunnelId]; 21 | 22 | let index = connectedTunnelIds.indexOf(tunnelId); 23 | if (~index) { 24 | connectedTunnelIds.splice(index, 1); 25 | } 26 | }); 27 | } 28 | }); 29 | }; 30 | 31 | /** 32 | * 调用 TunnelService.closeTunnel() 关闭信道 33 | * @param {String} tunnelId 信道ID 34 | */ 35 | const $close = (tunnelId) => { 36 | TunnelService.closeTunnel(tunnelId); 37 | }; 38 | 39 | // 保存 WebSocket 信道对应的用户 40 | // 在实际的业务中,应该使用数据库进行存储跟踪,这里作为示例只是演示其作用 41 | let userMap = {}; 42 | 43 | // 保存 当前已连接的 WebSocket 信道ID列表 44 | let connectedTunnelIds = []; 45 | 46 | /** 47 | * 实现 WebSocket 信道处理器 48 | * 本示例配合客户端 Demo 实现一个简单的聊天室功能 49 | */ 50 | class ChatTunnelHandler { 51 | /** 52 | * 实现 onRequest 方法 53 | * 在客户端请求 WebSocket 信道连接之后, 54 | * 会调用 onRequest 方法,此时可以把信道 ID 和用户信息关联起来 55 | */ 56 | onRequest(tunnelId, userInfo) { 57 | debug(`${this.constructor.name} [onRequest] =>`, { tunnelId, userInfo }); 58 | 59 | if (typeof userInfo === 'object') { 60 | // 保存 信道ID => 用户信息 的映射 61 | userMap[tunnelId] = userInfo; 62 | } 63 | } 64 | 65 | /** 66 | * 实现 onConnect 方法 67 | * 在客户端成功连接 WebSocket 信道服务之后会调用该方法, 68 | * 此时通知所有其它在线的用户当前总人数以及刚加入的用户是谁 69 | */ 70 | onConnect(tunnelId) { 71 | debug(`${this.constructor.name} [onConnect] =>`, { tunnelId }); 72 | 73 | if (tunnelId in userMap) { 74 | connectedTunnelIds.push(tunnelId); 75 | 76 | $broadcast('people', { 77 | 'total': connectedTunnelIds.length, 78 | 'enter': userMap[tunnelId], 79 | }); 80 | } else { 81 | debug(`Unknown tunnelId(${tunnelId}) was connectd, close it`); 82 | $close(tunnelId); 83 | } 84 | } 85 | 86 | /** 87 | * 实现 onMessage 方法 88 | * 客户端推送消息到 WebSocket 信道服务器上后,会调用该方法,此时可以处理信道的消息。 89 | * 在本示例,我们处理 `speak` 类型的消息,该消息表示有用户发言。 90 | * 我们把这个发言的信息广播到所有在线的 WebSocket 信道上 91 | */ 92 | onMessage(tunnelId, type, content) { 93 | debug(`${this.constructor.name} [onMessage] =>`, { tunnelId, type, content }); 94 | 95 | switch (type) { 96 | case 'speak': 97 | if (tunnelId in userMap) { 98 | $broadcast('speak', { 99 | 'who': userMap[tunnelId], 100 | 'word': content.word, 101 | }); 102 | } else { 103 | $close(tunnelId); 104 | } 105 | break; 106 | 107 | default: 108 | // ... 109 | break; 110 | } 111 | } 112 | 113 | /** 114 | * 实现 onClose 方法 115 | * 客户端关闭 WebSocket 信道或者被信道服务器判断为已断开后, 116 | * 会调用该方法,此时可以进行清理及通知操作 117 | */ 118 | onClose(tunnelId) { 119 | debug(`${this.constructor.name} [onClose] =>`, { tunnelId }); 120 | 121 | if (!(tunnelId in userMap)) { 122 | debug(`${this.constructor.name} [onClose][Invalid TunnelId]=>`, tunnelId); 123 | $close(tunnelId); 124 | return; 125 | } 126 | 127 | const leaveUser = userMap[tunnelId]; 128 | delete userMap[tunnelId]; 129 | 130 | const index = connectedTunnelIds.indexOf(tunnelId); 131 | if (~index) { 132 | connectedTunnelIds.splice(index, 1); 133 | } 134 | 135 | // 聊天室没有人了(即无信道ID)不再需要广播消息 136 | if (connectedTunnelIds.length > 0) { 137 | $broadcast('people', { 138 | 'total': connectedTunnelIds.length, 139 | 'leave': leaveUser, 140 | }); 141 | } 142 | } 143 | } 144 | 145 | module.exports = ChatTunnelHandler; -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * Node 服务器启动端口,如果是自行搭建,请保证负载均衡上的代理地址指向这个端口 6 | */ 7 | port: '5757', 8 | }; 9 | -------------------------------------------------------------------------------- /globals.js: -------------------------------------------------------------------------------- 1 | global.debug = (() => { 2 | const log = console.log.bind(console); 3 | 4 | return function () { 5 | log('========================================'); 6 | log.apply(null, Array.from(arguments).map(item => { 7 | return (typeof item === 'object' 8 | ? JSON.stringify(item, null, 2) 9 | : item); 10 | })); 11 | log('========================================\n'); 12 | }; 13 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wafer-node-server-demo", 3 | "version": "1.0.0", 4 | "description": "Wafer 服务端 Demo - Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "pm2 start process.json" 8 | }, 9 | "keywords": [], 10 | "author": "CFETeam", 11 | "license": "MIT", 12 | "dependencies": { 13 | "body-parser": "^1.15.2", 14 | "express": "^4.14.0", 15 | "lodash": "^4.16.1", 16 | "morgan": "^1.7.0", 17 | "qcloud-weapp-server-sdk": "latest" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /process.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weapp", 3 | "script": "app.js", 4 | "cwd": "./", 5 | "exec_mode": "fork", 6 | "watch": true, 7 | "env": { 8 | "NODE_ENV": "production", 9 | "DEBUG_SDK": "yes" 10 | } 11 | } -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const router = express.Router(); 5 | 6 | router.get('/', require('./welcome')); 7 | router.get('/login', require('./login')); 8 | router.get('/user', require('./user')); 9 | router.all('/tunnel', require('./tunnel')); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /routes/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const LoginService = require('qcloud-weapp-server-sdk').LoginService; 4 | 5 | module.exports = (req, res) => { 6 | LoginService.create(req, res).login(); 7 | }; -------------------------------------------------------------------------------- /routes/tunnel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TunnelService = require('qcloud-weapp-server-sdk').TunnelService; 4 | const ChatTunnelHandler = require('../business/chat-tunnel-handler'); 5 | 6 | module.exports = (req, res) => { 7 | let handler = new ChatTunnelHandler(); 8 | TunnelService.create(req, res).handle(handler, { 9 | 'checkLogin': true, 10 | }); 11 | }; -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const LoginService = require('qcloud-weapp-server-sdk').LoginService; 4 | 5 | module.exports = (req, res) => { 6 | const loginService = LoginService.create(req, res); 7 | 8 | loginService.check() 9 | .then(data => { 10 | res.json({ 11 | 'code': 0, 12 | 'message': 'ok', 13 | 'data': { 14 | 'userInfo': data.userInfo, 15 | }, 16 | }); 17 | }); 18 | }; -------------------------------------------------------------------------------- /routes/welcome.js: -------------------------------------------------------------------------------- 1 | const html = 2 | ` 3 | 4 |
5 | 6 |