предисловие
В последнее время возникла необходимость во внедрении набора API-интерфейсов среднего уровня на основе внешнего стека технологий для обработки набора API-интерфейсов, поддерживаемых внешним интерфейсом.JSON
конфигурационный файл.
После недолгих поисков я наконец выбралnest.js
Эта структура, благодаря поддержке АОП-программирования, совместима сSpringBoot
записывается аналогичным образом и может быть записан какSpringBoot
Применяется тот набор архитектурных идей, что очень дружелюбно ко мне как штатному инженеру (немного знающему Java) 😁
После 3-х дней изучения и метания, я наконец построил набор архитектуры, которой я вполне доволен.В этой статье я поделюсь с вами своим архитектурным планом.Заинтересованные разработчики могут прочитать эту статью.
написать впереди
Содержание этой статьи будет включатьTypeScript
, если вы не понимаете этого достаточно, пожалуйста, сделайте первый шаг:Документация TypeScript на китайском языкеУчись, заходи в дверь 🤓.
-
Полный код проекта этой статьи перемещается:nest-project
-
Для зависимостей, установленных в этой статье, требуется, чтобы версия вашего узла была
14.16.0
и выше.
Вы можете использовать контроллер управления версией узла
n
Чтобы управлять вашим узлом версии, вы можете использоватьnpm install -g n
установить его.После установки вы можете просто использовать
n 版本号
Вы можете установить и переключиться на соответствующую версию node. Возможно, потребуется использовать под macOSsudo n 版本号
. Например:n 14.16.0
.Связанный
n
Чтобы узнать больше о способах использования, перейдите по ссылке:n-github
Строительство окружающей среды
На официальном сайте гнезда он предоставляетТри способа построения:
- Установить с помощью интерфейса командной строки
- Установить с помощью Git
- Создать вручную
Эти три метода установки относительно просты, и заинтересованные разработчики могут обратиться к документации для изучения. Чтобы реализовать практические возможности каждого, в этой статье не используется описанный выше метод для сборки проекта.Мы будем использовать пряжу для инициализации пустого проекта с 0, а затем установим соответствующие зависимости гнезда.
Примечание. Если вы уже настроили среду, пропустите эту главу и перейдите к следующей главе:Архитектура проекта.
Инициализировать пустой проект
В этой статье для инициализации проекта используется пряжа. Если он у вас не установлен, вам нужно сначала установить его с помощью npm. Команда выглядит следующим образом:
npm install --global yarn
После установки вы можете использовать команду:yarn --version
чтобы убедиться, что установка прошла успешно, в случае успеха вы увидите вывод, подобный следующему:
Далее мы создаем файл с именемnest-project
пустая папка, войдите в эту папку в терминале, используйте команду:yarn init
Чтобы инициализировать проект, как показано ниже, вы можете заполнить его в соответствии с вашими потребностями.Часть со скобками можно оставить незаполненной и оставить по умолчанию, просто нажмите Enter.
Затем открываем проект, в папке всего один файл package.json, содержимое следующее:
{
"name": "nest-project",
"version": "1.0.0",
"main": "index.js", // 这个可以删除,不需要这个字段
"author": "likai",
"license": "MIT",
"private": true
}
Приведенный выше контент — это то, что мы только что выбрали в терминале, поэтому вы также можете сами создать пустой файл, создать этот файл json и написать соответствующую конфигурацию для достижения того же результата.
Установить вложенные зависимости
Мы открываем только что созданный файл package.json и добавляем поля, показанные ниже:
{
"dependencies": {
"@nestjs/common": "^8.1.1",
"@nestjs/core": "^8.1.1",
"@nestjs/platform-express": "^8.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.4.0"
},
"devDependencies": {
"@nestjs/cli": "^8.1.3",
"@nestjs/schematics": "^8.0.4",
"@types/express": "^4.17.13",
"@types/node": "^16.11.1",
"supertest": "^6.1.6",
"ts-loader": "^9.2.6",
"ts-node": "^10.3.0",
"tsconfig-paths": "^3.11.0",
"tslib": "^2.3.0",
"typescript": "^4.4.4",
"webpack": "5.0.0"
}
}
Затем мы открываем терминал, входим в каталог проекта и выполняемyarn install
команда, интерфейс после успеха выглядит следующим образом:
Установить зависимости спецификации кода
В этой статье для стандартизации кода используются eslint и prettier.Для разработчиков, которые этого не знают, перейдите к другой моей статье:Самостоятельно использовать ESLint+Prettier для форматирования кода.
Далее открываем ранее созданныйpackage.json
файл, вdevDependencies
Добавьте в объект следующий код:
{
"devDependencies": {
"eslint": "^7.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
}
}
После добавления выполнитьyarn install
Внедрение пакета зависимостей завершено.
Добавить команду запуска
После установки всех зависимостей мы добавляем 6 запущенных скриптов в package.json для запуска проекта и создания пакета, как показано ниже:
- prebuild удаляет каталог dist
- собрать упакованный проект
- начать запускать проект
- start:dev запустить проект (поддерживается горячее обновление)
- start:debug запускает проект в режиме отладчика (поддерживает отладку по точкам останова)
- start:prod запустить упакованный проект
{
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main"
}
}
Добавить файл конфигурации
Затем нам также нужно добавить файлы конфигурации, такие как гнездо, eslint и prettier в корневой каталог проекта, как показано ниже:
- .editorconfigУнифицируйте файлы конфигурации для решения проблем, связанных с форматом кода, между различными операционными системами.
- .eslintrc.jsконфигурационный файл eslint
- .gitignoreФайлы, которые нужно игнорировать при фиксации git
- .prettierrc.jsonболее красивый файл конфигурации
- nest-cli.jsonконфигурационный файл гнезда
- tsconfig.jsonфайл конфигурации машинописного текста
- tsconfig.build.jsonСвязанные файлы конфигурации обработки для файлов ts при упаковке проекта
Для содержимого определенного файла щелкните синий шрифт выше, чтобы перейти непосредственно к соответствующему файлу в GitHub.
Архитектура проекта
В этой главе мы поделимся с вами архитектурой моего проекта, сначала создайте ее в корневом каталоге проекта.src
Папка, в которой будет храниться весь код проекта.
Полный код проекта, соответствующий этой главе, перемещен:nest-project
слой управления
Этот уровень используется для обработки входящих запросов от клиентов и возврата ответов клиентам.Все сопоставления запросов реализованы на этом уровне. Каждый запрос соответствует контроллеру, а контроллер может иметь несколько подметодов для обработки различных операций одного и того же типа.
Например
Далее создаем в каталоге srccontroller
папку, создайте новую в ее каталогеAppController.ts
документ.
Начнем с примера:
- иметь дело с
/home/setTitle
изpost
запрос, его параметры находятся в теле http - иметь дело с
/home/getTitle
изget
запрос, его параметры находятся в URL запроса
код реализации
Прочитав официальную документацию, мы можем написать следующий код:
import { Body, Controller, Get, Query, Post } from "@nestjs/common";
@Controller("home")
export class AppController {
@Post("setTitle")
setTitle(@Body() data: { id: number; title: string }): {
code: number;
data: null | string;
msg: string;
} {
// 客户端传入的数据
console.log(data);
// 返回给客户端的数据
return { code: 0, data: null, msg: "post方法调用成功" };
}
@Get("getTitle")
getTitle(@Query("id") id: number): {
code: number;
data: string;
msg: string;
} {
console.log("客户端传入的数据", id);
return { code: 0, data: null, msg: "get方法调用成功" };
}
}
Давайте посмотрим на роль каждого декоратора в приведенном выше коде:
-
@Controller
Используется для идентификации этого файла как контроллера, он принимает параметр, здесь я написал домой, который представляет все/home
запросы будут идти сюда. -
@Post
Он используется для обработки запросов в формате post.Также принимает параметр.Здесь я написал setTitle, который представляет/home/setTitle
Почтовый запрос будет здесь. -
@Body
Используется для получения данных в теле http -
@Query
Используется для получения данных в URL-адресе запроса
В документации Nest есть еще много декораторов, которые он предоставляет, которые могут справиться с различными сценариями разработки.Контроллер - запрос.
сервисный уровень
Сервисный уровень используется для обработки конкретной бизнес-логики, когда мы получаем запрос от клиента, мы извлекаем параметры и пишем конкретный бизнес-код.
Например
Далее создаем в каталоге srcservice
папку, создайте новую в ее каталогеAppService.ts
документ.
Например:
- Напишите метод, чтобы сделать что-то в соответствии с идентификатором и вернуть результат операции после ее выполнения.
код реализации
Изучив документацию, мы знаем, что нам нужно использовать@Injectable()
Чтобы украсить этот класс, код выглядит так:
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
public setTitle(id: string): {
code: number;
data: null | string;
msg: string;
} {
// 根据id做一些事情,此处省略
console.log(id);
// 返回操作结果
return { code: 0, data: null, msg: "设置成功" };
}
}
После выполнения вышеуказанных операций нам также необходимо изменитьAppController
,существуетconstructor
Представьте сервис, который мы только что создали, часть кода выглядит следующим образом:
export class TextAttributeController {
constructor(private readonly appService: AppService) {}
@Post("setTitle")
setTitle(){
// 此处省略了较多代码,这里的重点是演示如何调用我们刚才写好的方法
return this.appService.setTitle();
}
}
В сервисном классе много методов, мы установим соответствующие методы обработки в соответствии с отображением уровня управления, чтобы уровень управления мог быть более внимательным к своим задачам и улучшить читаемость кода.
интерфейсный слой
Этот уровень используется для объявления того, какие методы находятся в каждом классе службы, что может значительно улучшить читабельность кода. Без этого слоя, когда методов в сервисе становится все больше и больше, код будет очень длинным, и быстро найти метод станет очень трудоемко.
Например
Затем мы создаем его в каталоге srcinterface
папку, создайте новую в ее каталогеAppInterface.ts
документ.
Например, нам нужно объявить 5 методов следующим образом:
- getTitle
- getName
- getAge
- setName
- setTitle
код реализации
Использование в TypeScriptinterface
ключевое слово для объявления интерфейса, то приведенный выше пример преобразуется в код следующим образом:
export interface AppInterface {
getTitle(): string;
getName(): string;
getAge(): string;
setName(): string;
setTitle(): string;
}
После выполнения вышеуказанных операций нам также необходимо изменить код нижнего сервисного уровня для реализации этого интерфейса.Часть кода выглядит следующим образом:
@Injectable()
export class AppService implements AppInterface {
getAge(): string {
return "";
}
getName(): string {
return "";
}
// 其他方法省略
}
В TypeScript мы используем
implements
ключевое слово для реализации интерфейса.
Модульный слой
Этот слой используется@Module()
Класс для декораторов, предоставляющий метаданные, которые Nest использует для организации структуры приложения. Когда у нас появились уровень управления и сервисный уровень, они перестали работать, потому что им не хватало организации.
код реализации
Далее создаем в каталоге srcmodule
папка, созданная в ее каталогеAppModule.ts
файл, код такой:
- controllers — это тип данных массива, здесь мы можем ввести контроллеры слоя контроллера один за другим.
- Поставщики также является массивом типа данных, мы можем представить услуги сервисного слоя один за другим здесь.
import { Module } from "@nestjs/common";
import { AppController } from "../controller/AppController";
import { AppService } from "../service/AppService";
@Module({
imports: [],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
Для подробного ознакомления с контроллерами и провайдерами перейдите по ссылке:Nest-@module
Файл записи конфигурации
Далее в каталоге src создаемmain.ts
файл, его код выглядит так:
- Импортируйте AppModule и используйте NestFactory для создания экземпляров.
- Установите порт 3000 в качестве прослушивателя для этого проекта.
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./module/AppModule";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Наконец, мы запускаем package.jsonstart:dev
команда, доступная в браузереhttp://127.0.0.1:3000
для доступа к элементу.
Проверить контроллер, созданный уровнем управления
Далее, давайте проверим, что предыдущийAppController.ts
Могут ли нормально работать два метода, написанные в.
Проверка метода Get
Давайте сначала проверим доступ к запросу на получение и получим доступ к нему в браузере.http://127.0.0.1:3000/home/getTitle?id=12
, интерфейс клиента выглядит следующим образом:
Сервер также выводит идентификатор, переданный клиентом, в адресной строке, как показано ниже:
Подтвердить метод публикации
Нам нужно использовать postman, чтобы проверить, можно ли нормально получить доступ к методу post.Предполагая, что вы установили postman, мы создаем новый запрос и пишем по адресуhttp://127.0.0.1:3000/home/setTitle
, результат доступа следующий:
Точно так же сервер также получит данные json, которые мы передали в теле http, как показано ниже:
Слой DTO (обработка параметров клиента)
В предыдущем примере параметры, которые мы получаем от клиента, напрямую прописываются в параметрах каждого метода в контроллере, проблемы, вызванные этим:
- Это снизит читабельность кода, а писать большое количество параметров в методе очень неэлегантно.
- Когда многие методы должны передавать одни и те же параметры, необходимо написать много повторяющегося кода, что значительно снижает удобство сопровождения.
- Валидацию параметров нужно прописать в методе в контроллере, что будет генерировать избыточный код.
Роль слоя DTO заключается в решении вышеуказанных проблем, мы используемclass
для обработки параметров, переданных клиентом.
код реализации
Мы создаем папку DTO в каталоге src и создаем его в своем каталогеAppDto.ts
файл, код такой:
export class AppDto {
public id: string;
public title: string;
public name: string;
}
export class GetNameDto extends AppDto {
public type: string;
}
Впоследствии мыAppController.ts
Его можно использовать в методе в , код такой:
import { AppDto, GetNameDto } from "../dto/AppDto";
@Controller("home")
export class AppController {
@Post("setTitle")
setTitle(@Body() data: AppDto): {
code: number;
data: null | string;
msg: string;
} {
// 其他代码省略
}
@Get("getName")
getName(@Body() data: GetNameDto): {
code: number;
data: null | string;
msg: string;
} {
// 其他代码省略
}
}
После выполнения вышеуказанных операций мы успешно решили 1, 2 задачи. Поскольку получение параметров реализовано с помощью классов, мы можем использовать наследование, чтобы избежать избыточного кода.
Проверка правильности параметров с помощью конвейера
Далее используем пайпы для решения третьей задачи, на официальном сайте гнезда это предусмотрено8 встроенных труб из коробки, здесь нам нужно использовать егоValidationPipe
конвейер для проверки параметров.
Согласно документу перед использованием нам необходимо привязать пайплайн, официальный сайт дает два способа:
- Для привязки к контроллеру или его методу мы используем
@UsePipes()
декоратор и создайте экземпляр конвейера и передайте его на проверку Joi. - Сделайте его глобальным конвейером на входе для каждого процессора маршрутов в приложении.
Здесь мы используем конвейер глобальной области видимости и модифицируем файл main.ts, код выглядит следующим образом:
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./module/AppModule";
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Чтобы узнать о конкретном принципе конвейера, перейдите по ссылке:вложенный трубопровод
Затем мы можем использовать связанный с ним декоратор на уровне dto для проверки параметров,AppDto.ts
Часть кода выглядит так:
import { IsString, MinLength } from "class-validator";
export class AppDto {
@MinLength(5)
@IsString()
public id!: string;
@IsString()
public title!: string;
@IsString()
public name!: string;
}
export class GetNameDto extends AppDto {
@IsString()
public type!: string;
}
Наконец, мы используем postman, чтобы проверить, работает ли он, следующим образом:
- Идентификатор типа передается в
- Не передан параметр имени
Сервер вернул ошибку 400 и сообщил причину ошибки.
Поскольку мы передаем ненулевую проверку параметра декоратору, в классе dto нам нужно использовать
!:
оператор, чтобы утверждать, что параметр должен иметь значение.мы начинаем с
class-validator'
В пакете представлен декоратор проверки строкового типа, который также может проверять другие типы. Заинтересованные разработчики, пожалуйста, перейдите по адресу:class-validator#usage
Слой VO (представление, возвращаемое клиенту)
Обычно поля, которые мы возвращаем клиенту, являются фиксированными.На уровне контроллера ранее в этой статье мы возвращали оба методаcode
,data
,msg
Эти три поля, только данные разные. Затем мы должны инкапсулировать его и передавать данные в качестве параметров, что значительно повышает удобство сопровождения кода, что мы и называем слоем VO.
пакетный инструментальный класс
Мы создаем его в каталоге srcVO
папка, созданная в ее каталогеResultVO.ts
файл, код такой:
- Просто создайте класс и добавьте три поля
- Напишите методы get и set для каждого поля
export class ResultVO<T> {
private code!: number;
private msg!: string;
private data!: T | null;
public getCode(): number {
return this.code;
}
public setCode(value: number): void {
this.code = value;
}
public getMsg(): string {
return this.msg;
}
public setMsg(value: string): void {
this.msg = value;
}
public getData(): T | null {
return this.data;
}
public setData(value: T | null): void {
this.data = value;
}
}
Затем мы создаем папку utils в каталоге src и создаем ее в своем каталоге.VOUtils.ts
файл, который инкапсулирует общие методы, которые удобно вызывать другим слоям напрямую Код выглядит следующим образом:
- мы инкапсулировали
success
а такжеerror
метод - В случае успеха передать данные
- В случае сбоя передайте код и сообщение, чтобы сообщить клиенту о причине ошибки.
// 返回给调用者的视图结构
import { ResultVO } from "../VO/ResultVO";
export class VOUtils {
public static success<T>(data?: T): ResultVO<T> {
const resultVo = new ResultVO<T>();
resultVo.setCode(0);
resultVo.setMsg("接口调用成功");
resultVo.setData(data || null);
return resultVo;
}
public static error(code: number, msg: string): ResultVO<null> {
const resultVo = new ResultVO<null>();
resultVo.setCode(code);
resultVo.setMsg(msg);
return resultVo;
}
}
Уведомление:
success
Метод поддерживает входящие параметры любого типа.В реальных бизнес-требованиях уровень данных будет очень сложным.Когда вы фактически используете его, вы можете создать класс vo, соответствующий бизнесу в соответствии с вашими конкретными бизнес-требованиями, а затем создать его экземпляр. , присваивая значение каждому полю. Наконец, вы можете передать объект, который вы создали при вызове метода успеха.
использовать в бизнес-коде
Тогда мы можемservice
слой для использования созданного нами класса инструмента, пример кода выглядит следующим образом:
import { VOUtils } from "../utils/VOUtils";
@Injectable()
export class AppService implements AppInterface {
// 其它代码省略
setTitle(): VOUtils {
return VOUtils.success("标题设置成功");
}
}
Результат вызова интерфейса следующий:
тип слоя
Когда мы пишем бизнес-код, мы сталкиваемся со многимиObject
Тип данных, обычно мы определяем определенный тип для каждого поля. В настоящее время нам нужно объединить все типы для удобства обслуживания. Здесь мой подход заключается в создании папки типов в каталоге src и размещении всех определений типов. помещается в эту папку, а код выглядит так:
- Создал папку типов
- созданный в папке типа
AppDataType.ts
файл для хранения всех типов
export type book = {
title: string;
author: string;
time: string;
updateTime: string;
};
export interface specialBook extends book {
id: number;
createTime: string;
}
Примечание. Все определения типов определяются с помощью ключевого слова type, которое можно напрямую импортировать при использовании.Когда мы хотим наследовать тип, мы должны использовать ключевое слово interface.
слой перечисления
Когда мы пишем бизнес-код, мы обязательно столкнемся с различными аномальными состояниями.При возникновении исключения на стороне сервера нам необходимо вернуть информацию об ошибке и код состояния на уровне VO.Если мы напрямую записываем данные в метод, когда нам нужно изменить его позже, это будет головная боль. Затем, когда мы определяем эти данные на уровне перечисления и напрямую используем наше определенное перечисление в бизнес-коде, эта проблема легко решается.
Мы создаем его в каталоге srcenum
папка, созданная в ее папкеAppEnum.ts
файл, код такой:
- NOTFOUND указывает код ошибки
- NOTFOUND_DESCRIPTION указывает описание кода ошибки
export enum AppEnum {
NOTFOUND = -1,
NOTFOUND_DESCRIPTION = "未找到相关人物"
}
Затем мы можем использовать его в бизнес-коде, как показано ниже:
@Injectable()
export class AppService implements AppInterface {
// 其它代码省略
getName(): VOUtils {
return VOUtils.error(AppEnum.NOTFOUND, AppEnum.NOTFOUND_DESCRIPTION);
}
}
Примечание: перечисление в машинописном тексте не может установить соответствующую информацию описания, когда оно определено как Java, поэтому здесь мы можем выбрать только способ сохранения страны по кривой.Если определение неверно, определите перечисление, заканчивающееся на __DESCRIPTION.
код проекта
Для получения полного кода, используемого в этой статье, перейдите в репозиторий проекта на GitHub:nest-project
напиши в конце
На данный момент статья опубликована.
яудивительный программист, фронтенд-разработчик.
Если вы заинтересованы во мне, пожалуйста, перейдите на мойперсональный сайт,Узнать больше о.
- Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
- Эта статья была впервые опубликована вудивительный программистПубличный аккаунт, перепечатка без разрешения запрещена 💌