发送 Search 请求
添加配置项,src/infrastructure/config/mod.rs:
@@ -16,6 +16,7 @@ pub struct SearchConfig {
#[derive(Debug, Deserialize, Serialize)]
pub struct ApplicationConfig {
pub port: i32,
+ pub page_size: u32,
}
pub fn parse_config(file_name: &str) -> Config {
更新 src/domain/gateway/book_manager.rs:
@@ -7,4 +7,5 @@ use crate::domain::model;
#[async_trait]
pub trait BookManager: Send + Sync {
async fn index_book(&self, b: &model::Book) -> Result<String, Box<dyn Error>>;
+ async fn search_books(&self, q: &str) -> Result<Vec<model::Book>, Box<dyn Error>>;
}
更新 src/infrastructure/search/es.rs:
@@ -2,8 +2,8 @@ use std::error::Error;
use async_trait::async_trait;
use elasticsearch::http::transport::Transport;
-use elasticsearch::{Elasticsearch, IndexParts};
-use serde_json::Value;
+use elasticsearch::{Elasticsearch, IndexParts, SearchParts};
+use serde_json::{json, Value};
use crate::domain::gateway::BookManager;
use crate::domain::model;
@@ -12,13 +12,14 @@ const INDEX_BOOK: &str = "book_idx";
pub struct ElasticSearchEngine {
client: Elasticsearch,
+ page_size: u32,
}
impl ElasticSearchEngine {
- pub fn new(address: &str) -> Result<Self, Box<dyn Error>> {
+ pub fn new(address: &str, page_size: u32) -> Result<Self, Box<dyn Error>> {
let transport = Transport::single_node(address)?;
let client = Elasticsearch::new(transport);
- Ok(ElasticSearchEngine { client })
+ Ok(ElasticSearchEngine { client, page_size })
}
}
@@ -34,4 +35,30 @@ impl BookManager for ElasticSearchEngine {
let response_body = response.json::<Value>().await?;
Ok(response_body["_id"].as_str().unwrap().into())
}
+
+ async fn search_books(&self, q: &str) -> Result<Vec<model::Book>, Box<dyn Error>> {
+ let response = self
+ .client
+ .search(SearchParts::Index(&[INDEX_BOOK]))
+ .from(0)
+ .size(self.page_size as i64)
+ .body(json!({
+ "query": {
+ "multi_match": {
+ "query": q,
+ "fields": vec!["title", "author", "content"],
+ }
+ }
+ }))
+ .send()
+ .await?;
+ let response_body = response.json::<Value>().await?;
+ let mut books: Vec<model::Book> = vec![];
+ for hit in response_body["hits"]["hits"].as_array().unwrap() {
+ let source = hit["_source"].clone();
+ let book: model::Book = serde_json::from_value(source).unwrap();
+ books.push(book);
+ }
+ Ok(books)
+ }
}
此处我们使用 multi_match
来进行多字段查询:“title”,“author”和“content”。
更新 src/application/executor/book_operator.rs:
@@ -16,4 +16,8 @@ impl BookOperator {
pub async fn create_book(&self, b: model::Book) -> Result<String, Box<dyn Error>> {
Ok(self.book_manager.index_book(&b).await?)
}
+
+ pub async fn search_books(&self, q: &str) -> Result<Vec<model::Book>, Box<dyn Error>> {
+ Ok(self.book_manager.search_books(q).await?)
+ }
}
更新 src/adapter/router.rs:
@@ -1,10 +1,11 @@
use axum::{
- extract::{Json, State},
+ extract::{Json, Query, State},
http::StatusCode,
response::IntoResponse,
routing::{get, post},
Router,
};
+use serde::Deserialize;
use std::sync::Arc;
use serde_json::json;
@@ -13,6 +14,11 @@ use crate::application;
use crate::application::executor;
use crate::domain::model;
+#[derive(Deserialize)]
+struct QueryParams {
+ q: String,
+}
+
pub struct RestHandler {
book_operator: executor::BookOperator,
}
@@ -27,6 +33,16 @@ async fn create_book(
}
}
+async fn search_books(
+ State(rest_handler): State<Arc<RestHandler>>,
+ Query(params): Query<QueryParams>,
+) -> Result<Json<Vec<model::Book>>, impl IntoResponse> {
+ match rest_handler.book_operator.search_books(¶ms.q).await {
+ Ok(books) => Ok(Json(books)),
+ Err(err) => Err((StatusCode::INTERNAL_SERVER_ERROR, err.to_string())),
+ }
+}
+
async fn welcome() -> Json<serde_json::Value> {
Json(json!({
"status": "ok"
@@ -40,5 +56,6 @@ pub fn make_router(wire_helper: &application::WireHelper) -> Router {
Router::new()
.route("/", get(welcome))
.route("/books", post(create_book))
+ .route("/books", get(search_books))
.with_state(rest_handler)
}
重启后再次使用 curl
测试:
curl 'http://localhost:3000/books?q=katniss+hunger'
样例响应:
[
{
"title": "The Hunger Games",
"author": "Suzanne Collins",
"published_at": "2008-09-14",
"content": "In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."
}
]
http://localhost:3000/books?q=new%20york%20circus%20girl
在 URL 编码中,
%20
和+
都可以表示空格,但是它们的使用场景稍有区别。
赞!你刚刚完成了一个全文检索。
注意:
全文检索需要语言分词器。默认的标准分词器对中、日、韩文等语言处理效果不理想。 可以使用语言特定的分词器以获取更佳效果。