找回密码
 骑士注册

QQ登录

微博登录

搜索
❏ 站外平台:

Linux中国开源社区 技术 查看内容

使用 Docker 和 Elasticsearch 构建一个全文搜索应用程序

作者: Patrick Triest 译者: LCTT qhwdw

| 2018-05-01 09:56   收藏: 5    

5 - 搜索

现在,Elasticsearch 中已经有了 100 本书了(大约有 230000 个段落),现在我们尝试搜索查询。

5.0 - 简单的 HTTP 查询

首先,我们使用 Elasticsearch 的 HTTP API 对它进行直接查询。

在你的浏览器上访问这个 URL - http://localhost:9200/library/_search?q=text:Java&pretty

在这里,我们将执行一个极简的全文搜索,在我们的图书馆的书中查找 “Java” 这个词。

你将看到类似于下面的一个 JSON 格式的响应。

{
  "took" : 11,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 13,
    "max_score" : 14.259304,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "novel",
        "_id" : "p_GwFWEBaZvLlaAUdQgV",
        "_score" : 14.259304,
        "_source" : {
          "author" : "Charles Darwin",
          "title" : "On the Origin of Species",
          "location" : 1080,
          "text" : "Java, plants of, 375."
        }
      },
      {
        "_index" : "library",
        "_type" : "novel",
        "_id" : "wfKwFWEBaZvLlaAUkjfk",
        "_score" : 10.186235,
        "_source" : {
          "author" : "Edgar Allan Poe",
          "title" : "The Works of Edgar Allan Poe",
          "location" : 827,
          "text" : "After many years spent in foreign travel, I sailed in the year 18-- , from the port of Batavia, in the rich and populous island of Java, on a voyage to the Archipelago of the Sunda islands. I went as passenger--having no other inducement than a kind of nervous restlessness which haunted me as a fiend."
        }
      },
      ...
    ]
  }
}

用 Elasticseach 的 HTTP 接口可以测试我们插入的数据是否成功,但是如果直接将这个 API 暴露给 Web 应用程序将有极大的风险。这个 API 将会暴露管理功能(比如直接添加和删除文档),最理想的情况是完全不要对外暴露它。而是写一个简单的 Node.js API 去接收来自客户端的请求,然后(在我们的本地网络中)生成一个正确的查询发送给 Elasticsearch。

5.1 - 查询脚本

我们现在尝试从我们写的 Node.js 脚本中查询 Elasticsearch。

创建一个新文件,server/search.js

const { client, index, type } = require('./connection')

module.exports = {
  /** Query ES index for the provided term */
  queryTerm (term, offset = 0) {
    const body = {
      from: offset,
      query: { match: {
        text: {
          query: term,
          operator: 'and',
          fuzziness: 'auto'
        } } },
      highlight: { fields: { text: {} } }
    }

    return client.search({ index, type, body })
  }
}

我们的搜索模块定义一个简单的 search 函数,它将使用输入的词 match 查询。

这是查询的字段分解 -

  • from - 允许我们分页查询结果。默认每个查询返回 10 个结果,因此,指定 from: 10 将允许我们取回 10-20 的结果。
  • query - 这里我们指定要查询的词。
  • operator - 我们可以修改搜索行为;在本案例中,我们使用 and 操作去对查询中包含所有字元(要查询的词)的结果来确定优先顺序。
  • fuzziness - 对拼写错误的容错调整,auto 的默认为 fuzziness: 2。模糊值越高,结果越需要更多校正。比如,fuzziness: 1 将允许以 Patricc 为关键字的查询中返回与 Patrick 匹配的结果。
  • highlights - 为结果返回一个额外的字段,这个字段包含 HTML,以显示精确的文本字集和查询中匹配的关键词。

你可以去浏览 Elastic Full-Text Query DSL,学习如何随意调整这些参数,以进一步自定义搜索查询。

6 - API

为了能够从前端应用程序中访问我们的搜索功能,我们来写一个快速的 HTTP API。

6.0 - API 服务器

用以下的内容替换现有的 server/app.js 文件。

const Koa = require('koa')
const Router = require('koa-router')
const joi = require('joi')
const validate = require('koa-joi-validate')
const search = require('./search')

const app = new Koa()
const router = new Router()

// Log each request to the console
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}`)
})

// Log percolated errors to the console
app.on('error', err => {
  console.error('Server Error', err)
})

// Set permissive CORS header
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', '*')
  return next()
})

// ADD ENDPOINTS HERE

const port = process.env.PORT || 3000

app
  .use(router.routes())
  .use(router.allowedMethods())
  .listen(port, err => {
    if (err) throw err
    console.log(`App Listening on Port ${port}`)
  })

这些代码将为 Koa.js Node API 服务器导入服务器依赖,设置简单的日志,以及错误处理。

6.1 - 使用查询连接端点

接下来,我们将在服务器上添加一个端点,以便于发布我们的 Elasticsearch 查询功能。

在  server/app.js 文件的 // ADD ENDPOINTS HERE  下面插入下列的代码。

/**
 * GET /search
 * Search for a term in the library
 */
router.get('/search', async (ctx, next) => {
    const { term, offset } = ctx.request.query
    ctx.body = await search.queryTerm(term, offset)
  }
)

使用 docker-compose up -d --build 重启动应用程序。之后在你的浏览器中尝试调用这个搜索端点。比如,http://localhost:3000/search?term=java 这个请求将搜索整个图书馆中提到 “Java” 的内容。

结果与前面直接调用 Elasticsearch HTTP 界面的结果非常类似。

{
    "took": 242,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 93,
        "max_score": 13.356944,
        "hits": [{
            "_index": "library",
            "_type": "novel",
            "_id": "eHYHJmEBpQg9B4622421",
            "_score": 13.356944,
            "_source": {
                "author": "Charles Darwin",
                "title": "On the Origin of Species",
                "location": 1080,
                "text": "Java, plants of, 375."
            },
            "highlight": {
                "text": ["<em>Java</em>, plants of, 375."]
            }
        }, {
            "_index": "library",
            "_type": "novel",
            "_id": "2HUHJmEBpQg9B462xdNg",
            "_score": 9.030668,
            "_source": {
                "author": "Unknown Author",
                "title": "The King James Bible",
                "location": 186,
                "text": "10:4 And the sons of Javan; Elishah, and Tarshish, Kittim, and Dodanim."
            },
            "highlight": {
                "text": ["10:4 And the sons of <em>Javan</em>; Elishah, and Tarshish, Kittim, and Dodanim."]
            }
        }
        ...
      ]
   }
}

6.2 - 输入校验

这个端点现在还很脆弱 —— 我们没有对请求参数做任何的校验,因此,如果是无效的或者错误的值将使服务器出错。

我们将添加一些使用 Joi 和 Koa-Joi-Validate 库的中间件,以对输入做校验。

/**
 * GET /search
 * Search for a term in the library
 * Query Params -
 * term: string under 60 characters
 * offset: positive integer
 */
router.get('/search',
  validate({
    query: {
      term: joi.string().max(60).required(),
      offset: joi.number().integer().min(0).default(0)
    }
  }),
  async (ctx, next) => {
    const { term, offset } = ctx.request.query
    ctx.body = await search.queryTerm(term, offset)
  }
)

现在,重启服务器,如果你使用一个没有搜索关键字的请求(http://localhost:3000/search),你将返回一个带相关消息的 HTTP 400 错误,比如像 Invalid URL Query - child "term" fails because ["term" is required]

如果想从 Node 应用程序中查看实时日志,你可以运行 docker-compose logs -f api

查看其它分页:

最新评论

我也要发表评论

返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。