环境文件配置
一、简单的使用.env文件实现配置文件
- 1、安装依赖包
pnpm add dotenv
pnpm add @types/dotenv -D
- 2、根目录下创建一个
.env
的文件,如果是线上的项目将env
加入到.gitignore
中 - 3、在
.env
文件中写入端口号及项目前缀
# 项目前缀
PREIFX = api/v1
# 端口号
PORT = 8000
- 4、在
main.ts
中使用
import 'dotenv/config';//加载环境文件.env
import { Logger, VersioningType } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { getConfig, IS_DEV } from './utils';
export const config = getConfig();
const PORT = config.PORT || 8080;
const PREFIX = config.PREFIX || '/';
console.log(`Port: ${PORT}`, IS_DEV);
async function bootstrap() {
const logger: Logger = new Logger('main.ts');
const app = await NestFactory.create(AppModule, {
// 开启日志级别打印
logger: IS_DEV ? ['log', 'debug', 'error', 'warn'] : ['error', 'warn'],
});
//允许跨域请求
app.enableCors();
// 启动版本管理
app.enableVersioning({
defaultVersion: '1', // 不指定默认版本为v1
type: VersioningType.URI,
});
// 给请求添加prefix
app.setGlobalPrefix(PREFIX);
await app.listen(PORT, () => {
logger.log(`服务已经启动,接口请访问:http://wwww.localhost:${PORT}/${PREFIX}`);
});
}
bootstrap();
- 5、这种方式使用简单,只要安装两个依赖包就可以了,但是不足之处在于不能区分开发环境和生产环境配置,都统一写在了
.env
文件中,每次要使用的时候还要process.env.xx
的方式来使用,下面会介绍如何区分开发环境和生产环境使用不同的配置文件,方便环境中的变量管理
二、使用config
的依赖包来方便获取配置文件中的配置
- 1、安装依赖包,技术 (nestjs.cn)
pnpm add @nestjs/config
- 2、在
app.module.ts
中使用
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath,
}),
],
})
export class AppModule {}
- 3、在需要获取配置的地方使用
ConfigService
提供的一个get
方法来获取
const PORT = this.configService.get<string>('PORT');
console.log('当前的端口:',PORT);
- 4、如果使用了
@nestjs/config
这个包,在main.ts
文件中首部可以不用引入import 'dotenv/config'
三、使用.env
的方式来区分环境
- 1、在项目根目录下分别创建
.env
、.env.dev
、.env.prod
文件丙炔在ConfigModule
中加载
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot(
isGlobal: true,
envFilePath:['.env.dev','.env', '.env.prod'],
)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- 2、根据官网上的介绍,前面的优先级比后面的优先级高,简单理解,就是获取的时候会先查找
envFilePath
数组的第一项,其次之后。 - 3、安装依赖包来配置并设置当前环境、上面使用
process.env.NODE_ENV
已经不能判断当前是什么环境了
pnpm add cross-env
- 4、手动配置启动环境,修改
package.json
文件启动命令
···
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "cross-env RUNNING_ENV=dev nest start",
"start:dev": "cross-env RUNNING_ENV=dev nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "cross-env RUNNING_ENV=prod node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"pm2:prod": "cross-env RUNNING_ENV=prod pm2-runtime start ecosystem.config.js",
"pm2:dev": "pm2 start ecosystem.config.js --env development",
"pm2:test": "pm2 start ecosystem.config.js --env test",
"prepare": "husky install"
}
···
- 5、重新定义获取当前环境的变量,注意不能第一在
main.ts
中,必须在app.module.ts
文件中
export const IS_DEV = process.env.NODE_ENV !== 'production';
- 6、根据环境来切换配置
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from 'nestjs-config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
const envFilePath = ['.env']
export const IS_DEV = process.env.NODE_ENV !== 'production';
if (IS_DEV){
envFilePath.unshift('.env.dev')
}else {
envFilePath.unshift('.env.pord')
}
@Module({
imports: [ConfigModule.forRoot(
isGlobal: true,
envFilePath:['.env'],
)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- 7、关于
.env
、.env.dev
、.env.prod
几个文件的说明.env
文件存放一些通用的配置,在开发环境和生产环境都保持一样的.env.dev
文件存放开发环境的配置.env.pord
文件存放生产部署需要的配置
四、使用yml
文件来做配置文件
-
1、官网上也有介绍 .Configuration | NestJS - A progressive Node.js framework
-
2、安装依赖包
pnpm add yaml
- 3、在根目录下创建
application.dev.yml
和application.prod.yml
两个文件来区分开发环境和生产环境,并且添加值gitignore
文件中 - 4、创建一个文件用来读取配置
utils/config.ts
import { parse } from 'yaml';
import * as path from 'path';
import * as fs from 'fs';
// 获取项目运行环境
export const getEnv = () => {
return process.env.RUNNING_ENV;
};
export const IS_DEV = getEnv() === 'dev';
// 读取项目配置
export const getConfig = () => {
const environment = getEnv();
console.log(environment, '当前运行的环境');
const yamlPath = path.join(process.cwd(), `./application.${environment}.yml`);
const file = fs.readFileSync(yamlPath, 'utf8');
const config = parse(file);
return config;
};
这时候的application.dev.yml
和application.prod.yml
PORT: 8080
PREFIX: api
基础配置
一、项目版本管理
- 1、直接在
main.ts
中配置版本号,可能项目迭代会出现V2
等版本
// 启动版本管理
app.enableVersioning({
defaultVersion: [VERSION_NUM,'1'], // 默认版本为v1
type: VersioningType.URI,
});
- 2、如果整个控制器,全部接口都是一个版本的,可以直接在控制层设置
@Controller({path: 'home', version: '1'})
export class HomeController {
...
}
-
3、这时候浏览器上访问的地址就是
http://localhost:8080/v1/home
,域名/版本号[v1|v2]/项目名/控制器/方法 -
4、单独某一个接口要设置,可以直接在这个接口方法单独加上,如果方法和类都加了,会以方法的优先级更高
@Controller({path: 'home', version: '1'})
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@Version('2')
getHello(): string {
return this.appService.getHello();
}
}
- 5、这时候浏览器上访问的地址就是
http://localhost:8080/v2/home
,
二、项目前戳配置
- 1、在main.ts中配置
const PREFIX = process.env.PREFIX || 'api';
// 给请求添加prefix
app.setGlobalPrefix(PREFIX);
- 2、这时候浏览器访问地址
http://localhost:8080/api/...
三、跨域请求
-
2、在main.ts中添加
const app = await NestFactory.create(AppModule);
// 一般添加到前面
app.enableCors();
四、配置模块前缀或模块项目名称
-
1、官网Router module | NestJS - A progressive Node.js framework
-
2、在app.module.ts文件中使用
@Module({
imports: [
DashboardModule,
RouterModule.register([
{
path: 'dashboard',
module: DashboardModule,
},
]),
],
})
export class AppModule {}
-
3、这时候浏览器访问地址
http:localhost:8080/api/v1/dashboard/...
-
4、注意:灵活使用module注册router
五、helmet 保护
安全相关的HTTP标头的较小中间件函数的集合
-
2、安装
pnpm add helmet
- 3、在main.ts中使用
import helmet from 'helmet';
// somewhere in your initialization file
app.use(helmet());
六、热部署
- 1、为什么要热部署
在nestjs在开发环境中,要先将ts编译成js文件,然后来执行,如果项目大的时候就会出现编译时间过长,影响性能和效率,如果是小项目就无所谓配置
七、限流Rate Limiting
- 1、为什么要限流?
为保护应用程序免受暴力攻击、直接限制访问的速率;
Guards 守卫、登录鉴权
一、关于守卫
守卫其实也是中间件的一种,只是在NestJs
中对中间件更加具体的划分,将不同于职能的部分给了不同的称谓,一般守卫是用来做登录鉴权,权限类的,但是官网上写的比较复杂
二、uuid
生成 token
方法
jwt鉴权和token+redis的区别
jwt方案
1、jwt提供了2个方法,一个是将用户信息加密到一个字符串中
2、另一个方法是将字符串中的信息解密出用户信息
推荐方法:跨语言使用,v1 -> node , v2 -> go
token + redis 方案
1、使用一些算法生成一个token当做key存储,存储到redis中,值为当前用户用户信息(用户id,用户名等)
2、使用uuid简单生成token
3、根据前端传递token(uuid )从redis中读取数据,获取用户信息
这里使用uuid
的方式生成一个token
储存到redis
中,只要是唯一的值就可以
- 1、安装依赖包
pnpm add uuid
pnpm add @types/uuid -D
- 2、创建一个工具包或者使用服务
nest g s plugin/tools
- 3、直接定义生成一个
token
的get
属性
// tools.service.ts
import { Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class ToolsService {
// 去除uuid的中划线
public get uuidToken(): string {
return uuidv4().replace(/-/g, '');
}
}
三、在Nestjs
中使用redis
来缓存数据
- 1、安装依赖包
pnpm add ioredis
- 2、在插件中创建一个
redis
的服务
nest g s plugin/redis
- 3、在
redis
插件中写几个常用的方法,有需要可以自行在里面扩展
// redis.service.ts
import { Injectable } from '@nestjs/common';
import Redis, { ClientContext, Result } from 'ioredis';
import { ObjectType } from '@src/types';
import { isObject } from '@src/utils';
@Injectable()
export class RedisService {
public redisClient!: Redis;
// 模块加载的时候就缓存创建redis句柄
onModuleInit() {
if (!this.redisClient) {
this.getClient();
}
}
private getClient() {
this.redisClient = new Redis({
port: 6379, // Redis port
host: 'localhost', // redisDb Redis host
username: '', // needs Redis >= 6
password: '', // 密码
db: 0, // redis是几个数据库的,使用第一个
});
}
public async set(key: string, value: unknown): Promise<Result<'OK', ClientContext>>;
public async set(
key: string,
value: unknown,
second: number
): Promise<Result<'OK', ClientContext>>;
public async set(key: string, value: any, second?: number): Promise<Result<'OK', ClientContext>> {
value = isObject(value) ? JSON.stringify(value) : value;
if (!second) {
return await this.redisClient.set(key, value);
} else {
return await this.redisClient.set(key, value, 'EX', second);
}
}
public async incr(key: string): Promise<Result<number, ClientContext>> {
return await this.redisClient.incr(key);
}
public async get(key: string): Promise<Result<string | null, ClientContext>> {
try {
const data = await this.redisClient.get(key);
if (data) {
return JSON.parse(data);
} else {
return null;
}
} catch (e) {
return await this.redisClient.get(key);
}
}
public async del(key: string): Promise<Result<number, ClientContext>> {
return await this.redisClient.del(key);
}
async hset(key: string, field: ObjectType): Promise<Result<number, ClientContext>> {
return await this.redisClient.hset(key, field);
}
async hget(key: string, field: string): Promise<Result<string | null, ClientContext>> {
return await this.redisClient.hget(key, field);
}
async hgetall(key: string): Promise<Result<Record<string, string>, ClientContext>> {
return await this.redisClient.hgetall(key);
}
public async flushall(): Promise<Result<'OK', ClientContext>> {
return await this.redisClient.flushall();
}
}
- 4、测试在
app
服务层使用,储存一个数据导redis
中然后取出来
// app.service.ts
@Injectable()
export class AppService {
constructor(private readonly redisService: RedisService) {}
async getHello(): Promise<string> {
const result = await this.redisService.set(name, 'name');
console.log('存储成功');
const result1 = await this.redisService.get('name');
console.log('获取成功', result1);
return 'Hello World!';
}
}
- 5、本地查看
redis
数据库
![image-20221005232524290](/Users/xuesheng/Library/Application Support/typora-user-images/image-20221005232524290.png)
四、守卫实现token拦截,获取用户信息
# 使用命令创建一个守卫
nest g gu guard/auth
-
2、其实简单理解为中间件,只是在
nest
中给这类的中间件赋予了特殊的称呼,如果守卫中返回true表示放行,如果返回false表示就要拦截,我们要做的就是根据请求头上token去redis中查询到数据,如果有就返回 true,如果没有就返回false -
3、一般需要配置,登录白名单等,建议设置在yaml中
-
4、从
get url
中获取token
,并创建utils/url.ts
pnpm add url
import { URL } from 'url';
// 根据key从一段url中获取query值
export const getUrlQuery = (urlPath: string, key: string): string | null => {
const url = new URL(urlPath, 'https://www.');
const params = new URLSearchParams(url.search.substring(1));
return params.get(key);
};
- 5、在守卫中获取用户传递的
token
// auth.guard.ts
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { RedisService } from '@src/plugin/redis/redis.service';
import { getUrlQuery } from '@src/utils';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly redisService: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token =
context.switchToRpc().getData().headers.token ||
context.switchToHttp().getRequest().body.token ||
getUrlQuery(request.url, 'token');
console.log(token, '当前token----');
const currentUrl = request.route.path;
// 登录白名单 login
if(currentUrl.include('/login')){
return true
}
request.user = null
if(token){
// 如果传递了token的话就要从redis中查询是否有该token
const result = await this.redisService.get(token);
if (result) {
// 这里我们知道result数据类型就是我们定义的直接断言
request.user = result as unkonwn;
return true;
}else{
throw new HttpException('你传递token错误', HttpStatus.FORBIDDEN);
}
}
throw new HttpException('请传递token', HttpStatus.FORBIDDEN);
}
}
上面的守卫,实现所token的判断
不需要被守卫的接口请求
没有携带token的时候,直接返回false
携带了token但是携带的是过时的或者不存在,返回false
携带了正确的token,返回true
-
6、分别可以从
url
、headers
、body
中使用token
请求守卫 -
7、在接口上使用守卫
import { AuthGuard } from '@src/guard/auth.guard';
@UseGuards(AuthGuard) // 可直接在类上使用,下面的全部方法将都受到守卫的保护
@Controller('account')
export class AccountController {
@Get()
@UseGuards(AuthGuard) // 可单独设置一个接口来使用
async findAccount(@Body() findAccountDto: FindAccountDto): Promise<string> {
...
}
...
}
- 8、全局使用
app.module.ts
中
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from '@src/guard/auth.guard';
...
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
];
...
// 注意:APP_GUARD 这个由@nestjs/core指定 ,其同类型的还有APP_FILTER, APP_INTERCEPTOR, APP_PIPE , APP_GUARD
当前用户装饰器
创建自定义的装饰器来实现获取当前用户信息
- 1、Custom decorators | NestJS - A progressive Node.js framework
- 2、创建
current-user
nest g d decorator/current-user
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if(data && request.user ){
return request.user[data];
}else{
return request.user;
}
},
);
- 3、在守卫代码中,将用户信息挂载到
request
上,这个地方定义的装饰器的匹配值要一致是user
// auth.guard.ts
...
if(token){
// 如果传递了token的话就要从redis中查询是否有该token
const result = await this.redisService.get(token);
if (result) {
// 这里我们知道result数据类型就是我们定义的直接断言
request.user = result;
return true;
}else{
throw new HttpException('你传递token错误', HttpStatus.FORBIDDEN);
}
}
- 4、在
controller
中使用
@Get()
async getHello(@CurrentUser() user): Promise<string> {
console.log('user', user); // 这里打印出来的就是用户信息
return await this.appService.getHello();
}
- 5、获取user中的userId
@Get()
async getHello(@CurrentUser('userId') userId): Promise<string> {
console.log('userId', userId); // 这里打印出来的就是用户userId
return await this.appService.getHello();
}
配置typeorm操作mysql
一、在Nestjs中配置typeorm链接mysql
pnpm install --save typeorm mysql2
- 3、创建
database.providers.ts
import { DataSource } from 'typeorm';
export const databaseProviders = [
{
provide: 'DATA_SOURCE',
useFactory: async () => {
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [
__dirname + '/../**/*.entity{.ts,.js}',
],
synchronize: true,
});
return dataSource.initialize();
},
},
];
- 4、在
application.dev.yarm
文件中添加关于mysql
的配置信息
# 数据库配置
datasource:
driverName: mysql
host: localhost
port: 3306
database: nestjs-mysql-api
username: root
password: 123456
charset: utf8mb4
loc: Asia/Shanghai
logging: true
- 5、修改
database.providers.ts
// mysql的连接
{
provide: 'DATA_SOURCE',
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: String(configService.get('datasource.host')),
port: Number.parseInt(configService.get('datasource.port') ?? '3306'),
username: String(configService.get('datasource.username')),
password: String(configService.get('datasource.password')),
database: String(configService.get('datasource.database')),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
logging: configService.get('datasource.logging'),
timezone: '+08:00', // 东八区
cache: {
duration: 60000, // 1分钟的缓存
},
extra: {
poolMax: 32,
poolMin: 16,
queueTimeout: 60000,
pollPingInterval: 60, // 每隔60秒连接
pollTimeout: 60, // 连接有效60秒
},
}),
}
database.module.ts
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
providers: [...databaseProviders],
exports: [...databaseProviders],
})
export class DatabaseModule {}
二、关于数据库的创建
- 1、在
typeorm
中已经帮我们实现了是否将实体类同步到数据库表,注意:数据库还是要手动创建的,只是typeorm
会根据项目中的实体类同步的在数据库中生成对应的数据表,并不是数据库也可以自动创建。 - 2、开启自动同步数据表功能
// 自动同步开启
synchronize: true,
// 开启控制器的日志输出
logging:true
注意:synchronize 如果是开发环境可以设置为true,如果是生产环境建议设置成false
配置Prisma操作数据库
Prisma 是一个面向 Node.js 和 TypeScript 的开源 ORM。它可以作为编写普通 SQL 或使用其他数据库访问工具(如 SQL 查询构建器(如 knex.js)或 ORM (如 TypeORM 和 Sequelize))的替代方法。Prisma 目前支持 PostgreSQL、 MySQL、 SQLServer、 SQLite、 MongoDB 和 CockroachDB (预览)。
一、安装配置
- 1、官网 英文:Prisma | Next-generation ORM for Node.js & TypeScript 中文:Prisma | Next-generation ORM for Node.js & TypeScript
- 2、安装依赖
pnpm install prisma --save-dev
- 3、本地调用 CLI ,使用 Prisma CLI 的 init 命令创建初始 Prisma 设置:
npx prisma init
这个命令创建一个新的 Prisma 目录,其内容如下:
schema.prisma
: 指定数据库连接并包含数据库模式.env
: dotenv 文件,通常用于在一组环境变量中存储数据库凭据,schema读取DATABASE_URL地址
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
- 4、使用 Prisma Client
pnpm install @prisma/client
- 5、在 src 目录中,创建一个名为 prisma.service.ts 的新文件,并向其添加以下代码:
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
// 连接数据库成功
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
// 关闭连接
await app.close();
});
}
}
- 6、在
service
中使用
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from '@prisma/client';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
): Promise<User | null> {
return this.prisma.user.findUnique({
where: userWhereUniqueInput,
});
}
async users(params: {
skip?: number;
take?: number;
cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput;
orderBy?: Prisma.UserOrderByWithRelationInput;
}): Promise<User[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.user.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}
async updateUser(params: {
where: Prisma.UserWhereUniqueInput;
data: Prisma.UserUpdateInput;
}): Promise<User> {
const { where, data } = params;
return this.prisma.user.update({
data,
where,
});
}
async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
return this.prisma.user.delete({
where,
});
}
}
表单验证 Dto
表单验证使用管道操作,所以你需要pipe
管道有所了解。
一、安装配置
pnpm add class-validator class-transformer
pnpm add -D @nestjs/mapped-types
- 3、然后在
main.ts
中注册
async function bootstrap() {
...
app.useGlobalPipes(new ValidationPipe());
...
}
bootstrap();
二、验证实现
- 1、首先创建一个
dto
文件auth/dto/create-user.dto.ts
,对请求的数据进行验证规则声明
$property
指当前表单字段
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty({message: '$property:用户名不能为空'})
name: string;
//存在price时才验证
@ValidateIf((o) => o.price)
//将类型转换为数值
@Type(() => Number)
price:number
}
- 2、然后创建
validate.pipe.ts
验证管道
import {
ArgumentMetadata,
BadRequestException,
Injectable,
PipeTransform,
} from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class ValidatePipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
const { metatype } = metadata;
//前台提交的表单数据没有类型,使用 plainToClass 转为有类型的对象用于验证
const object = plainToInstance(metatype, value);
//根据 DTO 中的装饰器进行验证
const errors = await validate(object);
if (errors.length) {
throw new BadRequestException('表单数据错误');
}
return value;
}
}
- 3、然后在控制器方法中使用验证管道进行验证
@Post('add')
add(@Body(ValidatePipe) dto: UserDto): any {
return dto;
}
三、内置验证
-
1、NestJs 提供了开箱即用的验证,不需要我们自己来实现验证,我们现在来体验
-
2、首先在 main.ts 全局注册验证管道
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Validate } from './validate';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//注册验证管道
app.useGlobalPipes(new Validate());
await app.listen(3000);
}
bootstrap();
- 3、创建user资源用于进行验证实验
nest g res user
- 4、创建dto文件 auth/dto/create-user.dto.ts ,用于定义验证规则
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty({message: '用户名不能为空'})
name: string;
}
- 5、在需要验证的控制器方法中使用 DTO
import { Body, Controller, Get, Post } from '@nestjs/common';
import { RegisterDto } from './dto/register.dto';
@Controller('user')
export class AuthController {
@Post()
register(@Body() dto: CreateUserDto) {
return dto;
}
}
- 6、在api测试工具中对
user
进行测试
{
"statusCode": 400,
"message": [
"用户名不能为空"
],
"error": "Bad Request"
}
其他配置
一、自动装换
- 1、ValidationPipe 可以根据对象的 DTO 类自动将有效数据转换为对象类型。
如果不使用自动转换时,下面的id为string
@Get(':id')
index(@Param('id') id: number) {
console.log(typeof id);
}
- 2、在main.ts中设置全局自动转换后,上面的**id****类型自动转换为 number
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
}),
);
二、白名单
- 1、想过滤掉在 Dto 中没有声明的字段,可以在 main.ts 文件中对 ValidationPipe 管道进行配置。
async function bootstrap() {
...
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
...
}
登录注册
- 1、安装依赖包
pnpm add argon2
- 2、然后创建资源
nest g res auth
用户注册
- 1、修改
create-auth.dto.ts
对字段进行验证
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateAuthDto {
@IsNotEmpty({ message: '用户名不能为空' })
@IsEmail({}, { message: '用户名必须是邮箱' })
email: string;
@IsNotEmpty({ message: '密码不能为空' })
password: string;
}
- 2、然后修改
auth.service.ts
完善业务
import { PrismaClient } from '@prisma/client';
import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { UpdateAuthDto } from './dto/update-auth.dto';
import * as argon2 from 'argon2';
@Injectable()
export class AuthService extends PrismaClient {
async register(createAuthDto: CreateAuthDto) {
//加密用户密码
const password = await argon2.hash(createAuthDto.password);
const user = await this.users.create({
data: {
email: createAuthDto.email,
password,
},
});
//返回的数据不显示密码
delete user.password;
return user;
}
...
}
- 3、然后控制器
auth.controller.ts
中使用
@Controller()
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
register(@Body() createAuthDto: CreateAuthDto) {
return this.authService.register(createAuthDto);
}
...
}
用户登录
- 1、创建
login-dto.ts
内容如下。 - 2、其中
IsExist
自定义的验证,请查看管道章节,你也可以把这行删除
import { IsEmail, IsNotEmpty } from 'class-validator';
import { IsExist } from 'src/rules/is-exist.rule';
export class LoginDto {
@IsNotEmpty({ message: '用户名不能为空' })
@IsEmail({}, { message: '用户名必须是邮箱' })
@IsExist({ field: 'email', table: 'users' }, { message: '用户不存在' })
email: string;
@IsNotEmpty({ message: '密码不能为空' })
password: string;
}
- 3、修改
auth.service.ts
,添加login
方法
import { PrismaClient } from '@prisma/client';
import { Injectable, BadRequestException } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import * as argon2 from 'argon2';
@Injectable()
export class AuthService extends PrismaClient {
...
async login({ email, password }: CreateAuthDto) {
const user = await this.users.findUnique({
where: { email },
});
if (!user) throw new BadRequestException('用户不存在');
const psMatch = await argon2.verify(user.password, password);
if (!psMatch) throw new BadRequestException('密码输入错误');
delete user.password;
return user;
}
}
控制器代码
@Controller()
export class AuthController {
constructor(private readonly authService: AuthService) {}
...
@Post('login')
login(@Body() data: LoginDto) {
return this.authService.login(data);
}
}