*Это первая часть многосерийного блога о создании приложения для голосования по SMS и голосу в реальном времени с использованием Node.js. В первой части мы создадим приложение Node.js, настроим базу данных CouchDB и подключим все к Twilio, чтобы мы могли обрабатывать голоса через SMS.*
Хотя я занимаюсь разработкой веб-приложений с 1999 года и использовал Java, PHP, Python и Ruby, это был мой первый опыт работы с такими технологиями, как Node.js и CouchDB. Во всех предыдущих проектах я использовал реляционную базу данных, такую как MySQL, как правило, со специфическим для языка ORM. Безболезненно переключившись между этими технологиями, я был шокирован тем, насколько растерянным я себя почувствовал, создавая приложение Node.js и работая с базой данных NoSQL, такой как CouchDB.
Я подумал, что может быть полезно для других людей, совершающих аналогичный переход, написать о своем опыте кодирования Votr, о подводных камнях, с которыми я столкнулся, и о нескольких найденных мною решениях. В этом первом посте я просто пройдусь по основам: созданию приложения, настройке базы данных и обработке голосов через SMS.
Во-первых, вам нужно будет установить Node.js и NPM. Для остальной части этого блога я буду работать в терминале Mac OS X, но эти шаги должны быть легко выполнимы в Windows или Linux. Вот как установить Express и создать приложение:
«`bash
npm install -g express
express votr
cd votr
npm install
«`
На данном этапе у вас должно быть стартовое приложение Node. Давайте развернем его в облаке.
Для целей этого руководства я буду использовать Nodejitsu для развертывания нашего приложения Node.
«`bash
npm install -g nodejitsu
jitsu config use
jitsu deploy
«`
Вас попросят ввести поддомен для вашего приложения и версию Node.js для использования. Значения по умолчанию должны работать нормально, так что не стесняйтесь нажимать Enter. После завершения развертывания проверьте, работает ли оно:
«`bash
jitsu ps
«`
Настройка локальной CouchDB проста, но для целей этого руководства я покажу вам, как использовать размещенную CouchDB от Cloudant. Я оценил несколько различных размещенных решений и столкнулся с наименьшим количеством проблем с Cloudant. Мне действительно понравился их веб-интерфейс для CouchDB.
Прежде чем приступить к этому, вы можете задаться вопросом, чем CouchDB (и NoSQL в целом) отличается от MySQL. Хотя это выходит за рамки этого руководства, ниже приведена таблица, сравнивающая некоторые основные концепции MySQL и CouchDB:
| MySQL | CouchDB |
|—|—|
| Таблицы | Коллекции |
| Строки | Документы |
| Столбцы | Поля |
| Схемы | Динамические схемы (функции валидации, но не схемы) |
В нашем приложении наши документы событий будут иметь следующую структуру:
**Name:** название события, по которому происходит голосование
**Short Name:** версия этого для URL-адреса
**State:** состояние голосования (включено, выключено и т. д.)
**Vote Options:** массив вариантов для голосования, где каждый вариант выглядит следующим образом
**Id:** номер для голосования
**Name:** имя для отображения
**Votes:** количество голосов, поданных за этот вариант
**Numbers:** массив телефонных номеров, проголосовавших за этот вариант
Давайте загрузим фиктивный документ события в нашу базу данных, чтобы мы могли приступить к созданию нашего приложения.
«`javascript
{
«_id»: «event_1»,
«name»: «Favorite Color»,
«short_name»: «color»,
«state»: «on»,
«vote_options»: [
{ «id»: 1, «name»: «Red», «votes»: 0, «numbers»: [] },
{ «id»: 2, «name»: «Blue», «votes»: 0, «numbers»: [] },
{ «id»: 3, «name»: «Green», «votes»: 0, «numbers»: [] }
]
}
«`
Как только наше приложение будет запущено и будет работать, нам нужно будет иметь возможность запрашивать события. В мире CouchDB для запроса документов используются представления (views). Для всех, кто работал с базами данных Oracle, это покажется знакомым. В Oracle представление — это просто предопределенный SQL-запрос, который ведет себя как таблица только для чтения. В CouchDB представление — это функция отображения (map function), которая обрабатывает все документы в вашей базе данных и создает хэш пар ключ/значение. Эти функции хранятся внутри специальных документов, называемых Design Documents. В нашем приложении мы захотим иметь возможность запрашивать события по их телефонному номеру и короткому имени. Давайте создадим документ дизайна и вставим содержимое Gist ниже.
«`javascript
{
«_id»: «_design/events»,
«views»: {
«by_phone»: {
«map»: «function (doc) { emit(doc.short_name, doc._id); }»
},
«by_short_name»: {
«map»: «function (doc) { emit(doc.short_name, doc._id); }»
}
}
}
«`
Итак, после закусок переходим к главному блюду: написанию нашего приложения Node.js. Команда express предоставила нам хорошую основу для использования, так что давайте начнем реализовывать конкретные детали.
«`javascript
// app.js
var express = require(‘express’);
var cradle = require(‘cradle’);
var twilio = require(‘twilio’);
var app = express.createServer();
// Modules
var votr = require(‘./votr’);
// Configuration
var config = require(‘./config’);
app.configure(function(){
app.set(‘views’, __dirname + ‘/views’);
app.set(‘view engine’, ‘ejs’);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + ‘/public’));
});
// Routes
app.get(‘/’, function(req, res){
res.render(‘index’, {
locals: {
title: ‘Votr’
}
});
});
app.post(‘/sms’, function(req, res){
// Respond to Twilio
res.send(‘
// Get vote details
var from = req.body.From;
var to = req.body.To;
var body = req.body.Body;
// Process vote
votr.processVote(from, to, body);
});
app.listen(8080);
«`
Создайте новый файл с именем `votr.js` в корневой папке вашего приложения. Этот файл будет содержать полезные функции, которые мы будем использовать в нашем приложении.
«`javascript
// votr.js
var cradle = require(‘cradle’);
var config = require(‘./config’);
var c = new cradle.Connection(config.couchdb.url, config.couchdb.port, {
auth: {
username: config.couchdb.username,
password: config.couchdb.password
}
});
var db = new Connection.database(‘votr’);
exports.processVote = function(from, to, body) {
db.get(‘event_1’, function (err, doc) {
if (err) {
console.log(‘Error getting event document: ‘ + err);
} else {
var response = votr.processVote(doc, from, body);
// Update document
db.save(response.doc, function (err, res) {
if (err) {
console.log(‘Error saving vote: ‘ + err);
} else {
console.log(‘Vote saved: ‘ + JSON.stringify(res));
}
});
}
});
}
exports.processVote = function(doc, from, body) {
var vote = parseInt(body);
var response = {
code: 200,
message: «»,
doc: doc
};
if (isNaN(vote)) {
response.message = «Invalid vote. Please send the number of the option you want to vote for.»;
} else {
var option = votr.getOption(doc, vote);
if (!option) {
response.message = «Invalid vote. That option does not exist.»;
} else {
if (votr.hasVoted(doc, from)) {
response.message = «You have already voted for this event.»;
} else {
// Cast vote
option.votes++;
option.numbers.push(from);
response.message = «Thank you for your vote!»;
}
}
}
return response;
}
exports.getOption = function(doc, vote) {
for (var i = 0; i < doc.vote_options.length; i++) { if (doc.vote_options[i].id == vote) { return doc.vote_options[i]; } } return false; } exports.findBy = function(key, value, callback) { db.view('events/by_short_name', { key: value, include_docs: true }, function (err, res) { if (err) { callback(err); } else { callback(false, res); } }); } exports.hasVoted = function(doc, phone_number) { for (var i = 0; i < doc.vote_options.length; i++) { for (var j = 0; j < doc.vote_options[i].numbers.length; j++) { if (doc.vote_options[i].numbers[j] == phone_number) { return true; } } } return false; } ``` Создайте новый файл с именем `config.js` в корневой папке вашего приложения. В этом файле мы будем хранить наши учетные данные для CouchDB и Twilio. ```javascript // config.js exports.couchdb = { url: 'https://your-username.cloudant.com', port: 443, username: 'your username', password: 'your password' }; exports.twilio = { sid: 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', token: 'your twilio auth token', number: '+15555555555' }; ``` Теперь, когда мы установили все наши зависимости, давайте запустим наше приложение, чтобы проверить, все ли работает: ```bash node app.js ``` Вы должны увидеть следующую ошибку, поскольку мы еще не установили наши пакеты: ```bash Error: Cannot find module 'cradle' ``` Это потому, что мы забыли установить наши зависимости! Давайте сделаем это сейчас: ```bash npm install cradle twilio ``` Теперь, когда мы добавили наши зависимости, давайте добавим их в наш файл манифеста пакета. ```json { "name": "votr", "version": "0.0.1", "dependencies": { "express": "2.5.8", "ejs": "0.8.3", "cradle": "0.6.6", "twilio": "1.0.0-rc.1" } } ``` Наконец, если вы хотите, чтобы ваше приложение работало в режиме реального времени, вы можете получить доступ к своим данным напрямую из CouchDB. В противном случае для работы этого приложения вам не нужно ничего делать. В продакшене вы, вероятно, захотите использовать представления CouchDB для доступа к своим данным. - config.couchdb.url: ‘https://your-username.cloudant.com’ - config.couchdb.port: 443 - config.couchdb.username: ‘your username’ - config.couchdb.password: ‘your password’ - findBy – ищет событие по ключу - hasVoted – проверяет, голосовал ли человек уже за конкретное событие - saveVote – сохраняет голос человека X-Twilio-Signature и отображает следующее видео для потенциальных злоумышленников. Хорошо, мы почти закончили! Последнее, что нам нужно сделать, это настроить номер телефона для обработки входящих голосов через SMS. Зайдите в свою учетную запись Twilio и перейдите в раздел «Numbers». Затем нажмите «Buy a Number» и найдите номер, поддерживающий SMS. Когда вы найдете подходящий номер, нажмите «Buy», затем «Configure». В разделе «Forwarding» выберите «Webhook» и введите URL вашего приложения. В нашем случае это будет `http://your-app-url.com/sms`. Когда вы введете свой номер, пожалуйста, убедитесь, что он в формате E.164 (например, +12026189023). На данном этапе вы можете взять свой телефон и начать тестировать свое приложение для голосования. Вот простой скрипт, который я использую: - Отправьте действительный голос: отправьте «1» (без кавычек) на номер голосования - Отправьте голос вне диапазона: отправьте «7» - Отправьте недействительный голос: отправьте «A» - Попробуйте проголосовать дважды: отправьте «1» Ниже вы увидите результаты этих текстовых сообщений в логах Nodejitsu для вашего приложения: ``` info: Received POST /sms info: Event: Favorite Color info: Voted: 1 info: Response: Thank you for your vote! ``` Давайте дважды проверим нашу базу данных в Cloudant, чтобы убедиться, что голос поступил:  На этом завершается первая часть создания приложения для голосования по SMS в реальном времени с использованием Node.js. Весь исходный код этого приложения можно найти на Github: https://github.com/crabasa/votr-part1. Вы, вероятно, задаетесь вопросом, где же часть про "реальное время". Во второй части мы создадим визуализацию для этого приложения для голосования с использованием Highcharts JS и анимируем эту визуализацию в реальном времени с помощью Socket.io.