Создание приложения для голосования в реальном времени по SMS, Часть 2: Socket.io и Highcharts.js

Автор: | 22.07.2026

Это вторая часть серии статей о создании приложения для голосования в реальном времени по SMS с использованием Node.js. В первой части мы создали приложение Node.js, настроили базу данных CouchDB и обрабатывали входящие голоса по SMS с помощью Twilio. В этой статье мы создадим диаграмму, которая отображает статус голосования, и будем обновлять эту диаграмму в реальном времени с помощью Socket.io.

Теперь, когда мы можем принимать голоса по SMS и сохранять их в нашей базе данных, пришло время создать интерактивный просмотр голосования. Существует множество способов визуализации голосования, но столбчатая диаграмма, вероятно, является самым простым. Существует множество библиотек для построения графиков и диаграмм, и я потратил немало времени на поиск подходящей. Вот мои требования:

— Клиентский JS

— Легкое обновление данных и перезагрузка

— Отличные стили по умолчанию

Покопавшись, я нашел отличную ветку на Hacker News об этом. Я рассмотрел некоторые популярные библиотеки FOSS, такие как Flot и gRaphaël, но обнаружил, что у них были нетривиальные кривые обучения, особенно когда дело доходило до динамического обновления данных графика. В итоге я остановился на Highcharts и смог настроить диаграмму голосования менее чем за 30 минут. Мы рассмотрим Highcharts подробнее позже, но сначала давайте подключим наше приложение Node.js для приема URL-запросов для отдельных событий.

Представление, которое мы строим, будет представлять собой одно событие. События хранятся в нашей CouchDB как отдельные документы. Поэтому, когда пользователь делает запрос к нашему приложению, URL выглядит следующим образом:

Помните, что routes — это модуль, который мы определили для хранения нашей логики контроллера. В нашем файле routes/index.js теперь нам нужно создать функцию для обработки запроса:

Функция обратного вызова события относительно проста и выполняет следующие действия:

— Загружает событие из CouchDB на основе запроса представления

— Санитизирует конфиденциальные данные в документе

— Передает хэш пар ключ/значение в представление

— Возвращает 404, если событие не удалось найти

Теперь давайте настроим наше представление. В мире приложений Node.js у вас есть удивительный набор шаблонизаторов на выбор. Я не уверен, что это хорошо, иногда мне не хватает комфорта стеков, которые навязывают определенный набор соглашений. В любом случае, нам повезло, потому что замечательный Garann Means создал отличный подборщик шаблонов Node.js, чтобы помочь людям выбрать идеальный шаблонизатор. Давайте пройдемся по критериям для моего приложения:

— Используется на клиенте или сервере?

**Сервер** — Сколько логики он должен иметь?

**Основы** — Должен ли он быть самым быстрым?

**Нет** — Нужно ли предварительно компилировать шаблоны?

**Нет** — Нужны ли вам частичные шаблоны?

**Нет** — Нужна ли вам структура DOM или просто строка?

**Строка** — Помимо тегов шаблона, должен ли он быть на том же языке до и после рендеринга?

**Да**

И вот результаты!

Поскольку мы начали наш проект с использованием Hogan.js (HJS), и он соответствует нашим критериям, мы будем придерживаться его. Давайте создадим новый файл с именем events.hjs и поместим его в папку views нашего приложения. Hogan.js (и Handlebars, и многие другие) являются частью семейства логически-бедных шаблонизаторов mustache. Если вы посмотрите на наш новый файл events.hjs, вы увидите строки вроде этой:

Имя переменной, которое вы видите, соответствует имени ключа, которое мы определили в нашей функции события в routes/index.js. Шаблоны в стиле Mustache потрясающие и действительно улучшают читаемость и поддерживаемость ваших представлений, заставляя разделять ответственность и гарантируя, что ваша логика находится в вашем приложении. В этом файле происходит много всего, но давайте теперь посмотрим на Highcharts и как его настроить.

Чтобы использовать Highcharts, вам сначала нужно будет скачать и включить JS-библиотеку:

ID div полностью настраивается. Следующим шагом является использование jQuery для обнаружения окончания загрузки HTML-страницы, а затем для создания объекта Highcharts и инициализации его значений:

В нашем приложении Node.js мы использовали JSON.stringify для сериализации наших объектов voteoptions перед отправкой их в представление. Для справки, объект voteoptions выглядит так:

Это массив объектов, представляющих варианты, за которые идет голосование, и некоторую информацию об этих вариантах, такую как количество голосов, которые каждый из них набрал. В нашем приложении Node.js мы сериализуем этот объект с помощью функции JSON.stringify. Следующие строки кода отображают эту строку в нашем блоке JavaScript и декодируют ее, сначала отменяя HTML-кодирование, которое применил шаблонизатор к строке, а затем используя функцию JSON.parse:

Если вам интересно, как работает метод unescapeHtml, который я применяю к моему объекту String данных, это небольшой monkeypatch, который я инициализировал ранее в скрипте. Вот функция:

На этом этапе у нас есть переменная voting, содержащая текущее состояние этого события. Все, что нам нужно сделать, это пройти по этим данным и построить пару массивов, которые Highcharts будет использовать для определения серий:

Как только массивы chartdata и labels будут инициализированы, мы просто передадим эти массивы Highcharts во время инициализации:

И это все! Предполагая, что вы уже настроили событие в своей базе данных, вы можете перейти по адресу http://domain.com/events/event-short-name, и вы должны увидеть что-то вроде этого:

Теперь пришло время добавить немного магии реального времени и анимировать этот график по мере поступления голосов по SMS. Для этого мы будем использовать socket.io, вероятно, самый простой способ добавить возможности реального времени в приложение Node.js. Socket.io предоставляет как NPM-модуль (на стороне сервера), так и клиентскую JS-библиотеку и предоставляет чистый и понятный интерфейс для разработчиков при создании своих приложений реального времени. Socket.io поддерживает веб-сокеты, но будет поддерживать старые браузеры, понижая уровень до XHR и long polling.

Добавление socket.io в наше приложение Node.js легко:

— Отредактируйте package.json и добавьте «socket.io»: «0.9.11» в наши зависимости

— npm install

В нашем app.js давайте настроим socket.io для использования:

Последний блок кода стоит обсудить. Я просто настраиваю обработчик событий, который будет выполняться, когда socket.io обнаружит соединение от клиента, в нашем случае браузера. Когда этот сокет установлен, я настраиваю обработчик сообщений для этого конкретного сокета, который будет слушать сообщение «event».

Сообщение event — это, по сути, браузер, говорящий: «Привет, сервер, я заинтересован в получении информации о новых голосах для события X. Пожалуйста, не беспокой меня голосами для других событий». В ответ на это сообщение сервер помещает этот сокет в нечто называемое комнатой с помощью метода join. Комнаты — это просто удобный способ группировки сокетов. Думайте о них как о чатах. Когда сообщение отправляется в комнату, его будут получать только сокеты в этой комнате. В нашем случае каждое событие голосования имеет уникальное имя и будет использоваться для определения комнаты.

Теперь давайте подключим все на стороне клиента. Во-первых, давайте включим JS-библиотеку:

Затем давайте откроем сокет, и после установления соединения отправим сообщение event на сервер.

Хорошо, мы почти закончили. Все, что нам теперь нужно сделать, это использовать socket.io, чтобы сообщить браузеру, когда поступают новые голоса. На сервере давайте посмотрим на routes/index.js и нашу функцию voteSMS. На строке 94 мы видим, что мы успешно сохранили голос в базу данных и собираемся отправить SMS-ответ «спасибо» голосующему. Это идеальное место для добавления кода для обновления нашей диаграммы:

Сначала мы ограничиваем наше исходящее сообщение сокетами в «комнате» для этого события, используя io.sockets.in, а затем мы передаем сообщение «vote» со значением голоса. Давайте посмотрим на код на стороне клиента, который получит это сообщение:

Этот код принимает входящий голос, анализирует строку для создания целого числа, вычисляет индекс массива, а затем выполняет простое инкрементирование счетчика голосов. Вот видео этого в действии:

Итак, это завершает вторую часть создания приложения для голосования в реальном времени с использованием Node.js. Исходный код приложения, которое мы построили в этой статье, доступен на Github: https://github.com/crabasa/votr-part2.

Если вы проницательный инженер, вы, возможно, начали замечать некоторые очевидные проблемы с масштабируемостью кода на данный момент. Можно с уверенностью сказать, что это приложение не будет хорошо работать даже при умеренной нагрузке голосования. Но это нормально, я хотел начать с нуля и строить это приложение поэтапно. В Части 3 мы рассмотрим, как заставить это приложение масштабироваться до сотен или тысяч входящих голосов в секунду.