Исходный код:awsome-nest
существуетНачало работы сnesjs (1), немного разобрались в некоторых важных концепциях Nestjs, теперь давайте приступим к созданию приложения на основе Nestjs.
Nestjs, как и Angular, предоставляет инструменты CLI, которые помогают нам инициализировать и разрабатывать приложения.
$ npm install -g @nestjs/cli
$ nest new my-awesome-app
На этом этапе вы получите такую структуру каталогов:
бегатьnpm start
После этого зайдите в браузереhttp://localhost:3000/
ты можешь видетьHello World!
.
Контроллер и сервис
В Nestjs все контроллеры и сервисы должны быть зарегистрированы в соответствующем модуле, вот так:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
В шаблоне MVC контроллер получает данные через модель. Соответственно, в Nestjs контроллер отвечает за обработку входящих запросов, вызов соответствующей службы для завершения бизнес-обработки и возврат ответа клиенту.
Контроллер обычно можно создать с помощью команды CLI:
$ nest g co cats
В это время интерфейс командной строки автоматически сгенерирует файл контроллера и зарегистрирует контроллер в соответствующем модуле.
В отличие от некоторых других узловых сред, маршрутизация Nestjs не является централизованным управлением, а распределена по контроллеру через@controller()
(Необязательный) префикс, объявленный в, и любые маршруты, указанные в декораторе запроса.
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {
}
@Get(':id')
findOne(@Param('id') id: string): string {
return this.catsService.getCat();
}
}
В приведенном выше коде запрос выполняется через запрос Get.http://localhost:3000/cats/1
позвонюfindOne
метод.
Если вам нужно добавить префикс перед всеми запросами, вы можетеmain.ts
Установите GlobalPrefix непосредственно в:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();
В NestJS контроллер как бы вызывает командира службы, распределяет соответствующий запрос на соответствующую службу.
В контроллере мы замечаем, что в конструктор вводитсяCatsService
instance для вызова метода в соответствующем сервисе. Вот как внедряется внедрение зависимостей в Nestjs — внедрение конструктора.
Сервис можно рассматривать как слой, зажатый между контроллером и моделью.Сервис вызывает DAO (в Nestjs различные инструменты ORM или инкапсулированный сам по себе уровень DAO) для обеспечения доступа к базе данных, обработки и интеграции данных.
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
getCat(id: string): string {
return `This action returns ${id} cats`;
}
}
введите приведенный выше код@Injectable()
Служба определяется таким образом, что вы можете внедрить эту службу в другие контроллеры или службы.
Dто и труба
пройти черезНачало работы сnesjs (1)Была введена концепция DTO.В Nestjs DTO в основном определяет, как отправлять объекты данных по сети, обычно сclass-validator
иclass-transformer
Проверьте.
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
}
на лицо просьбаbody
Определен DTO, и тип параметра ограничен в DTO, еслиbody
Если переданный тип не соответствует требованиям, об ошибке будет сообщено напрямую.
в ДТОclass-validator
Он также должен взаимодействовать с трубой для завершения функции проверки:
import {
PipeTransform,
ArgumentMetadata,
BadRequestException,
Injectable,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'
import * as _ from 'lodash'
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata
if (!metatype || !this.toValidate(metatype)) {
return value
}
const object = plainToClass(metatype, value)
const errors = await validate(object)
if (errors.length > 0) {
const errorMessage = _.values(errors[0].constraints)[0]
throw new BadRequestException(errorMessage)
}
return value
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object]
return !types.find(type => metatype === type)
}
}
Этот канал создаст исходный тип на основе метаданных и экземпляра объекта, а затем передастvalidate
Проверять.
Этот канал обычно используется как глобальный канал:
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Если предположить, что у нас нет этого слоя трубы, то проверка параметров будет производиться в контроллере, что нарушит принцип единой ответственности. Этот слой конвейера помогает нам проверять параметры, что эффективно снижает сложность класса и улучшает читаемость и удобство сопровождения.
Перехватчик и фильтр исключений
Код написан здесь, и мы обнаруживаем, что строка возвращается напрямую, что слишком грубо, и нам нужно обернуть правильные и неправильные ответы. Предположим, я хочу, чтобы возвращаемый формат был примерно таким:
# 请求成功
{
status: 0,
message: '请求成功',
data: any
}
# 请求失败
{
status: 1,
message: string,
}
На данный момент вы можете использовать для этого идею АОП. Во-первых, нам нужен слой среза, который фиксирует ошибки глобально для обработки всех исключений; во-вторых, если запрос успешен, нам нужно обернуть возвращенный результат в слой среза.
В Nestjs, когда результат запроса возвращается, перехватчик срабатывает перед фильтром исключений, поэтому фильтр исключений будет последним шансом перехватить исключение. Мы используем его как слой среза для обработки глобальных ошибок.
import {
Catch,
ArgumentsHost,
HttpException,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common'
@Catch()
export class ExceptionsFilter implements ExceptionFilter {
async catch(exception, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
let message = exception.message
let isDeepestMessage = false
while (!isDeepestMessage) {
isDeepestMessage = !message.message
message = isDeepestMessage ? message : message.message
}
const errorResponse = {
message: message || '请求失败',
status: 1,
}
const status = exception instanceof HttpException ?
exception.getStatus() :
HttpStatus.INTERNAL_SERVER_ERROR
response.status(status)
response.header('Content-Type', 'application/json; charset=utf-8')
response.send(errorResponse)
}
}
Перехватчик отвечает за упаковку успешного результата запроса:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
interface Response<T> {
data: T
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map(rawData => {
return {
data: rawData,
status: 0,
message: '请求成功',
}
}
)
)
}
}
Также в глобальной области необходимо определить Interceptor и Exception Filter:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.useGlobalFilters(new ExceptionsFilter());
app.useGlobalInterceptors(new TransformInterceptor());
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
TypeORM
TypeORM эквивалентен уровню DAO в Nestjs, который поддерживает несколько баз данных, таких как PostgreSQL, SQLite и даже MongoDB (NoSQL). Здесь мы возьмем MySQL в качестве примера, сначала создадим базу данных вручную в MySQL:
> CREATE DATABASE test
Затем установите тип:
$ npm install --save @nestjs/typeorm typeorm mysql
Обычно, когда мы разрабатываем, будет несколько сред, и эти среды будут иметь разные конфигурации базы данных, поэтому сначала создайте одну.config
Папки, в которых размещаются разные конфигурации баз данных:
// index.ts
import * as _ from 'lodash'
import { resolve } from 'path'
import productionConfig from './prod.config'
const isProd = process.env.NODE_ENV === 'production'
let config = {
port: 3000,
hostName: 'localhost',
orm: {
type: 'mysql',
host: 'localhost',
port: 3310,
username: 'root',
password: '123456',
database: 'test',
entities: [resolve(`./**/*.entity.ts`)],
migrations: ['migration/*.ts'],
timezone: 'UTC',
charset: 'utf8mb4',
multipleStatements: true,
dropSchema: false,
synchronize: true,
logging: true,
},
}
if (isProd) {
config = _.merge(config, productionConfig)
}
export { config }
export default config
// prod.config.ts
import { resolve } from 'path'
export default {
port: 3210,
orm: {
type: 'mysql',
host: 'localhost',
port: 3312,
username: 'root',
password: '123456',
database: 'test',
entities: [resolve('./**/*.entity.js')],
migrations: ['migration/*.ts'],
dropSchema: false,
synchronize: false,
logging: false,
},
}
Категорически не рекомендуется открывать формы в онлайн-среде.synchronize
Функции. Если вы хотите открыть его локально, вам следует обратить внимание на один момент: если тип поля, определенный в сущности, отличается от исходного типа базы данных, при ее открытииsynchronize
После выполнения ormdrop
после этогоadd
операции, что приведет к потере данных при локальном тестировании (для удобства локальное тестированиеsynchronize
Функция включена, чтобы сущность после записи автоматически синхронизировалась с БД).
существуетapp.module.ts
импортировать вTypeOrmModule
:
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'
@Module({
imports: [
TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
Следующим шагом является запись объекта, ниже мы определяем метод с именемcat
Таблица,id
Чтобы увеличить первичный ключ:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('cat')
export class CatEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 50 })
name: string
@Column()
age: number
@Column({ length: 100, nullable: true })
breed: string
}
В это время сущность будет синхронизирована с базой данных, вtest
В базе можно увидетьcat
этот стол.
Когда модуль использует эту сущность, ее необходимо зарегистрировать в соответствующем модуле, используяforFeature()
Определение метода определяет, какие репозитории должны быть зарегистрированы в текущей области:
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'
import { CatEntity } from './cats/cat.entity'
const ENTITIES = [
CatEntity,
]
@Module({
imports: [
TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
TypeOrmModule.forFeature([...ENTITIES]),
],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
тогда вы можете использовать@InjectRepository()
модификатор кCatService
инъекцияCatRepository
:
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { CatEntity } from './cat.entity'
import { Repository } from 'typeorm'
@Injectable()
export class CatsService {
constructor(
@InjectRepository(CatEntity)
private readonly catRepository: Repository<CatEntity>,
) {
}
async getCat(id: number): Promise<CatEntity[]> {
return await this.catRepository.find({ id })
}
}
теперь запроситьhttp://localhost:3000/api/v1/cats/1
Этот API вернет следующие результаты:
{
"data": [],
"status": 0,
"message": "请求成功"
}
В typeorm, если вам нужно использовать более сложные операторы sql, вы можете использоватьcreateQueryBuilder
чтобы помочь вам построить:
this.catRepository
.createQueryBuilder('cat')
.Where('name != ""')
.andWhere('age > 2')
.getMany()
еслиcreateQueryBuilder
Не может удовлетворить ваши требования, вы можете использовать его напрямуюquery
Напишите оператор sql:
this.catRepository.query(
'select * from cat where name != ? and age > ?',
[age],
)
Migration
В проекте с непрерывной доставкой проект будет продолжать повторяться и переходить в онлайн.В это время возникнет проблема изменений базы данных.Для используемой системы миграция обычно используется, чтобы помочь нам синхронизировать базу данных. TypeORM также поставляется синструменты командной строкиПомогите нам синхронизировать базу данных.
Сначала создайте локальныйormconfig.json
документ:
{
"type": "mysql",
"host": "localhost",
"port": 3310,
"username": "root",
"password": "123456",
"database": "test",
"entities": ["./**/*.entity.ts"],
"migrations": ["migrations/*.ts"],
"cli": {
"migrationsDir": "migrations"
},
"timezone": "UTC",
"charset": "utf8mb4",
"multipleStatements": true,
"dropSchema": false,
"synchronize": false,
"logging": true
}
Этот документ определяет правило сопоставления сущностей json и миграцию файлов, расположение и миграцию файла конфигурации, размещенные в интерфейсе командной строки.
В настоящее время выполнение следующей команды автоматически сгенерирует ее в папке миграции.1563725408398-update-cat.ts
документ
$ ts-node node_modules/.bin/typeorm migration:create -n update-cat
в имени файла1563725408398
- временная метка сгенерированного файла. В этом файле будетup
иdown
Эти два метода:
import {MigrationInterface, QueryRunner} from "typeorm";
export class updateCat1563725408398 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
}
public async down(queryRunner: QueryRunner): Promise<any> {
}
}
up
Должен содержать код, необходимый для выполнения миграции.down
должен восстановить любойup
Изменять. существуетup
иdown
EстьQueryRunner
объект. Используйте этот объект для выполнения всех операций с базой данных. Например, пишем фейковые данные в таблицу cat:
import {MigrationInterface, QueryRunner} from "typeorm";
export class updateCat1563725408398 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`insert into cat (id, name, age, breed) values (2, 'test', 3, 'cat') `)
}
public async down(queryRunner: QueryRunner): Promise<any> {
}
}
В это время вpackage.json
Напишите следующий скрипт и запуститеnpm run migration:run
, то таблица cat будет иметьid
за2
поддельные данные.
{
"scripts": {
"migration:run": "ts-node node_modules/.bin/typeorm migration:run",
}
}
Обратите внимание, что этоormconfig.json
Конфигурация файла является конфигурацией локальной среды.Если вам нужно использовать его в производственной среде, вы можете переписать егоormconfig-prod.json
, затем запуститеmigration
Добавить при названии--config ormconfig-prod.json
.
Миграция сгенерированная typeorm имеет недостаток, sql и код сцеплены вместе, лучше чтобы sql был отдельным файлом, а скрипт миграции файлом, чтобы в особых случаях удобно было запускать эти sql файлы прямо в MySQL. В это время вы можете использоватьdb-migrateВместо использования typeorm для управления сценариями миграции db-migrate сгенерирует сценарий js и два файла sql в каталоге миграции.up
sql, одинdown
sql.
Для существующих проектов создать соответствующую сущность с нуля на основе базы данных очень хлопотно, в настоящее время вы можете использоватьtypeorm-model-generatorдля автоматического создания этих объектов. Например, выполните следующую команду:
$ typeorm-model-generator -h 127.0.0.1 -d arya -p 3310 -u root -x 123456 -e mysql -d test -o 'src/entities/' --noConfig true --cf param --ce pascal
В это время будетsrc/entities/
Сгенерировано нижеcat.ts
файл сущности:
import {BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGeneratedColumn,RelationId} from "typeorm";
@Entity("cat",{schema:"test", database:"test" } )
export class Cat {
@PrimaryGeneratedColumn({
type:"int",
name:"id"
})
id:number;
@Column("varchar",{
nullable:false,
length:50,
name:"name"
})
name:string;
@Column("int",{
nullable:false,
name:"age"
})
age:number;
@Column("varchar",{
nullable:true,
length:100,
name:"breed"
})
breed:string | null;
}
бревно
Официальное решение для журнала дано, но здесь мы ссылаемся наnestify,использоватьlog4jsЗаймитесь обработкой журнала. Основная причина в том, что log4js классифицировал, разделил и разместил журналы, что нам удобно для лучшего управления журналами.
Журналы разделены на девять уровней в Log4JS:
export enum LoggerLevel {
ALL = 'ALL',
MARK = 'MARK',
TRACE = 'TRACE',
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR',
FATAL = 'FATAL',
OFF = 'OFF',
}
ALL
иOFF
Эти два уровня обычно не используются напрямую в бизнес-коде. Остальные семь соответствуютLogger
Семь методов экземпляра, то есть когда эти методы вызываются, это эквивалентно рейтингу этих журналов.
Для разных уровней журнала выведите в log4js разные цвета и выведите время вывода журнала и соответствующее имя модуля:
Log4js.addLayout('Awesome-nest', (logConfig: any) => {
return (logEvent: Log4js.LoggingEvent): string => {
let moduleName: string = ''
let position: string = ''
const messageList: string[] = []
logEvent.data.forEach((value: any) => {
if (value instanceof ContextTrace) {
moduleName = value.context
if (value.lineNumber && value.columnNumber) {
position = `${value.lineNumber}, ${value.columnNumber}`
}
return
}
if (typeof value !== 'string') {
value = Util.inspect(value, false, 3, true)
}
messageList.push(value)
})
const messageOutput: string = messageList.join(' ')
const positionOutput: string = position ? ` [${position}]` : ''
const typeOutput: string = `[${
logConfig.type
}] ${logEvent.pid.toString()} - `
const dateOutput: string = `${Moment(logEvent.startTime).format(
'YYYY-MM-DD HH:mm:ss',
)}`
const moduleOutput: string = moduleName
? `[${moduleName}] `
: '[LoggerService] '
let levelOutput: string = `[${logEvent.level}] ${messageOutput}`
switch (logEvent.level.toString()) {
case LoggerLevel.DEBUG:
levelOutput = Chalk.green(levelOutput)
break
case LoggerLevel.INFO:
levelOutput = Chalk.cyan(levelOutput)
break
case LoggerLevel.WARN:
levelOutput = Chalk.yellow(levelOutput)
break
case LoggerLevel.ERROR:
levelOutput = Chalk.red(levelOutput)
break
case LoggerLevel.FATAL:
levelOutput = Chalk.hex('#DD4C35')(levelOutput)
break
default:
levelOutput = Chalk.grey(levelOutput)
break
}
return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow(
moduleOutput,
)}${levelOutput}${positionOutput}`
}
})
В log4js проблема экспорта лога (т.е. куда выводится лог) решает Appender:
Log4js.configure({
appenders: {
console: {
type: 'stdout',
layout: { type: 'Awesome-nest' },
},
},
categories: {
default: {
appenders: ['console'],
level: 'debug',
},
},
})
настроено в конфигеdebug
Логи выше уровня пройдутconsole
вывод.
Далееexport
Одинlog class
, который предоставляет различные уровни методов журнала в log4js для вызова. Полный код выглядит следующим образом:
import * as _ from 'lodash'
import * as Path from 'path'
import * as Log4js from 'log4js'
import * as Util from 'util'
import * as Moment from 'moment'
import * as StackTrace from 'stacktrace-js'
import Chalk from 'chalk'
export enum LoggerLevel {
ALL = 'ALL',
MARK = 'MARK',
TRACE = 'TRACE',
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR',
FATAL = 'FATAL',
OFF = 'OFF',
}
export class ContextTrace {
constructor(
public readonly context: string,
public readonly path?: string,
public readonly lineNumber?: number,
public readonly columnNumber?: number,
) {}
}
Log4js.addLayout('Awesome-nest', (logConfig: any) => {
return (logEvent: Log4js.LoggingEvent): string => {
let moduleName: string = ''
let position: string = ''
const messageList: string[] = []
logEvent.data.forEach((value: any) => {
if (value instanceof ContextTrace) {
moduleName = value.context
if (value.lineNumber && value.columnNumber) {
position = `${value.lineNumber}, ${value.columnNumber}`
}
return
}
if (typeof value !== 'string') {
value = Util.inspect(value, false, 3, true)
}
messageList.push(value)
})
const messageOutput: string = messageList.join(' ')
const positionOutput: string = position ? ` [${position}]` : ''
const typeOutput: string = `[${
logConfig.type
}] ${logEvent.pid.toString()} - `
const dateOutput: string = `${Moment(logEvent.startTime).format(
'YYYY-MM-DD HH:mm:ss',
)}`
const moduleOutput: string = moduleName
? `[${moduleName}] `
: '[LoggerService] '
let levelOutput: string = `[${logEvent.level}] ${messageOutput}`
switch (logEvent.level.toString()) {
case LoggerLevel.DEBUG:
levelOutput = Chalk.green(levelOutput)
break
case LoggerLevel.INFO:
levelOutput = Chalk.cyan(levelOutput)
break
case LoggerLevel.WARN:
levelOutput = Chalk.yellow(levelOutput)
break
case LoggerLevel.ERROR:
levelOutput = Chalk.red(levelOutput)
break
case LoggerLevel.FATAL:
levelOutput = Chalk.hex('#DD4C35')(levelOutput)
break
default:
levelOutput = Chalk.grey(levelOutput)
break
}
return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow(
moduleOutput,
)}${levelOutput}${positionOutput}`
}
})
Log4js.configure({
appenders: {
console: {
type: 'stdout',
layout: { type: 'Awesome-nest' },
},
},
categories: {
default: {
appenders: ['console'],
level: 'debug',
},
},
})
const logger = Log4js.getLogger()
logger.level = LoggerLevel.TRACE
export class Logger {
static trace(...args) {
logger.trace(Logger.getStackTrace(), ...args)
}
static debug(...args) {
logger.debug(Logger.getStackTrace(), ...args)
}
static log(...args) {
logger.info(Logger.getStackTrace(), ...args)
}
static info(...args) {
logger.info(Logger.getStackTrace(), ...args)
}
static warn(...args) {
logger.warn(Logger.getStackTrace(), ...args)
}
static warning(...args) {
logger.warn(Logger.getStackTrace(), ...args)
}
static error(...args) {
logger.error(Logger.getStackTrace(), ...args)
}
static fatal(...args) {
logger.fatal(Logger.getStackTrace(), ...args)
}
static getStackTrace(deep: number = 2): ContextTrace {
const stackList: StackTrace.StackFrame[] = StackTrace.getSync()
const stackInfo: StackTrace.StackFrame = stackList[deep]
const lineNumber: number = stackInfo.lineNumber
const columnNumber: number = stackInfo.columnNumber
const fileName: string = stackInfo.fileName
const extnameLength: number = Path.extname(fileName).length
let basename: string = Path.basename(fileName)
basename = basename.substr(0, basename.length - extnameLength)
const context: string = _.upperFirst(_.camelCase(basename))
return new ContextTrace(context, fileName, lineNumber, columnNumber)
}
}
Таким образом, там, где вам нужно вывести журнал, просто вызовите его так:
Logger.info(id)
Но мы не хотим, чтобы каждый запрос регистрировался сам по себе, сейчас мы можем использовать этот журнал как промежуточное ПО:
import { Logger } from '../../shared/utils/logger'
export function logger(req, res, next) {
const statusCode = res.statusCode
const logFormat = `${req.method} ${req.originalUrl} ip: ${req.ip} statusCode: ${statusCode}`
next()
if (statusCode >= 500) {
Logger.error(logFormat)
} else if (statusCode >= 400) {
Logger.warn(logFormat)
} else {
Logger.log(logFormat)
}
}
существуетmain.ts
Зарегистрируйтесь в:
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix('api/v1')
app.use(logger)
app.useGlobalFilters(new ExceptionsFilter())
app.useGlobalInterceptors(new TransformInterceptor())
app.useGlobalPipes(new ValidationPipe())
await app.listen(config.port, config.hostName)
}
И вExceptionsFilter
Также вывод журнала для пойманных исключений:
export class ExceptionsFilter implements ExceptionFilter {
async catch(exception, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
Logger.error('exception', JSON.stringify(exception))
let message = exception.message
let isDeepestMessage = false
while (!isDeepestMessage) {
isDeepestMessage = !message.message
message = isDeepestMessage ? message : message.message
}
const errorResponse = {
message: message || '请求失败',
status: 1,
}
const status = exception instanceof HttpException ?
exception.getStatus() :
HttpStatus.INTERNAL_SERVER_ERROR
Logger.error(
`Catch http exception at ${request.method} ${request.url} ${status}`,
)
response.status(status)
response.header('Content-Type', 'application/json; charset=utf-8')
response.send(errorResponse)
}
}
Такая базовая система вывода журнала почти завершена. Конечно, log4jsappender
Также поддерживает следующее:
-
DateFile: журнал выводится в файл, и файл журнала можно прокручивать по определенному шаблону даты, например, сегодняшний вывод в
default-2016-08-21.log
, выход завтра вdefault-2016-08-22.log
; -
SMTP: выход вывода на электронную почту;
-
Mailgun: вывод логов в Mailgun через API Mailgun;
-
levelFilter может фильтровать по уровню;
-
и так далее для некоторых других приложений, чтобыздесьВы можете увидеть полный список.
Например, следующая конфигурация выводит журнал в файл с суффиксом даты и хранит его в течение 60 дней:
Log4js.configure({
appenders: {
fileAppender: {
type: 'DateFile',
filename: './logs/prod.log',
pattern: '-yyyy-MM-dd.log',
alwaysIncludePattern: true,
layout: { type: 'Flash' },
daysToKeep: 60
}
},
categories: {
default: {
appenders: ['fileAppender'],
level: 'info'
}
},
})
CRUD
Для общих операций CRUD его можно использовать в Nestjs.@nestjsx/crudЭта библиотека помогает нам сократить объем разработки.
Сначала установите соответствующие зависимости:
npm i @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator --save
затем создайте новыйdog.entity.ts
:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('dog')
export class DogEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 50 })
name: string
@Column()
age: number
@Column({ length: 100, nullable: true })
breed: string
}
существуетdog.service.ts
Просто напишите следующие строки кода:
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm'
import { DogEntity } from './dog.entity'
@Injectable()
export class DogsService extends TypeOrmCrudService<DogEntity> {
constructor(@InjectRepository(DogEntity) repo) {
super(repo)
}
}
существуетdog.controller.ts
в использовании@crud
Чтобы помочь автоматически сгенерировать API:
import { Controller } from '@nestjs/common'
import { Crud, CrudController } from '@nestjsx/crud'
import { DogEntity } from './dog.entity'
import { DogsService } from './dogs.service'
@Crud({
model: {
type: DogEntity,
},
})
@Controller('dogs')
export class DogsController implements CrudController<DogEntity> {
constructor(public service: DogsService) {}
}
В это время вы можете следитьДокументация @nestjsx/crudВ правиле API запросить соответствующую операцию CRUD. Например, запросGET api/v1/dogs
, он вернет всеdog
массив; запросGET api/v1/dogs/1
Вернусьid
за1
изdog
.
Ссылаться на
Использование интерфейса командной строки