From 9ac17e43e3e14324a7d9a83781ca4ba0120516a6 Mon Sep 17 00:00:00 2001 From: xidongdong-153 Date: Sat, 18 Nov 2023 23:42:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(learning):=20=E5=AE=8C=E6=88=90NestJS?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E6=A6=82=E5=BF=B5=E5=92=8C=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=8F=90=E4=BE=9B=E8=80=85=E7=9A=84=E5=AD=A6=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入并配置了Fastify替代Express作为HTTP服务器,提高应用性能。 - 实现了PostController及其CRUD操作,包括请求方法和参数处理。 - 创建并使用CreatePostDto和UpdatePostDto进行请求数据验证。 - 完成了多种自定义提供者的实现和应用,如值提供者、类提供者和工厂提供者。 - 添加了一些全局类型定义,如RecordAny和BaseType,用于增强代码的可读性和健壮性。 - 通过ConfigService添加和管理新的环境配置项。 --- src/app.module.ts | 17 ++++- src/modules/content/content.module.ts | 11 ++++ src/modules/content/controllers/index.ts | 1 + .../content/controllers/post.controller.ts | 60 +++++++++++++++++ src/modules/content/dtos/create-post.dto.ts | 24 +++++++ src/modules/content/dtos/index.ts | 2 + src/modules/content/dtos/update-post.dto.ts | 14 ++++ src/modules/content/services/index.ts | 1 + src/modules/content/services/post.service.ts | 64 +++++++++++++++++++ src/modules/content/types.ts | 6 ++ src/modules/core/core.module.ts | 23 +++++++ src/modules/core/services/config.service.ts | 15 +++++ src/modules/welcome/services/fifth.service.ts | 15 +++++ src/modules/welcome/services/first.service.ts | 16 +++++ .../welcome/services/fourth.service.ts | 20 ++++++ .../welcome/services/second.service.ts | 16 +++++ src/modules/welcome/services/sixth.service.ts | 15 +++++ src/modules/welcome/services/third.service.ts | 12 ++++ src/modules/welcome/welcome.controller.ts | 51 ++++++++++++++- src/modules/welcome/welcome.module.ts | 58 +++++++++++++++++ tsconfig.build.json | 2 +- typings/global.d.ts | 54 ++++++++++++++++ 22 files changed, 491 insertions(+), 6 deletions(-) create mode 100644 src/modules/content/content.module.ts create mode 100644 src/modules/content/controllers/index.ts create mode 100644 src/modules/content/controllers/post.controller.ts create mode 100644 src/modules/content/dtos/create-post.dto.ts create mode 100644 src/modules/content/dtos/index.ts create mode 100644 src/modules/content/dtos/update-post.dto.ts create mode 100644 src/modules/content/services/index.ts create mode 100644 src/modules/content/services/post.service.ts create mode 100644 src/modules/content/types.ts create mode 100644 src/modules/core/core.module.ts create mode 100644 src/modules/core/services/config.service.ts create mode 100644 src/modules/welcome/services/fifth.service.ts create mode 100644 src/modules/welcome/services/first.service.ts create mode 100644 src/modules/welcome/services/fourth.service.ts create mode 100644 src/modules/welcome/services/second.service.ts create mode 100644 src/modules/welcome/services/sixth.service.ts create mode 100644 src/modules/welcome/services/third.service.ts create mode 100644 src/modules/welcome/welcome.module.ts create mode 100644 typings/global.d.ts diff --git a/src/app.module.ts b/src/app.module.ts index ad632b3..6b83193 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,21 @@ import { Module } from '@nestjs/common'; -import { WelcomeController } from '@/modules/welcome/welcome.controller'; +import { ContentModule } from '@/modules/content/content.module'; +import { WelcomeModule } from '@/modules/welcome/welcome.module'; + +import { CoreModule } from './modules/core/core.module'; @Module({ - imports: [], - controllers: [WelcomeController], + imports: [ + ContentModule, + WelcomeModule, + CoreModule.forRoot({ + config: { + name: '欢迎访问 Ink NestJS API !', + }, + }), + ], + controllers: [], providers: [], }) export class AppModule {} diff --git a/src/modules/content/content.module.ts b/src/modules/content/content.module.ts new file mode 100644 index 0000000..2ca4b36 --- /dev/null +++ b/src/modules/content/content.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { PostController } from './controllers'; +import { PostService } from './services'; + +@Module({ + controllers: [PostController], + providers: [PostService], + exports: [PostService], +}) +export class ContentModule {} diff --git a/src/modules/content/controllers/index.ts b/src/modules/content/controllers/index.ts new file mode 100644 index 0000000..66c1b42 --- /dev/null +++ b/src/modules/content/controllers/index.ts @@ -0,0 +1 @@ +export * from './post.controller'; diff --git a/src/modules/content/controllers/post.controller.ts b/src/modules/content/controllers/post.controller.ts new file mode 100644 index 0000000..b6bfaee --- /dev/null +++ b/src/modules/content/controllers/post.controller.ts @@ -0,0 +1,60 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, ValidationPipe } from '@nestjs/common'; + +import { CreatePostDto, UpdatePostDto } from '../dtos'; +import { PostService } from '../services'; + +/** + * 文章控制器 + * 负责处理与文章相关的请求,如获取文章列表、创建新文章等。 + */ +@Controller('post') +export class PostController { + constructor(private postService: PostService) {} + + @Get() + async index() { + return this.postService.findAll(); + } + + @Get(':id') + async show(@Param('id') id: number) { + return this.postService.findOne(id); + } + + @Post() + async store( + @Body( + new ValidationPipe({ + transform: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + validationError: { target: false }, + groups: ['create'], + }), + ) + data: CreatePostDto, + ) { + return this.postService.create(data); + } + + @Patch() + async update( + @Body( + new ValidationPipe({ + transform: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + validationError: { target: false }, + groups: ['update'], + }), + ) + data: UpdatePostDto, + ) { + return this.postService.update(data); + } + + @Delete(':id') + async delete(@Param('id') id: number) { + return this.postService.delete(id); + } +} diff --git a/src/modules/content/dtos/create-post.dto.ts b/src/modules/content/dtos/create-post.dto.ts new file mode 100644 index 0000000..17aab68 --- /dev/null +++ b/src/modules/content/dtos/create-post.dto.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { IsNotEmpty, IsOptional, MaxLength } from 'class-validator'; + +@Injectable() +export class CreatePostDto { + @MaxLength(255, { + always: true, + message: '帖子标题长度最大为$constraint1', + }) + @IsNotEmpty({ groups: ['create'], message: '帖子标题必需填写' }) + @IsOptional({ groups: ['update'] }) + title: string; + + @IsNotEmpty({ groups: ['create'], message: '帖子内容必需填写' }) + @IsOptional({ groups: ['updatae'] }) + body: string; + + @MaxLength(500, { + always: true, + message: '帖子描述长度最大为$constraint1', + }) + @IsOptional({ always: true }) + summary: string; +} diff --git a/src/modules/content/dtos/index.ts b/src/modules/content/dtos/index.ts new file mode 100644 index 0000000..e67ec36 --- /dev/null +++ b/src/modules/content/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './create-post.dto'; +export * from './update-post.dto'; diff --git a/src/modules/content/dtos/update-post.dto.ts b/src/modules/content/dtos/update-post.dto.ts new file mode 100644 index 0000000..5fc4d77 --- /dev/null +++ b/src/modules/content/dtos/update-post.dto.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; + +import { PartialType } from '@nestjs/swagger'; + +import { IsDefined, IsNumber } from 'class-validator'; + +import { CreatePostDto } from './create-post.dto'; + +@Injectable() +export class UpdatePostDto extends PartialType(CreatePostDto) { + @IsNumber(undefined, { groups: ['update'], message: '帖子ID格式错误' }) + @IsDefined({ groups: ['update'], message: '帖子ID必需指定' }) + id: number; +} diff --git a/src/modules/content/services/index.ts b/src/modules/content/services/index.ts new file mode 100644 index 0000000..588bc86 --- /dev/null +++ b/src/modules/content/services/index.ts @@ -0,0 +1 @@ +export * from './post.service'; diff --git a/src/modules/content/services/post.service.ts b/src/modules/content/services/post.service.ts new file mode 100644 index 0000000..e52a668 --- /dev/null +++ b/src/modules/content/services/post.service.ts @@ -0,0 +1,64 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; + +import { isNil } from 'lodash'; + +import { CreatePostDto, UpdatePostDto } from '../dtos'; + +import { PostEntity } from '../types'; + +@Injectable() +export class PostService { + protected posts: PostEntity[] = [ + { title: '第一篇文章标题', body: '第一篇文章内容' }, + { title: '第二篇文章标题', body: '第二篇文章内容' }, + { title: '第三篇文章标题', body: '第三篇文章内容' }, + { title: '第四篇文章标题', body: '第四篇文章内容' }, + { title: '第五篇文章标题', body: '第五篇文章内容' }, + { title: '第六篇文章标题', body: '第六篇文章内容' }, + ].map((v, id) => ({ ...v, id })); + + async findAll() { + return this.posts; + } + + async findOne(id: number) { + const post = this.posts.find((item) => item.id === id); + + if (isNil(post)) throw new NotFoundException(`id: ${id} 文章不存在`); + + return post; + } + + async create(data: CreatePostDto) { + const newPost: PostEntity = { + id: Math.max(...this.posts.map(({ id }) => id + 1)), + ...data, + }; + + this.posts.push(newPost); + + return newPost; + } + + async update(data: UpdatePostDto) { + let toUpdate = this.posts.find((item) => item.id === data.id); + + if (isNil(toUpdate)) throw new NotFoundException(`id: ${data.id} 文章不存在`); + + toUpdate = { ...toUpdate, ...data }; + + this.posts = this.posts.map((item) => (item.id === data.id ? toUpdate : item)); + + return toUpdate; + } + + async delete(id: number) { + const toDelete = this.posts.find((item) => item.id === id); + + if (isNil(toDelete)) throw new NotFoundException(`id: ${id} 文章不存在`); + + this.posts = this.posts.filter((item) => item.id !== id); + + return toDelete; + } +} diff --git a/src/modules/content/types.ts b/src/modules/content/types.ts new file mode 100644 index 0000000..2d8a348 --- /dev/null +++ b/src/modules/content/types.ts @@ -0,0 +1,6 @@ +export interface PostEntity { + id: number; + title: string; + summary?: string; + body: string; +} diff --git a/src/modules/core/core.module.ts b/src/modules/core/core.module.ts new file mode 100644 index 0000000..5422b25 --- /dev/null +++ b/src/modules/core/core.module.ts @@ -0,0 +1,23 @@ +import { DynamicModule, Global, Module } from '@nestjs/common'; + +import { ConfigService } from './services/config.service'; + +@Global() +@Module({}) +export class CoreModule { + static forRoot(options: { config: RecordAny }): DynamicModule { + return { + module: CoreModule, + global: true, + providers: [ + { + provide: ConfigService, + useFactory() { + return new ConfigService(options.config); + }, + }, + ], + exports: [ConfigService], + }; + } +} diff --git a/src/modules/core/services/config.service.ts b/src/modules/core/services/config.service.ts new file mode 100644 index 0000000..e5685d6 --- /dev/null +++ b/src/modules/core/services/config.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { get } from 'lodash'; + +@Injectable() +export class ConfigService { + protected config: RecordAny = {}; + + constructor(data: RecordAny) { + this.config = data; + } + + get(key: string, defaultValue?: T): T | undefined { + return get(this.config, key, defaultValue); + } +} diff --git a/src/modules/welcome/services/fifth.service.ts b/src/modules/welcome/services/fifth.service.ts new file mode 100644 index 0000000..d3eb2b5 --- /dev/null +++ b/src/modules/welcome/services/fifth.service.ts @@ -0,0 +1,15 @@ +import { Injectable, forwardRef, Inject } from '@nestjs/common'; + +import { SixthService } from './sixth.service'; + +@Injectable() +export class FifthService { + constructor( + @Inject(forwardRef(() => SixthService)) + protected sixth: WrapperType, + ) {} + + circular() { + return `FifthService 循环依赖1${this.sixth.circular()}`; + } +} diff --git a/src/modules/welcome/services/first.service.ts b/src/modules/welcome/services/first.service.ts new file mode 100644 index 0000000..683cb0e --- /dev/null +++ b/src/modules/welcome/services/first.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class FirstService { + useValue() { + return 'FirstService useValue'; + } + + useId() { + return 'FirstService 字符串提供者'; + } + + useAlias() { + return 'FirstService 别名提供者'; + } +} diff --git a/src/modules/welcome/services/fourth.service.ts b/src/modules/welcome/services/fourth.service.ts new file mode 100644 index 0000000..9c6f694 --- /dev/null +++ b/src/modules/welcome/services/fourth.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; + +import { ThirdService } from './third.service'; + +@Injectable() +export class FourthService { + constructor(private third: ThirdService) {} + + getContent() { + return this.third.useFactory(); + } + + async getPromise() { + return new Promise((resolve) => { + setTimeout(() => { + resolve(this.third); + }, 100); + }); + } +} diff --git a/src/modules/welcome/services/second.service.ts b/src/modules/welcome/services/second.service.ts new file mode 100644 index 0000000..503acf2 --- /dev/null +++ b/src/modules/welcome/services/second.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SecondService { + useClass() { + return 'useClass - 2'; + } + + useFactory() { + return '构造器提供者 - 1'; + } + + useAsync() { + return '异步提供者'; + } +} diff --git a/src/modules/welcome/services/sixth.service.ts b/src/modules/welcome/services/sixth.service.ts new file mode 100644 index 0000000..b1d9dba --- /dev/null +++ b/src/modules/welcome/services/sixth.service.ts @@ -0,0 +1,15 @@ +import { Injectable, forwardRef, Inject } from '@nestjs/common'; + +import { FifthService } from './fifth.service'; + +@Injectable() +export class SixthService { + constructor( + @Inject(forwardRef(() => FifthService)) + protected fifth: WrapperType, + ) {} + + circular() { + return `SixthService 循环依赖2`; + } +} diff --git a/src/modules/welcome/services/third.service.ts b/src/modules/welcome/services/third.service.ts new file mode 100644 index 0000000..ac27615 --- /dev/null +++ b/src/modules/welcome/services/third.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ThirdService { + useClass() { + return 'useClass - 3'; + } + + useFactory() { + return '构造器提供者 - 2'; + } +} diff --git a/src/modules/welcome/welcome.controller.ts b/src/modules/welcome/welcome.controller.ts index 3d42d0a..71addfe 100644 --- a/src/modules/welcome/welcome.controller.ts +++ b/src/modules/welcome/welcome.controller.ts @@ -1,12 +1,59 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Inject } from '@nestjs/common'; + +import { ConfigService } from '../core/services/config.service'; + +import { FifthService } from './services/fifth.service'; +import { FirstService } from './services/first.service'; +import { FourthService } from './services/fourth.service'; +import { SecondService } from './services/second.service'; /** * 欢迎访问 Ink NestJS API */ @Controller() export class WelcomeController { + constructor( + private configService: ConfigService, + private first: FirstService, + @Inject('ID-WELCOME') private idExp: FirstService, + @Inject('FACTORY-WELCOME') private ftExp: FourthService, + @Inject('ALIAS-WELCOME') private asExp: FirstService, + @Inject('ASYNC-WELCOME') private acExp: SecondService, + private fifth: FifthService, + ) {} + @Get() getMessage(): string { - return '访问 ink nestjs api, 了解更多 请联系邮箱 youzegehq@gmail.com'; + return this.configService.get('name'); + } + + @Get('value') + async useValue() { + return this.first.useValue(); + } + + @Get('id') + async useId() { + return this.idExp.useId(); + } + + @Get('factory') + async useFactory() { + return this.ftExp.getContent(); + } + + @Get('alias') + async useAlias() { + return this.asExp.useAlias(); + } + + @Get('async') + async useAsync() { + return this.acExp.useAsync(); + } + + @Get('circular') + async useCircular() { + return this.fifth.circular(); } } diff --git a/src/modules/welcome/welcome.module.ts b/src/modules/welcome/welcome.module.ts new file mode 100644 index 0000000..5ab17cd --- /dev/null +++ b/src/modules/welcome/welcome.module.ts @@ -0,0 +1,58 @@ +import { Module } from '@nestjs/common'; + +import { FirstService } from './services/first.service'; +import { FourthService } from './services/fourth.service'; +import { SecondService } from './services/second.service'; +import { ThirdService } from './services/third.service'; +import { WelcomeController } from './welcome.controller'; +import { FifthService } from './services/fifth.service'; +import { SixthService } from './services/sixth.service'; + +const firstObject = { + useValue: () => 'firstObject useValue 提供者', + useAlias: () => 'firstObject 别名提供者', +}; + +const firstInstance = new FirstService(); + +@Module({ + controllers: [WelcomeController], + providers: [ + { + provide: FirstService, + useValue: firstObject, + }, + { + provide: 'ID-WELCOME', + useValue: firstInstance, + }, + { + provide: SecondService, + useClass: ThirdService, + }, + { + provide: 'FACTORY-WELCOME', + useFactory(second: SecondService) { + const factory = new FourthService(second); + + return factory; + }, + inject: [SecondService], + }, + { + provide: 'ALIAS-WELCOME', + useExisting: FirstService, + }, + { + provide: 'ASYNC-WELCOME', + useFactory: async () => { + const factory = new FourthService(new SecondService()); + + return factory.getPromise(); + }, + }, + FifthService, + SixthService, + ], +}) +export class WelcomeModule {} diff --git a/tsconfig.build.json b/tsconfig.build.json index 43f8ac0..46dadc7 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "**.js"] + "exclude": ["node_modules", "test", "dist", "trashed", "back", "**/*spec.ts", "**.js"] } \ No newline at end of file diff --git a/typings/global.d.ts b/typings/global.d.ts new file mode 100644 index 0000000..15fcb9e --- /dev/null +++ b/typings/global.d.ts @@ -0,0 +1,54 @@ +declare type RecordAny = Record; +declare type RecordNever = Record; +declare type RecordAnyOrNever = RecordAny | RecordNever; + +/** + * 基础类型接口 + */ +declare type BaseType = boolean | number | string | undefined | null; + +/** + * 环境变量类型转义函数接口 + */ +declare type ParseType = (value: string) => T; + +/** + * 类转义为普通对象后的类型 + */ +declare type ClassToPlain = { [key in keyof T]: T[key] }; + +/** + * 一个类的类型 + */ +declare type ClassType = { new (...args: any[]): T }; + +/** + * 嵌套对象全部可选 + */ +declare type RePartial = { + [P in keyof T]?: T[P] extends (infer U)[] | undefined + ? RePartial[] + : T[P] extends object | undefined + ? T[P] extends ((...args: any[]) => any) | ClassType | undefined + ? T[P] + : RePartial + : T[P]; +}; + +/** + * 嵌套对象全部必选 + */ +declare type ReRequired = { + [P in keyof T]-?: T[P] extends (infer U)[] + ? ReRequired[] + : T[P] extends ReadonlyArray + ? ReadonlyArray> + : T[P] extends object + ? ReRequired + : T[P]; +}; + +/** + * 防止swc下循环依赖报错 + */ +declare type WrapperType = T;