原文:Real-Time Twilio and Flybase
协议:CC BY-NC-SA 4.0
六、发送每日短信提醒
在这一章中,我将向你展示如何使用 Node.js、Flybase 和 Twilio 来编写你自己的每日短信提醒应用。
必要的工具
-
Twilio 发送和接收短信
-
存储订阅我们服务的用户
-
Node.js 构建在 Chrome 的 JavaScript 运行时之上,轻松构建快速、可扩展的网络应用
使用 Cron 安排 SMS 消息
首先,我们需要安装几个 npm 包。我们将使用 twilio 包( https://github.com/twilio/twilio-node
)来发送文本消息,并且我们将使用 cron 包( https://github.com/ncb000gt/node-cron
)来安排我们想要发送文本消息的时间。您可以通过运行以下命令来安装它们:
npm install twilio
npm install cron
- 1
- 2
- 3
创建一个名为 app.js 的新文件,并需要 twilio 和 cron 包:
var twilio = require('twilio'),
client = twilio('ACCOUNTSID', 'AUTHTOKEN'),
cronJob = require('cron').CronJob;
- 1
- 2
- 3
- 4
让我们写一些每天下午 6 点发送短信的代码:
var textJob = new cronJob( '0 18 * * *', function(){
client.sendMessage( { to:'YOURPHONENUMBER', from:'YOURTWILIONUMBER', body:'Hello! Hope you're having a good day!' }, function( err, data ) {});
}, null, true);
- 1
- 2
- 3
- 4
您可能想知道我们作为 cronJob 的第一个参数传递的字符串是什么。这是一种特定于 Cron 的格式,允许我们定义希望该作业启动的时间和频率。
在这种情况下,每天 18 小时的 0 分钟。这篇文章( www.nncron.ru/help/EN/working/cron-format.htm
)很好地打破了 Cron 格式。
在对 cronJob 的回调中,我们使用 Twilio 客户端库发送消息。我们传递收件人和发件人号码以及我们想要发送的消息正文。
运行这段代码,等待你的短信。如果是上午 10 点,你可能不想等 8 个小时来看看你的代码是否工作。只需更新 Cron 格式,在更早的时间发送即可。给你个提示。要在上午 10:13 发送,您可以使用以下格式:“13 10 * * *”。
你现在有了这个应用的基本版本,但你很可能不想每天只给自己发一条消息。如果你有,那么恭喜你!你们都完了!对于我们其余的人,我们可以做一些小的代码修改,让这个发送到多个电话号码。
首先,让我们添加一个名为 numbers 的新变量,它包含我们要向其发送消息的电话号码:
var numbers = ['YOURPHONENUMBER', 'YOURFRIENDSPHONENUMBER'];
- 1
- 2
然后,让我们更新 textJob 中的代码,循环遍历这些电话号码并向它们发送消息:
for( var i = 0; i < numbers.length; i++ ) {
client.sendMessage( { to:numbers[i], from:'YOURTWILIONUMBER', body:'Hello! Hope you’re having a good day.'}, function( err, data ) {
console.log( data.body );
});
}
- 1
- 2
- 3
- 4
- 5
- 6
接收短信
现在,我们在需要的时间向不同的号码发送短信,让我们更新代码,以了解用户何时向我们的应用发送短信。Twilio 使用 webhooks ( https://en.wikipedia.org/wiki/Webhook
)来让您的服务器知道何时有消息或电话进入我们的应用。我们需要设置一个端点,我们可以告诉 Twilio 将它用于消息传递 webhook。
我们将使用 express 框架( http://expressjs.com/
)来设置我们的 Node web 服务器,以接收来自 Twilio 的 POST 请求,因此我们需要安装 Express 包。我们还将使用 body-parser 模块,所以我们也要安装它:
npm install express
npm install body-parser
- 1
- 2
- 3
在 app.js 文件的开头,我们需要 require express 并将其初始化为一个名为 app 的变量。我们还将使用 bodyParser 中间件( https://github.com/expressjs/body-parser
)来方便地使用我们将在 POST 请求中获得的数据:
var express = require('express'),
bodyParser = require('body-parser'),
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
我们将为/message 添加一个路由,用一些 TwiML ( www.twilio.com/docs/api/twiml
)进行响应。TwiML 是一组基本指令,当你收到来电或短信时,你可以用它来告诉 Twilio 该做什么。我们的代码将如下所示:
app.post('/message', function (req, res) {
var resp = new twilio.TwimlResponse();
resp.message('Thanks for subscribing!');
res.writeHead(200, {
'Content-Type':'text/xml'
});
res.end(resp.toString());
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我们使用 Twilio Node 库来初始化一个新的 TwimlResponse。然后我们使用消息动词( www.twilio.com/docs/api/twiml/sms/message
)来设置我们想要用什么来响应消息。在这种情况下,我们只会说“感谢订阅!”然后,我们将响应的内容类型设置为text/xml
,并发送我们构建的 TwimlResponse 的字符串表示。
最后,让我们将服务器设置为侦听端口 3000:
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
- 1
- 2
- 3
- 4
现在让我们启动我们的应用:
node app.js
- 1
- 2
现在我们已经运行了服务器,我们需要告诉 Twilio 使用这个消息 URL 作为我们的消息请求 URL:

- 1
- 2
向您的 Twilio 号码发送短信,您应该会收到回复。如果你不知道,看看 Twilio 应用监视器( www.twilio.com/user/account/developer-tools/app-monitor
)来帮助确定哪里出了问题。
在 Flybase 中保存用户
我们设置了一个脚本,每天在同一时间发送短信,我们让用户能够在我们的应用中发送短信。只剩下最后一件事要做了。当用户向我们的应用发送文本时,我们需要保存他们的信息。我们将使用 Flybase ( www.flybase.io/
)作为我们的数据存储,因此我们需要安装 Flybase Node 模块:
npm install flybase
- 1
- 2
现在我们已经安装了 Flybase 模块,让我们在 app.js 文件的顶部要求并初始化它:
var api_key = "{YOUR-API-KEY}";
var db = "dailysms";
var collection = "users";
var usersRef = require('flybase').init(db, collection, api_key);
- 1
- 2
- 3
- 4
- 5
- 6
当你注册一个 Flybase 帐户时,他们会为你的帐户提供一个 API 密钥。请务必更新此代码,用此键替换{YOUR-API-KEY}
。
从 Flybase 内部,创建一个名为dailysms
的新应用。
因为我们将从 Flybase 中提取电话号码,所以我们希望将 numbers 变量更新为一个空数组,然后用数据库中的信息填充它。
Flybase 是一个实时数据库,它是围绕订阅事件而构建的,而不是按需阅读。我们将订阅两个事件:首先,我们希望检索所有现有电话号码的列表,然后我们希望在添加新用户时得到通知:
var numbers = [];
usersRef.on('value', function(snapshot) {
snapshot.forEach( function( rec ){
numbers.push( rec.value().phonenumber );
console.log( 'Added number ' + rec.value().phonenumber );
});
});
usersRef.on('added', function(snapshot) {
numbers.push( snapshot.value().phonenumber );
console.log( 'Added number ' + snapshot.value().phonenumber );
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
现在,我们需要添加用户到我们的数据库时,他们在订阅文本。让我们重新审视一下我们的消息路径,以进行更新:
```javascript app.post('/message', function (req, res) { var resp = new twilio.TwimlResponse(); if( req.body.Body.trim().toLowerCase() === 'subscribe' ) { var fromNum = req.body.From; if(numbers.indexOf(fromNum) !== -1) { resp.message('You already subscribed!'); } else { resp.message('Thank you, you are now subscribed. Reply "STOP" to stop receiving updates.'); usersRef.push({phonenumber:fromNum}); } } else { resp.message('Welcome to Daily Updates. Text "Subscribe" receive updates.'); } res.writeHead(200, { 'Content-Type':'text/xml' }); res.end(resp.toString()); }); ```js 当 Twilio 消息 webhook 向您的服务器触发一个新的 POST 请求时,我们会在请求参数( [`www.twilio.com/docs/api/twiml/sms/twilio_request#request-parameters`](http://www.twilio.com/docs/api/twiml/sms/twilio_request%2523request-parameters) )中包含关于消息的信息。 我们将使用 Body 参数来检查用户发送的文本内容,使用 From 参数来确定用户发送的文本数量。如果他们已经输入了单词“subscribe ”,并且他们还不在我们的数据库中,我们将使用我们的 Flybase 引用上的 push 函数来添加他们。 我们的应用现已准备就绪。让我们运行并尝试一下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
node app.js
## 摘要 我们做到了!现在你已经建立了一个简单的每日短信提醒应用,这是你自定义每日信息的机会。 # 七、构建实时呼叫跟踪仪表板 本章将向您展示如何实现实时呼叫跟踪仪表板。 我们将分两部分完成这项工作:第一部分是一个简单的 Node.js 文件,它接受来自 Twilio 的来电,然后将信息存储在 Flybase 应用中,第二部分是仪表板本身。 我们将显示两个统计数据,传入的 Twilio 电话号码和电话的始发城市。您可以稍后在此基础上进一步构建。 使用我们最初的仪表板,我们传递事件,实际上不存储任何信息。这一次,我们将存储信息,以便以后检索。 ## 后端 让我们构建您的仪表板的后端部分。 首先,让我们设置我们的“package.json”文件: ```js { "name": "call-tracking", "version": "1.0.0", "description": "Example app demonstrating how to do call tracking with Twilio and Flybase", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/flybaseio/call-tracking.git" }, "author": "", "license": "ISC", "bugs": { "url": "https://github.com/flybaseio/call-tracking/issues" }, "homepage": "https://github.com/flybaseio/call-tracking#readme", "dependencies": { "body-parser": "¹.15.2", "compression": "¹.6.2", "cors": "².8.1", "ejs": "².5.2", "express": "⁴.14.0", "flybase": "¹.7.8", "method-override": "².3.6", "serve-static": "¹.11.1" } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
现在,让我们设置“index.js”文件作为后端运行:
var http = require('http'); var express = require('express'); var bodyParser = require('body-parser'); var flybase = require('flybase'); var path = require('path'); var cors = require('cors'); var compression = require('compression'); var serveStatic = require('serve-static'); var app = express(); app.set('view engine', 'ejs'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static( path.join(__dirname, 'public'))); var port = process.env.PORT || 5000; // set our port var flybaseRef = flybase.init('YOUR-FLYBASE-APP-NAME', "calltracking", 'YOUR-FLYBASE-API-KEY'); // backend app.post('/call', function(req, res) { flybaseRef.push({ time: Date.now()/1000, number: req.body.To, city: req.body.FromCity }).then( function( rec ){ res.type('text/xml'); res.render('twiml', { message: 'Your call has been recorded!' }) }, function(err){ res.type('text/xml'); console.log(error); res.render('twiml', { message: 'Sorry, an error happened.' }); }); });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
现在,让我们添加前端处理程序。这只是同一个“index.js”文件的一部分:
```javascript // frontend function setCustomCacheControl(res, path) { if (serveStatic.mime.lookup(path) === 'text/html') { // Custom Cache-Control for HTML files res.setHeader('Cache-Control', 'public, max-age=0') } } app.use(compression()); app.use(serveStatic(__dirname + '/dashboard', { maxAge: '1d', setHeaders: setCustomCacheControl, 'index': ['index.html'], fallthrough: true })); var server = http.createServer(app); server.listen(process.env.PORT || 3000, function() { console.log('Express server started.'); }); ```js
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
我在这里使用了 serve-static 模块,因为如果我们希望的话,仪表板可以是独立的,所以它只是静态提供的标准 HTML 页面,所以我们会告诉我们的应用显示dashboard
文件夹中的任何文件。
最后,我们需要创建一个名为views
的文件夹,并添加一个名为twiml.ejs
的小文件:
<Response>
<Say><%= message %></Say>
</Response>
- 1
- 2
- 3
- 4
这用于返回我们对传入呼叫的 TwiML (Twilio 标记语言)响应。你可以进一步玩这个,让它做一些事情,如将呼叫连接到另一个号码等等,但对于这个应用,我们只需要记录和跟踪。
前端
我们希望这个仪表板能够在任何地方运行,所以我们只需包含dashboard
文件夹,并设置我们的 Node 应用来静态地为其提供服务。你可以上传dashboard
文件夹到任何你想上传的地方,让它运行并显示你的电话追踪数据。
创建一个名为dashboard
的文件夹。现在,在dashboard
文件夹中创建一个名为index.html
的文件:
<!doctype html> <html> <head> <title>Call Tracking On the Fly</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous"> <link href="https://cdnjs.cloudflare.com/ajax/libs/epoch/0.5.2/epoch.min.css" rel="stylesheet" /> <link href="dashboard.css" rel="stylesheet" /> </head> <body> <div class="navbar-nav navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="index.html"> Call Tracking Dashboard </a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li class="active"> <!-- <a href="index.html"> <i class="icon-home icon-white"></i> Home </a> --> </li> </ul> </div><!--/.nav-collapse --> </div> </div> <div class="container"> <div class="row"> <div class="col-sm-12 col-lg-12"> <article class="widget"> <div class="widget-inner"> <header> <h1>Calls</h1> </header> <section class="widget-body"> <div id="calls" class="epoch" style="height: 200px;"></div> </section> </div><!-- .widget-inner --> </article> </div> </div> <div class="row"> <div class="col-sm-6 col-lg-6"> <article class="widget"> <div class="widget-inner"> <header> <h1>Incoming Number</h1> </header> <section class="widget-body"> <div id="numbers" class="epoch" style="height: 200px;"></div> </section> </div><!-- .widget-inner --> </article> </div> <div class="col-sm-6 col-lg-6"> <article class="widget"> <div class="widget-inner"> <header> <h1>City</h1> </header> <section class="widget-body"> <div id="cities" class="epoch" style="height: 200px;"></div> </section> </div><!-- .widget-inner --> </article> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.10/d3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/epoch/0.5.2/epoch.min.js"></script> <script src="https://cdn.flybase.io/flybase.js"></script> <script src="dashboard.js"></script> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
接下来,您将创建一个名为dashboard.js
的文件:
$( function() { var calls = $('#calls').epoch( { type: 'time.area', axes: ['left', 'bottom', 'right'], data: [ { values: [ { time: Date.now()/1000, y: 0 } ] } ] } ); var numbers = $( '#numbers' ).epoch( { type: 'bar' } ); var cities = $( '#cities' ).epoch( { type: 'bar' } ); var stats = { cities: {}, numbers: {} }; var dashboard = new Flybase("YOUR-FLYBASE-API-KEY", "calltracking", "stats"); dashboard.once('value', function (data) { updateStats( data ); }); dashboard.on( 'added', function (data ){ updateStats( data ); }); function updateStats( data ){ // process the new data... data.forEach( function( snapshot ){ var row = snapshot.value(); calls.push( [ { time: row.time, y: 1 } ] ); var cityCount = stats.cities[ row.city ] || 0; stats.cities[ row.city ] = ++cityCount; var numberCount = stats.numbers[ row.number ] || 0; stats.numbers[ row.number ] = ++numberCount; }); var citiesData = []; for( var city in stats.cities ) { citiesData.push( { x: city, y: stats.cities[ city ] } ); } cities.update( [ { values: citiesData } ] ); var numbersData = []; for( var number in stats.numbers ) { numbersData.push( { x: number, y: stats.numbers[ number ] } ); } numbers.update( [ { values: numbersData } ] ); } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
这是我们仪表板的大脑;它处理所有的电话,并显示在仪表板上。
最后,我们来添加一些 CSS。
创建一个名为“dashboard.css”的文件,并添加以下内容:
body { font: 400 0.95em/1 "Proxima Nova", Helvetica,sans-serif; font-size: .875em; background-color: #f0f0f0; padding-top: 90px; } .widget { -webkit-box-shadow: #f0f0f0 0 0 8px; -moz-box-shadow: #f0f0f0 0 0 8px; box-shadow: #f0f0f0 0 0 8px; background-color: #f0f0f0; margin-bottom: 30px; } .widget h1 { font-size: 1.0em; margin: 0 0 .4em; font-weight: bold; } .widget .widget-inner>header, .widget .widget-inner>footer { font-size: 12px; text-shadow: 1px 1px #0e0e0e; } .widget .widget-inner>header { background-color: #272727; text-transform: uppercase; padding: 16px 12px 16px 26px; font-weight: 700; } .widget .widget-inner { border: solid 1px #e5e5e5; background-color: #fff; } .widget .widget-inner>header { background-color: #f5f5f5; } .widget .widget-inner>header h1 { color: #8b8b8b; text-shadow: 1px 1px #fff; margin-bottom: 0; } .widget .widget-body { color: #666; height: 225px } .widget .widget-body { padding: 16px; color: #d3d4d4; font-family: Helvetica, Arial, sans-serif; z-index: 1; } .widget .widget-inner>footer { color: #8b8b8b; background-color: #f5f5f5; text-shadow: 1px 1px #fff; } .dash-unit { margin-bottom: 30px; padding-bottom: 10px; border: 1px solid #e5e5e5; /*background-image: url('../img/sep-half.png');*/ background-color: #f5f5f5; color: #8b8b8b; height: 290px; text-align: center; } .dash-unit dtitle { font-size: 11px; text-transform: uppercase; margin: 8px; padding: 0px; height: inherit; } .dash-unit hr { border: 0; border-top: 1px solid #151515; border-top-style: dashed; margin-top: 3px; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
摘要
你可以在任何地方运行它。您只需将您想要追踪的 Twilio 电话号码指向您添加到该网站的 URL,并以/call
作为终点。在 GitHub 可以看到完整的代码库: https://github.com/flybaseio/call-tracking
。