feat(learning): 完成NestJS核心概念和自定义提供者的学习
- 引入并配置了Fastify替代Express作为HTTP服务器,提高应用性能。 - 实现了PostController及其CRUD操作,包括请求方法和参数处理。 - 创建并使用CreatePostDto和UpdatePostDto进行请求数据验证。 - 完成了多种自定义提供者的实现和应用,如值提供者、类提供者和工厂提供者。 - 添加了一些全局类型定义,如RecordAny和BaseType,用于增强代码的可读性和健壮性。 - 通过ConfigService添加和管理新的环境配置项。
This commit is contained in:
parent
d00d3ab968
commit
9ac17e43e3
@ -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 {}
|
||||
|
11
src/modules/content/content.module.ts
Normal file
11
src/modules/content/content.module.ts
Normal 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 {}
|
1
src/modules/content/controllers/index.ts
Normal file
1
src/modules/content/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './post.controller';
|
60
src/modules/content/controllers/post.controller.ts
Normal file
60
src/modules/content/controllers/post.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
24
src/modules/content/dtos/create-post.dto.ts
Normal file
24
src/modules/content/dtos/create-post.dto.ts
Normal 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;
|
||||
}
|
2
src/modules/content/dtos/index.ts
Normal file
2
src/modules/content/dtos/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './create-post.dto';
|
||||
export * from './update-post.dto';
|
14
src/modules/content/dtos/update-post.dto.ts
Normal file
14
src/modules/content/dtos/update-post.dto.ts
Normal 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;
|
||||
}
|
1
src/modules/content/services/index.ts
Normal file
1
src/modules/content/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './post.service';
|
64
src/modules/content/services/post.service.ts
Normal file
64
src/modules/content/services/post.service.ts
Normal 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;
|
||||
}
|
||||
}
|
6
src/modules/content/types.ts
Normal file
6
src/modules/content/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface PostEntity {
|
||||
id: number;
|
||||
title: string;
|
||||
summary?: string;
|
||||
body: string;
|
||||
}
|
23
src/modules/core/core.module.ts
Normal file
23
src/modules/core/core.module.ts
Normal 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],
|
||||
};
|
||||
}
|
||||
}
|
15
src/modules/core/services/config.service.ts
Normal file
15
src/modules/core/services/config.service.ts
Normal 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);
|
||||
}
|
||||
}
|
15
src/modules/welcome/services/fifth.service.ts
Normal file
15
src/modules/welcome/services/fifth.service.ts
Normal 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()}`;
|
||||
}
|
||||
}
|
16
src/modules/welcome/services/first.service.ts
Normal file
16
src/modules/welcome/services/first.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class FirstService {
|
||||
useValue() {
|
||||
return 'FirstService useValue';
|
||||
}
|
||||
|
||||
useId() {
|
||||
return 'FirstService 字符串提供者';
|
||||
}
|
||||
|
||||
useAlias() {
|
||||
return 'FirstService 别名提供者';
|
||||
}
|
||||
}
|
20
src/modules/welcome/services/fourth.service.ts
Normal file
20
src/modules/welcome/services/fourth.service.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
16
src/modules/welcome/services/second.service.ts
Normal file
16
src/modules/welcome/services/second.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SecondService {
|
||||
useClass() {
|
||||
return 'useClass - 2';
|
||||
}
|
||||
|
||||
useFactory() {
|
||||
return '构造器提供者 - 1';
|
||||
}
|
||||
|
||||
useAsync() {
|
||||
return '异步提供者';
|
||||
}
|
||||
}
|
15
src/modules/welcome/services/sixth.service.ts
Normal file
15
src/modules/welcome/services/sixth.service.ts
Normal 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`;
|
||||
}
|
||||
}
|
12
src/modules/welcome/services/third.service.ts
Normal file
12
src/modules/welcome/services/third.service.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class ThirdService {
|
||||
useClass() {
|
||||
return 'useClass - 3';
|
||||
}
|
||||
|
||||
useFactory() {
|
||||
return '构造器提供者 - 2';
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
58
src/modules/welcome/welcome.module.ts
Normal file
58
src/modules/welcome/welcome.module.ts
Normal 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 {}
|
@ -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"]
|
||||
}
|
54
typings/global.d.ts
vendored
Normal file
54
typings/global.d.ts
vendored
Normal 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;
|
Loading…
Reference in New Issue
Block a user