Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Node.js
Code

Создание поисковика магазинов на Node.js и Redis

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Посетите веб-сайт любого сетевого ресторана или магазина, и вы, скорее всего, найдете «поиск магазина»: казалось бы, простая небольшая страница, на которой вы вводите свой адрес или почтовый индекс, и он предоставляет расположенные поблизости магазины. Для клиента это здорово, потому что вы можете найти магазин, который находится к вам близко.

Построение «искателя магазинов» на самом деле является сложной задачей. В этом уроке мы расскажем об основах работы с геопространственными данными в Node.js и Redis и создадим рудиментарный поиск магазина.

Мы будем использовать команды «geo» Redis. Эти команды были добавлены в версии 3.2, поэтому вам нужно будет установить ее на вашей рабочей машине. Давайте сначала проверим с помощью redis-cli и напечатаем GEOADD. Вы должны увидеть сообщение об ошибке, которое выглядит так:

Несмотря на сообщение об ошибке, это хороший знак - он показывает, что у вас есть команда GEOADD. Если вы запустите команду, и вы получите следующую ошибку:

Вам нужно будет загрузить, установить и установить версию Redis, которая поддерживает команды geo, прежде чем двигаться дальше.

Теперь, когда у вас есть поддерживаемый сервер Redis, давайте проведем экскурсию по командам geo. Redis имеет шесть команд, которые непосредственно связаны с геопространственным индексированием: GEOADD, GEOHASH, GEOPOS, GEODIST, GEORADIUS и GEORADIUSBYMEMBER.

Начнем с GEOADD. Эта команда, как вы можете себе представить, добавляет геопространственный предмет. Он содержит четыре аргумента: ключ, долгота, широта и элемент. Ключ подобен группировке и представляет одно значение в пространстве ключей. Долгота и широта, очевидно, являются координатами как поплавками; обратите внимание на порядок этих значений, поскольку они, скорее всего, будут в обратном порядке из того, что вы привыкли видеть. Наконец, «member» - это то, как вы собираетесь идентифицировать местоположение. В redis-cli давайте запустим следующие команды:

Это длинный способ добавления нескольких записей, но хорошо видеть шаблон. Если вы хотите сократить этот процесс, вы можете сделать то же самое, повторив долготу, широту и member для каждого дополнительного места в качестве большего количества аргументов. Это пример короткого представления двух последних элементов:

Внутри эти гео-элементы на самом деле не являются чем-то особенным - они хранятся в Redis как zset или сортированный набор. Чтобы показать это, давайте запустим еще несколько команд над ключом va-universities:

Это возвращает zset, как и любой другой отсортированный набор. Теперь, что произойдет, если мы попытаемся вернуть все значения и включить оценки?

Это возвращает массовый ответ элементов, введенных выше, с очень большим числом - 52-битным целым числом. Целое число на самом деле представляет собой представление geohash, умную небольшую структуру, которая может представлять любое место на земном шаре. Мы погрузимся немного глубже позже и на самом деле не будем взаимодействовать с геопространственными данными таким образом, но всегда хорошо знать, как хранятся ваши данные.

Теперь, когда у нас есть некоторые данные для игры, давайте посмотрим на команду GEODIST. С помощью этой команды вы можете определить расстояние между двумя точками, которые вы ранее ввели под одним и тем же ключом. Итак, давайте найдем расстояние между элементами virginia-tech и christopher-newport-university:

Это должно выводить 349054.2554687438 или расстояние между двумя местами в метрах. Вы также можете указать третий аргумент как единица mi (мили), km (километров), ft (футы) или m (метры, значение по умолчанию). Давайте пройдем расстояние в милях:

Команда должна ответить «216.89279795987412».

Прежде чем идти дальше, давайте поговорим о том, почему вычисление расстояния между двумя пространственными точками - это не просто простой геометрический расчет. Земля круглая (или почти), так как вы уходите от экватора, расстояние между линиями долготы начинает сходиться, и они «встречаются» на полюсах. Итак, чтобы рассчитать расстояние, вам нужно учесть земной шар.

К счастью, Redis защищает нас от этой математики (если вам интересно, есть пример чистой реализации JavaScript). Одно замечание, Redis делает предположение, что земля - идеальная сфера (формула Хаверсина), и она может ввести ошибку до 0,5%, что достаточно для большинства приложений, особенно для чего-то вроде магазина.

Большую часть времени нам понадобятся все точки в определенном радиусе местоположения, а не только расстояние между двумя точками. Мы можем сделать это с помощью команды GEORADIUS. Команда GEORADIUS ожидает, по крайней мере, ключа, долготы, широты, расстояния и единицы. Итак, давайте найдем все университеты в наборе данных в пределах 100 миль от этого пункта.

Что возвращает:

У GEORADIUS есть несколько опций. Скажем, мы хотели получить расстояние между нашей указанной точкой и всеми местоположениями. Мы можем сделать это, добавив аргумент WITHDIST в конце:

Это возвращает массовый ответ с указанием местоположения и расстояния (в указанном модуле):

Другим необязательным аргументом является WITHCOORD, который, как вы могли догадаться, возвращает вам координаты долготы и широты. Вы можете соединить это с аргументом WITHDIST. Попробуем это:

Результат получается немного сложнее:

Обратите внимание, что расстояние приближается к координатам, несмотря на обратный порядок в наших аргументах. Redis не заботится о том, в каком порядке вы указываете аргумент WITH *, но он вернет расстояние до координат. Существует еще один аргумент (WITHHASH), но мы расскажем об этом в следующем разделе - просто знайте, что он будет последним в вашем ответе.

Небольшое отступление от вычислений, происходящих здесь - если вы думаете о математике, которую мы ранее рассматривали в работе GEODIST, давайте подумаем о радиусе. Поскольку радиус является кругом, мы должны думать о том, что круг лежит над сферой, которая совершенно отличается от простого круга, наложенного на плоскую плоскость. Опять же, Redis делает все эти расчеты для нас (к счастью).

Теперь давайте рассмотрим команду GEORADIUS, связанную с GEORADIUSBYMYBER. GEORADIUSBYMEMBER работает точно так же, как GEORADIUS, но вместо указания долготы и широты в аргументах вы можете указать элемент уже в своем ключе. Таким образом, это, например, вернет все элементы в пределах 100 миль от university-of-virginia.

Вы можете использовать те же единицы и аргументы и единицы WITH * на GEORADIUSBYMEMBER, как вы могли бы на GEORADIUS.

Раньше, когда мы запускали ZRANGE по нашему ключу, вы, возможно, задавались вопросом, как вернуть координаты из позиции, которую вы добавили с помощью GEOADD, - мы можем выполнить это с помощью команды GEOPOS. Предоставляя ключ и элемент, мы можем получить обратно координаты:

Что должно дать результат:

Если вспомнить, когда мы добавили значение для university-of-virginia, цифры немного отличаются, хотя они округляются до той же суммы. Это связано с тем, как Redis сохраняет координаты в формате geohash. Опять же, это очень близко и достаточно хорошо для большинства приложений. В приведенном выше примере фактическая разница расстояний между входом и выходом GEOPOS составляет 5,5 дюйма / 14 см.

Это приводит нас к нашей окончательной команде Redis GEO: GEOHASH. Это вернет значение geohash, используемое для хранения координат. Ранее упоминалось, что это умная система, основанная на сетке и может быть представлена различными способами. Redis использует 52-битное целое число, но более часто встречающееся представление представляет собой строку base-32. Используя команду GEOHASH с ключом и элементом, Redis вернет строку base-32, которая представляет это местоположение. Если мы запустим команду:

Вы получите:

Это строковое представление geohash base-32. Строки Geohash имеют аккуратное свойство: если вы удаляете символы справа от строки, вы постепенно уменьшаете точность координат. Это можно проиллюстрировать на веб-сайте geohash - посмотрите на эти ссылки и посмотрите, как координаты и карта удаляются от исходного местоположения:

Есть еще одна функция, которую нам нужно будет покрыть, и если вы уже знакомы с сортированными наборами Redis, вы ее уже знаете. Поскольку ваши геопространственные данные действительно просто хранятся в zset, мы можем удалить элемент с помощью ZREM:

Server искателя магазинов

Теперь, когда у нас есть основы для использования команд Redis GEO, давайте построим Node.js-based сервер поиска в качестве примера. Мы собираемся использовать данные выше, поэтому я предполагаю, что технически это искатель университетов, а не поисковик магазина, но концепция идентична. Прежде чем начать, убедитесь, что у вас установлены Node.js и npm. Создайте каталог для своего проекта и перейдите в этот каталог в командной строке. В командной строке введите:

Это создаст ваш файл package.json, задав вам несколько вопросов. После инициализации вашего проекта мы будем устанавливать четыре модуля. Опять же, из командной строки выполните следующие четыре команды:

Первый модуль - Express.js, модуль веб-сервера. Чтобы пользоваться сервером, нам также потребуется установить систему шаблонов. Для этого проекта будет использоваться pug (ранее известный как Jade). Pug прекрасно интегрируется с Express и позволит нам создать основной шаблон страницы всего в нескольких строках. Мы также установили node_redis, который управляет соединением между Node.js и сервером Redis. Наконец, нам понадобится еще один модуль для обработки значений HTTP POST: body-parser.

Для нашего первого шага мы просто собираемся запустить сервер так, что он сможет принимать HTTP-запросы и заполнять шаблон со значениями.

Этот сервер будет успешно обслуживать только страницу верхнего уровня ('/'), и только если HTTP-клиент (a.k.a. браузер) с помощью метода GET или POST.

Нам понадобится шаблон bare-bones - достаточно, чтобы показать заголовок, форму и (позже) показать результаты. Pug - очень сложный язык шаблонов с соответствующими пробелами. Таким образом, при вложенности тегов вложений первое слово строки после отступа является тегом (и закрывающие теги выводятся парсером), и мы интерполируем значения с помощью #{}. К такому не сразу привыкаешь, но вы можете быстро создать много HTML с минимальными усилями - взгляните на сайт pug, чтобы узнать больше. Обратите внимание, что на момент публикации этой статьи официальный сайт Pug не обновлялся. Вот официальный тикет GitHub относительно этой проблемы.

Мы можем попробовать наш магазин, запустив сервер в командной строке:

Затем открыть на ваш браузер по адресу http://localhost:3000/.

Вы должны увидеть простую, неэкранированную страницу с большим заголовком, в котором говорится «Университетский поиск» и форма с несколькими текстовыми полями. Поскольку обычный запрос страницы браузером - это запрос GET, эта страница генерируется функцией в аргументе для app.get.

Basic form screenshot

Если вы вводите значения в поля широты и долготы и нажимаете «найти», вы увидите, что эти результаты отображаются и отображаются в строке, которая читает «Отображение результатов для ...». На данный момент у вас не будет никаких результатов, поскольку мы еще не интегрировали Redis.

Form with values after click screenshot

Интеграция Redis

Чтобы интегрировать Redis, сначала нам нужно немного настроить. В объявлении переменной укажите как модуль, так и переменную (пока еще не определенную) для клиента.

После объявления переменной нам нужно будет создать соединение с Redis. В нашем примере мы предположим подключение localhost на порту по умолчанию и без аутентификации (в производственной среде обязательно защитите свой сервер Redis).

Уточненная особенность node_redis заключается в том, что клиент будет устанавливать очереди в очереди при установлении соединения, поэтому нет необходимости беспокоиться о ожидании установления соединения с сервером Redis.

Теперь, когда наш экземпляр node имеет клиента Redis, который может принимать соединения, давайте поработаем над сердцем нашего поисковика. Мы будем использовать широту и долготу пользователя и применять их к команде GEORADIUS. Наш пример использует 100-мильный радиус. Мы также захотим получить расстояние и координаты этих результатов.

В обратном вызове мы обрабатываем любые ошибки, если они возникнут. Если ошибок не обнаружено, сопоставьте полученные результаты, чтобы сделать их более полезными и легче интегрировать в шаблон. Эти результаты затем подаются в шаблон.

В шаблоне нам нужно обработать набор результатов. Pug имеет бесшовную итерацию по массивам (с почти словесным синтаксисом). Речь идет о том, чтобы потянуть эти значения за один результат; шаблон будет обрабатывать все остальное.

После того, как вы получите окончательный код шаблона и node, снова запустите свой сервер app.js и откройте свой браузер снова по адресу http://localhost:3000/.

Если вы введете широту 38.904722 и долготу -77.016389 (координаты для Вашингтона, округ Колумбия, на северной границе Вирджинии) в поле и нажмите «Найти», вы получите три результата. Если вы измените значения на широту 37.533333 и долготу -77.466667 (Ричмонд, штат Вирджиния, столица штата и в центральной / восточной части штата), вы увидите десять результатов.

На данный момент у вас есть основные части поисковика, но вам нужно настроить его в соответствии с вашим собственным проектом.

  • Большинство пользователей не думают с точки зрения координат, поэтому вам нужно будет рассмотреть более удобный подход, например:
    1. Использование клиентского JavaScript для определения местоположения с использованием API геолокации
    2. Использование службы геолокатора на основе IP
    3. Запросите у пользователя почтовый индекс или адрес и используйте службу геокодирования, которая преобразует его в координаты. На рынке существует множество различных услуг по геокодированию, поэтому выберите ту, которая хорошо работает для вашей целевой области.

  • Этот скрипт не выполняет валидации формы. Если вы оставите поля ввода широты и долготы, вы должны убедиться, что вы проверяете свои данные и избегаете сообщения об ошибке.

  • Расширьте ключ местоположения до более полезных данных. Если вы используете Redis для хранения дополнительной информации о каждом местоположении, подумайте о сохранении этой информации в хэшах с помощью ключа, который соответствует вашим возвращенным элементам из GEORADIUS. Вам нужно будет сделать дополнительный вызов для Redis.

  • Более тесная интеграция с сервисами карт, такими как Карты Google, OpenStreetMap или Bing Maps, чтобы обеспечить встроенные карты и направления.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.