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;