feat(learning): 完成NestJS核心概念和自定义提供者的学习

- 引入并配置了Fastify替代Express作为HTTP服务器,提高应用性能。
- 实现了PostController及其CRUD操作,包括请求方法和参数处理。
- 创建并使用CreatePostDto和UpdatePostDto进行请求数据验证。
- 完成了多种自定义提供者的实现和应用,如值提供者、类提供者和工厂提供者。
- 添加了一些全局类型定义,如RecordAny和BaseType,用于增强代码的可读性和健壮性。
- 通过ConfigService添加和管理新的环境配置项。
This commit is contained in:
3R-喜东东 2023-11-18 23:42:29 +08:00
parent d00d3ab968
commit 9ac17e43e3
22 changed files with 491 additions and 6 deletions

View File

@ -1,10 +1,21 @@
import { Module } from '@nestjs/common'; 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({ @Module({
imports: [], imports: [
controllers: [WelcomeController], ContentModule,
WelcomeModule,
CoreModule.forRoot({
config: {
name: '欢迎访问 Ink NestJS API !',
},
}),
],
controllers: [],
providers: [], providers: [],
}) })
export class AppModule {} export class AppModule {}

View File

@ -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 {}

View File

@ -0,0 +1 @@
export * from './post.controller';

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
export * from './create-post.dto';
export * from './update-post.dto';

View File

@ -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;
}

View File

@ -0,0 +1 @@
export * from './post.service';

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
export interface PostEntity {
id: number;
title: string;
summary?: string;
body: string;
}

View File

@ -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],
};
}
}

View File

@ -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<T>(key: string, defaultValue?: T): T | undefined {
return get(this.config, key, defaultValue);
}
}

View File

@ -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<SixthService>,
) {}
circular() {
return `FifthService 循环依赖1${this.sixth.circular()}`;
}
}

View File

@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class FirstService {
useValue() {
return 'FirstService useValue';
}
useId() {
return 'FirstService 字符串提供者';
}
useAlias() {
return 'FirstService 别名提供者';
}
}

View File

@ -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);
});
}
}

View File

@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class SecondService {
useClass() {
return 'useClass - 2';
}
useFactory() {
return '构造器提供者 - 1';
}
useAsync() {
return '异步提供者';
}
}

View File

@ -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<FifthService>,
) {}
circular() {
return `SixthService 循环依赖2`;
}
}

View File

@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class ThirdService {
useClass() {
return 'useClass - 3';
}
useFactory() {
return '构造器提供者 - 2';
}
}

View File

@ -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 * 访 Ink NestJS API
*/ */
@Controller() @Controller()
export class WelcomeController { 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() @Get()
getMessage(): string { 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();
} }
} }

View File

@ -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 {}

View File

@ -1,4 +1,4 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "**.js"] "exclude": ["node_modules", "test", "dist", "trashed", "back", "**/*spec.ts", "**.js"]
} }

54
typings/global.d.ts vendored Normal file
View File

@ -0,0 +1,54 @@
declare type RecordAny = Record<string, any>;
declare type RecordNever = Record<never, never>;
declare type RecordAnyOrNever = RecordAny | RecordNever;
/**
*
*/
declare type BaseType = boolean | number | string | undefined | null;
/**
*
*/
declare type ParseType<T extends BaseType = string> = (value: string) => T;
/**
*
*/
declare type ClassToPlain<T> = { [key in keyof T]: T[key] };
/**
*
*/
declare type ClassType<T> = { new (...args: any[]): T };
/**
*
*/
declare type RePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[] | undefined
? RePartial<U>[]
: T[P] extends object | undefined
? T[P] extends ((...args: any[]) => any) | ClassType<T[P]> | undefined
? T[P]
: RePartial<T[P]>
: T[P];
};
/**
*
*/
declare type ReRequired<T> = {
[P in keyof T]-?: T[P] extends (infer U)[]
? ReRequired<U>[]
: T[P] extends ReadonlyArray<infer V>
? ReadonlyArray<ReRequired<V>>
: T[P] extends object
? ReRequired<T[P]>
: T[P];
};
/**
* swc下循环依赖报错
*/
declare type WrapperType<T> = T;