» Node.js:使用ElasticSearch构建全文检索API » 2. 索引文档 » 2.3 发送 Index 请求

发送 Index 请求

让我们添加索引图书的 API,然后放入一些测试数据供后续使用。

移动 domain/modelsrc/domain/model

创建 src/domain/gateway/book_manager.ts:

import { Book } from "../model";

export interface BookManager {
  indexBook(b: Book): Promise<string>;
}

此处也是在使用4层架构。项目越大,该架构的好处越明显。阅读更多

添加 src/domain/gateway/index.ts:

export { BookManager } from "./book_manager";

创建 src/infrastructure/search/es.ts:

import { Client } from "@elastic/elasticsearch";

import { Book } from "../../domain/model";
import { BookManager } from "../../domain/gateway";

const INDEX_BOOK = "book_idx";

export class ElasticSearchEngine implements BookManager {
  private client: Client;
  private page_size: number;

  constructor(address: string, page_size: number) {
    this.page_size = page_size;
    this.client = new Client({ node: address });
  }

  async indexBook(b: Book): Promise<string> {
    const result = await this.client.index({
      index: INDEX_BOOK,
      document: b,
    });
    return result._id;
  }
}

默认情况下,Elasticsearch 允许你将文档索引到尚不存在的索引中。 当你将文档索引到不存在的索引时,Elasticsearch 将会使用默认设置动态地创建索引。这在开发者不想显式地创建索引时会很方便。

创建 src/infrastructure/config/config.ts:

import { readFileSync } from "fs";

interface SearchConfig {
  address: string;
}

interface ApplicationConfig {
  port: number;
  page_size: number;
}

export interface Config {
  app: ApplicationConfig;
  search: SearchConfig;
}

export function parseConfig(filename: string): Config {
  return JSON.parse(readFileSync(filename, "utf-8"));
}

添加 src/infrastructure/config/index.ts:

export { Config, parseConfig } from "./config";

创建 config.json:

{
  "app": {
    "port": 3000,
    "page_size": 10
  },
  "search": {
    "address": "http://localhost:9200"
  }
}

警醒:
不要直接 git 提交 config.json。可能会导致敏感数据泄露。如果非要提交的话,建议只提交配置格式模板。
比如:

{
 "app": {
   "port": 3000,
   "page_size": 10
 },
 "search": {
   "address": ""
 }
}

创建 src/application/executor/book_operator.ts:

import { BookManager } from "../../domain/gateway";
import { Book } from "../../domain/model";

export class BookOperator {
  private bookManager: BookManager;

  constructor(b: BookManager) {
    this.bookManager = b;
  }

  async createBook(b: Book): Promise<string> {
    return await this.bookManager.indexBook(b);
  }
}

记得给所有相关子目录创建 index.ts 文件。

创建 src/application/wire_helper.ts:

import { Config } from "../infrastructure/config";
import { BookManager } from "../domain/gateway";
import { ElasticSearchEngine } from "../infrastructure/search";

// WireHelper is the helper for dependency injection
export class WireHelper {
  private engine: ElasticSearchEngine;

  constructor(c: Config) {
    this.engine = new ElasticSearchEngine(c.search.address, c.app.page_size);
  }

  bookManager(): BookManager {
    return this.engine;
  }
}

创建 src/adapter/router.ts:

import express, { Request, Response } from "express";

import { Book } from "../domain/model";
import { BookOperator } from "../application/executor";
import { WireHelper } from "../application";

class RestHandler {
  private bookOperator: BookOperator;

  constructor(bookOperator: BookOperator) {
    this.bookOperator = bookOperator;
  }

  // Create a new book
  public async createBook(req: Request, res: Response): Promise<void> {
    try {
      const bookID = await this.bookOperator.createBook(req.body as Book);
      res.status(201).json({ id: bookID });
    } catch (err) {
      console.error(`Failed to create: ${err}`);
      res.status(404).json({ error: "Failed to create" });
    }
  }
}

// Create router
function MakeRouter(wireHelper: WireHelper): express.Router {
  const restHandler = new RestHandler(
    new BookOperator(wireHelper.bookManager())
  );

  const router = express.Router();
  router.get("/", (req: Request, res: Response) => {
    res.json({ status: "ok" });
  });
  router.post("/books", restHandler.createBook.bind(restHandler));
  return router;
}

export function InitApp(wireHelper: WireHelper): express.Express {
  const app = express();

  // Middleware to parse JSON bodies
  app.use(express.json());

  const r = MakeRouter(wireHelper);
  app.use("", r);
  return app;
}

用以下代码替换 main.ts 中内容:

import { WireHelper } from "./application";
import { InitApp } from "./adapter";
import { parseConfig } from "./infrastructure/config";

const CONFIG_FILENAME = "config.json";

const c = parseConfig(CONFIG_FILENAME);
const wireHelper = new WireHelper(c);
const app = InitApp(wireHelper);

app.listen(c.app.port, () => {
  console.log(`Running on port ${c.app.port}`);
});

再次运行 server,然后使用 curl 进行测试:

npm run dev

样例请求:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Da Vinci Code","author":"Dan Brown","published_at":"2003-03-18","content":"In the Louvre, a curator is found dead. Next to his body, an enigmatic message. It is the beginning of a race to discover the truth about the Holy Grail."}' \
  http://localhost:3000/books

样例响应:

{"id":"1jxTDo8BWkofwNB1TRnC"}

放入测试数据

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"Harry Potter and the Philosopher\u0027s Stone","author":"J.K. Rowling","published_at":"1997-06-26","content":"A young boy discovers he is a wizard and begins his education at Hogwarts School of Witchcraft and Wizardry, where he uncovers the mystery of the Philosopher‘s Stone."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"To Kill a Mockingbird","author":"Harper Lee","published_at":"1960-07-11","content":"Set in the American South during the Great Depression, the novel explores themes of racial injustice and moral growth through the eyes of young Scout Finch."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Lord of the Rings","author":"J.R.R. Tolkien","published_at":"1954-07-29","content":"A hobbit named Frodo Baggins embarks on a perilous journey to destroy a powerful ring and save Middle-earth from the Dark Lord Sauron."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Catcher in the Rye","author":"J.D. Salinger","published_at":"1951-07-16","content":"Holden Caulfield narrates his experiences in New York City after being expelled from prep school, grappling with themes of alienation, identity, and innocence."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Alchemist","author":"Paulo Coelho","published_at":"1988-01-01","content":"Santiago, a shepherd boy, travels from Spain to Egypt in search of a treasure buried near the Pyramids. Along the way, he learns about the importance of following one‘s dreams."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"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

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"1984","author":"George Orwell","published_at":"1949-06-08","content":"Winston Smith lives in a totalitarian society ruled by the Party led by Big Brother. He rebels against the oppressive regime but ultimately succumbs to its control."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Girl with the Dragon Tattoo","author":"Stieg Larsson","published_at":"2005-08-01","content":"Journalist Mikael Blomkvist and hacker Lisbeth Salander investigate the disappearance of a young woman from a wealthy family, uncovering dark secrets and corruption."}' \
  http://localhost:3000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"Gone Girl","author":"Gillian Flynn","published_at":"2012-06-05","content":"On their fifth wedding anniversary, Nick Dunne‘s wife, Amy, disappears. As the media circus ensues and suspicions mount, Nick finds himself in a whirlwind of deception and betrayal."}' \
  http://localhost:3000/books