vorst.ru - Статьи о задачах возникающих при создании сайта и их решении
Статьи помеченные log

Как создать индекс ElasticSearch

Полнотекстовый поиск

Один парень написал: "Нужно сделать поиск слов в таблице "articles" по столбцу "content". В результатах поиска сначала выводить те, которые содержат наибольшее количество слов в статье и далее по мере уменьшения."

Мысль летит) Но ясно, что нужен полнотекстовый поиск.


Модуль для управления блогом

Блог включает управление комментариями, пользователями, rbac

Новости, сообщения, описания - это все статьи, а статьи - это блог. Блог является важной составляющей любого сайта.

Блог настолько важен, что самая популярная CMS на планете WordPress - это блог.

Во фреймворке блог пишется самостоятельно или используется готовый модуль. Или сначала пишется самостоятельно, а потом переписывается как модуль.


Какую статью выбрать следующей или предыдущей

Как выбрать все статьи в цепочке

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

Обычно для связывания таких статей используются рубрики и метки. Даже с присвоением меток возникают проблемы, а с рубриками еще сложнее. Им надо давать названия, придумывать иерархию. И даже это, в результате, не всегда помогает.


Цепочки статей в блоге

Можно ли сделать блог удобнее?

Ну, во-первых, удобнее по отношению к чему? К WordPress, например. Некоторое время назад я использовал эту программу для ведения блога.

Для группировки статей в WordPress существует возможность помечать статьи метками или прикреплять к наперед заданной рубрике. Кроме того, в конце статьи есть навигация - предыдущая, следующая статья, ну и меню конечно. Вроде все удобно.



Поиск



Как создать индекс ElasticSearch

Полнотекстовый поиск

  • 1. Как создать индекс ElasticSearch
  • 2. Интерфейс для полнотекстового поиска

Один парень написал: "Нужно сделать поиск слов в таблице "articles" по столбцу "content". В результатах поиска сначала выводить те, которые содержат наибольшее количество слов в статье и далее по мере уменьшения."

Мысль летит) Но ясно, что нужен полнотекстовый поиск.


    Поделиться

Что мы имеем? Есть таблица с заголовками, статьями и прочими полями. Нужен индекс, который позволит проводить поиск по содержимому. Искать надо слово или набор слов, причем в любом количестве и быстро. Для подобной задачи подходит ElasticSearch и начать нужно с установки Java, ElasticSearch (который требует версии PHP >= 7.0) и расширения yii2-elasticsearch.

Model

Индекс нужно создавать для модели, содержащей статьи. Допустим это модель Post. В общем каталоге для моделей, то есть в папке common/models создадим подкаталог elastic и определим в нем класс модели для индекса (yii2-elasticsearch doc, https://habr.com/post/280488/).

<?php
namespace common\models\elastic;
use yii\elasticsearch\ActiveRecord;
class Post extends ActiveRecord
{
  // DB name
  public static function index() 
  {  
    return 'blog'; 
  } 
  // Table name
  public static function type() 
  {
    return 'post';  
  }
  public function attributes()
  {
    return ['id', 'title', 'content'];
  }
  public function rules() 
  {   
    return [
      [$this->attributes(), 'safe']  
    ];
  }
  // Table definition
  public static function mapping()
  {
    return [
      static::type() => [
        'properties' => [
          'id' => ['type' => 'long'],
          'title' => ['type' => 'text'],
          'content' => ['type' => 'text'],
        ]
      ],
    ];
  }
  public static function updateMapping()
  {
    $db = static::getDb();
    $command = $db->createCommand();
    $command->setMapping(static::index(), static::type(), static::mapping());
  }
  // Fill in by common/models/Post model
  public function fill($model, $setPrimaryKey = true) {
    if($setPrimaryKey)
      $this->primaryKey = $model->id;
    $this->attributes = [
      'title' => $model->title,
      'content'  => $model->content,
    ];
  }
  public static function createIndex()
  {
    $db = static::getDb();
    $command = $db->createCommand();
    if($command->indexExists(static::index()))
      $command->deleteIndex(static::index());
    $command->createIndex(static::index(), [
      'settings' => [
        'analysis' => [
          'filter' => [
            'ru_stop' => [
              'type' => 'stop',
              'stopwords' => '_russian_',
            ],
            'ru_stemmer' => [
              'type' => 'stemmer',
              'language' => 'russian',
            ],
          ],
          'analyzer' => [
            'default' => [
              'char_filter' => [
                'html_strip',
              ],
              'tokenizer' => 'standard',
              'filter' => [
                'lowercase',
                'ru_stop',
                'ru_stemmer',
              ],
            ],
          ],
        ],
      ],
      'mappings' => static::mapping(),
    ]);
  }
}

Полей в индексе может быть больше. Собственно, можно указать все поля, где целесообразно вести поиск. В "боевой" версии я добавил еще excerpt.

При создании индекса используется анализатор. Я просто скопировал его готовым из примера. Очевидно, что он требует к себе большего внимания, но результата можно достичь и копипастом. А результат классный - поиск ведется не только по полному совпадению слова, но и по корню.

Controller

Для выполнения поиска нужно предварительно сформировать индекс. Делается это просто. Перебираются все записи основной модели и сохраняются в индексе.

Контроллер можно определить консольным - для начала работы удобно формировать индекс из консоли.

Кроме того, при изменении записей в основной модели, индекс требует периодического полного обновления. Эту процедуру тоже удобно проводить из консоли, через cron.

<?php
namespace console\controllers;
use yii\console\Controller;
use common\models\Post;
use common\models\elastic\Post as ElasticPost;
class ElasticController extends Controller {
  public function actionCreateIndex() {
    ElasticPost::createIndex();
    foreach(Post::find()->where([
      'status' => Post::STATUS_PUBLISHED
    ])->all() as $post) {
      $elastic = new ElasticPost();
      $elastic->fill($post);
      $elastic->save();
    }
    \Yii::info('The ElasticSearch index was created ('. 
      ElasticPost::index() .'/'. ElasticPost::type() .').', __METHOD__);
  }
}

Log message

Оператор Yii::info() записывает сообщение в лог. Лог может быть не только файлом, но и email ящиком. В этом случае, после формирования индекса, можно получать извещение на нужный ящик.

Параметр categories в console/config.php определяет, что на ящик нужно отправлять только сообщения начинающиеся на указанную строку. В свою очередь строка задается в __METHOD__. Параметр logVars нужно обязательно обнулить, чтобы не получать кучу не нужной информации.

<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);
$mailer = require(__DIR__ . '/../../common/config/mailer.php');
return [
  'id' => 'app-console',
  'basePath' => dirname(__DIR__),
  'bootstrap' => ['log'],
  'controllerNamespace' => 'console\controllers',
  'components' => [
    'urlManager' => [
      'class' => 'yii\web\UrlManager',
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'baseUrl' => 'http://localhost/console',
    ],
    'mailer' => $mailer,
    'log' => [
      'targets' => [
        [
          'class' => 'yii\log\FileTarget',
          'levels' => ['error', 'warning'],
        ],
        [
          'class' => 'yii\log\EmailTarget',
          'levels' => ['info'],
          'categories' => ['console\controllers\ElasticController*'],
          'logVars' => [],
          'message' => [
            'from' => ['admin@sample.ru'],
            'to' => ['admin@sample.ru'],
            'subject' => 'ElasticSearch',
          ],
        ],
      ],
    ],
  ],
  'params' => $params,
];

Создание индекса ElasticSearch

Переходим в корень приложения, запускаем и ждем письма. Не забудьте определить mailer.

>php yii elastic/create-index

Проверим, как работает индекс.

curl -XGET "localhost:9200/blog/post/_search?pretty" -d'
{
  "_source": ["title"],
  "query": {
    "match": {
      "content": "history"
    }
  }
}'

Заключение

Для использования сформированного индекса нужен интерфейс во frontend, который позволит вводить слова поиска и учитывать полученное условие при выдаче результатов. Но об этом в следующей статье.

Leave a comment

Only authorized users can leave comments. Please log in or pass a registration.