Однажды в рассылке Golang Weekly мне попался проект Bleve. Это полнотекстовый поиск, который написан на Go. Проект интересный, и появилось бешеное желание получить с ним опыт работы.
Bleve может хранить данные в разных embedded БД:
- BoltDB (использует по умолчанию)
- LevelDB
- RocksDB
- Goleveldb
- forestdb
- Gtreap
Работать с Bleve просто:
import "github.com/blevesearch/bleve"
func main() {
    // Откроем новый индекс
    mapping := bleve.NewIndexMapping()
    index, err := bleve.New("example.bleve", mapping)
    // Положим не много данных
    err = index.Index(identifier, your_data)
    // Найдем что-нибудь
    query := bleve.NewMatchQuery("text")
    search := bleve.NewSearchRequest(query)
    searchResults, err := index.Search(search)
}Все просто и понятно. Но выглядит оно не с реального мира. Чтобы быть ближе к реальному миру, сделаем бота для Slack, который будет хранить историю.
Архитектура бота
Сервис для работы со slack;
Сервис индекс. Для хранения и поиска сообщений.
План
- Берем https://api.slack.com/methods/channels.history для работы со слаком;
- Берем Bleve для поиска и хранения истории;
- Если к нам пришло сообщение не по меншну бота?—?кладем в индекс;
- Если пришло сообщение с меншном?—?чистим и ищем по текущему каналу;
Slack
Со слаком все просто и пример по сути будет чуть сложнее, чем пример из репо
Единственное, что нам потребуется?—?два метода, чтобы проверить, адресовано ли боту сообщение и очистить его от имени бота
import "strings"
func isToMe(message string) bool {
    return strings.Contains(message, fmt.Sprintf("<@%s>", ss.me))
}
func cleanMessage(message string) string {
    return strings.Replace(message, fmt.Sprintf("<@%s> ", ss.me), "", -1)
}Bleve
Учитывая то, что я люблю использовать goleveldb как встраиваемую БД для своих проектов. В этом проекте решил использовать ее же.
Хранить в Bleve будем данные посложнее в виде:
type IndexData struct {
    ID        string `json:"id"`
    Username  string `json:"username"`
    Message   string `json:"message"`
    Channel   string `json:"channel"`
    Timestamp string `json:"timestamp"`
}Создадим индекс с Goleveldb в качестве БД:
import (
  "github.com/blevesearch/bleve"
  "github.com/blevesearch/bleve/index/store/goleveldb"
)
func createIndex() (bleve.Index, error) {
    indexName := "history.bleve"
    index, err := bleve.Open(indexName)
    if err == bleve.ErrorIndexPathDoesNotExist {
        mapping := buildMapping()
        kvStore := goleveldb.Name
        kvConfig := map[string]interface{}{
            "create_if_missing": true,
        }
        index, err = bleve.NewUsing(indexName, mapping, "upside_down", kvStore, kvConfig)
    }
    if err != nil {
        return err
    }
}и метод buildMapping, который создаст нам mapping для хранения:
func (ss *SearchService) buildMapping() *bleve.IndexMapping {
    ruFieldMapping := bleve.NewTextFieldMapping()
    ruFieldMapping.Analyzer = ru.AnalyzerName
    eventMapping := bleve.NewDocumentMapping()
    eventMapping.AddFieldMappingsAt("message", ruFieldMapping)
    mapping := bleve.NewIndexMapping()
    mapping.DefaultMapping = eventMapping
    mapping.DefaultAnalyzer = ru.AnalyzerName
    return mapping
}С поиском все чуть сложнее:
func (ss *SearchService) Search(query, channel string) (*bleve.SearchResult, error) {
    stringQuery := fmt.Sprintf("/.*%s.*/", query)
  // NewTermQuery создает Query для нахождения значений в индексе, которые строго совпадают с запросом
    ch := bleve.NewTermQuery(channel)
  // Создаем Query для совпадений фраз в индексе. Анализатор выбирается по полю. Ввод анализируется этим анализатором. Токенезированные выражения от анализа используются для посторения поисковой фразы. Результирующие документы должны совпадать с этой фразой.
    mq := bleve.NewMatchPhraseQuery(query)
  // Создаем Query для поиска значений в индексе по регулярному выражению
    rq := bleve.NewRegexpQuery(query)
  // Создаем Query для поиска документов, результаты которого удовлетворят поисковой строке.
    qsq := bleve.NewQueryStringQuery(stringQuery)
  // Создаем составную Query Результат должен удовлетворять хотя бы одной Query.
    q := bleve.NewDisjunctionQuery([]bleve.Query{ch, mq, rq, qsq})
    search := bleve.NewSearchRequest(q)
    search.Fields = []string{"username", "message", "channel", "timestamp"}
    return ss.index.Search(search)
}Соединив все вместе, мы получим бота, который сохраняет историю и может искать по ней без тяжеловесной жавы на примерах ElasticSearch, Solr.
Полный код проекта доступен на Github
Комментарии (3)
 - comerc19.07.2017 20:05- А какие есть проекты для быстрого запуска RESTFul API сервера? Типа как FeathersJS, ну или хотя бы на уровне PHP-CRUD-API 
 
           
 
SirEdvin
И правда. А еще можно прихопнуть муху без пушки.