Nest.JS 用户注入详细介绍
Nest.JS 是最流行的 Node.js 框架。 它利用 TypeScript 的全部功能使开发过程快速而简单。 在本文中,我将分享非常酷且有用的技巧“用户注入(User Injection)”,它可以大大简化我们的 Nest.JS 项目的代码。
为什么我们需要这个?
这是任何开发人员在开始新事物之前应该问的第一个问题。 让我们想象一下,我们有一个 API 服务器,并且我们需要限制对某些数据的访问,这是一个非常常见的场景。 例如,让我们为一个有订单和客户的在线商店创建一个简单的 API。 客户必须只能访问他们的订单,而不能访问其他客户的订单。 OrdersController
将如下所示:
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { OrdersService } from './orders.service';
import { ICustomer } from '../customers/customer.model';
import { Customer } from '../common/customer.decorator';
import { IOrder } from './order.model';
import { AuthGuard } from '../common/auth.guard';
@Controller('orders')
@UseGuards(AuthGuard)
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
@Get('/')
getOrders(@Customer() customer: ICustomer): Promise<IOrder[]> {
return this.ordersService.getOrders(customer);
}
@Get('/:id')
getOrder(
@Param('id') id: string,
@Customer() customer: ICustomer,
): Promise<IOrder> {
return this.ordersService.getOrder(id, customer);
}
@Post('/')
create(
@Body() data: Partial<IOrder>,
@Customer() customer: ICustomer,
): Promise<IOrder> {
return this.ordersService.create(data, customer);
}
@Delete('/:id')
cancel(
@Param('id') id: string,
@Customer() customer: ICustomer,
): Promise<void> {
return this.ordersService.cancel(id, customer);
}
}
view raw
这个控制器用 AuthGuard
封装,@Customer
用于检索当前用户。 这是 OrdersService
的代码:
import {
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { ICustomer } from 'src/customers/customer.model';
import { IOrder } from './order.model';
import { OrdersRepository } from './orders.repository';
@Injectable()
export class OrdersService {
constructor(private readonly ordersRepository: OrdersRepository) {}
async create(data: Partial<IOrder>, customer: ICustomer): Promise<IOrder> {
const order = { ...data, customerId: customer.id };
return this.ordersRepository.create(order);
}
async cancel(orderId: string, customer: ICustomer): Promise<void> {
const order = await this.ordersRepository.findOne(orderId);
if (!order) {
throw new NotFoundException();
}
if (order.customerId !== customer.id) {
throw new ForbiddenException();
}
await this.ordersRepository.deleteOne(orderId);
}
async getOrders(customer: ICustomer): Promise<IOrder[]> {
return this.ordersRepository.find({ customerId: customer.id });
}
async getOrder(orderId: string, customer: ICustomer): Promise<IOrder> {
return this.ordersRepository.findOne({
id: orderId,
customerId: customer.id,
});
}
}
如大家所见,customer
被传递给 OrdersService 的每个方法。 这只是一项服务。 如果我们有数百个服务,每个服务至少有 10 种方法怎么办? 使用 customers 数据传递额外的参数变得非常烦人。
解决方法
幸运的是,Nest.JS 提供了非常灵活的依赖注入机制,Nest.JS 从第 6 版开始支持请求注入范围 。 这意味着我们可以将当前请求注入到我们的服务中。 如果我们可以注入请求,我们就可以注入存储在请求中的用户数据。 让我们写一个 customer 提供者:
import { FactoryProvider, Scope } from '@nestjs/common'
import { REQUEST } from '@nestjs/core'
export const customerProvider: FactoryProvider = {
provide: 'CUSTOMER',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req) => req.user,
}
现在尝试用它向 OrdersService
注入 customer :
@Injectable()
export class OrdersService {
constructor(
@Inject('CUSTOMER')
private readonly customer,
private readonly ordersRepository: OrdersRepository,
) {
console.log('Current customer:', this.customer);
}
}
现在 Nest.JS 在每个请求上创建一个新的 OrdersService
实例,让我们发出一个请求,看看注入了什么:
Current customer: undefined
它应该可以工作,但实际上,undefined
已注入 OrdersService
。 这种意外行为的原因是 Nest.JS 依赖注入机制。 它会在收到新请求后立即创建所有提供者,此时 req
对象不包含有关提出此请求的客户的信息。 它需要一些时间来验证授权令牌/cookie 并从数据库接收用户数据。
解决方法 #2
我花了一些时间想出一个优雅的解决方案。 如果在注入的那一刻不可用,我们如何注入一些东西? 但是解决方案很明显,如果用户的数据还不可用,让我们将其包装为 getter:
import { Inject, Injectable, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { ICustomer } from './customer.model';
@Injectable({ scope: Scope.REQUEST })
export class CustomerProvider {
get customer(): ICustomer {
return this.req.user;
}
constructor(@Inject(REQUEST) private readonly req) {}
}
现在我们可以在 OrdersService
中使用它:
import {
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { ICustomer } from 'src/customers/customer.model';
import { IOrder } from './order.model';
import { OrdersRepository } from './orders.repository';
import { CustomerProvider } from '../customers/customer.provider';
@Injectable()
export class OrdersService {
private get customer(): ICustomer {
return this.customerProvider.customer;
}
constructor(
private readonly customerProvider: CustomerProvider,
private readonly ordersRepository: OrdersRepository,
) {}
async create(data: Partial<IOrder>): Promise<IOrder> {
const order = { ...data, customerId: this.customer.id };
return this.ordersRepository.create(order);
}
async cancel(orderId: string): Promise<void> {
const order = await this.ordersRepository.findOne(orderId);
if (!order) {
throw new NotFoundException();
}
if (order.customerId !== this.customer.id) {
throw new ForbiddenException();
}
await this.ordersRepository.deleteOne(orderId);
}
async getOrders(): Promise<IOrder[]> {
return this.ordersRepository.find({ customerId: this.customer.id });
}
async getOrder(orderId: string): Promise<IOrder> {
return this.ordersRepository.findOne({
id: orderId,
customerId: this.customer.id,
});
}
}
在 OrdersController
中:
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { OrdersService } from './orders.service';
import { ICustomer } from '../customers/customer.model';
import { Customer } from '../common/customer.decorator';
import { IOrder } from './order.model';
import { AuthGuard } from '../common/auth.guard';
@Controller('orders')
@UseGuards(AuthGuard)
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
@Get('/')
getOrders(): Promise<IOrder[]> {
return this.ordersService.getOrders();
}
@Get('/:id')
getOrder(@Param('id') id: string): Promise<IOrder> {
return this.ordersService.getOrder(id);
}
@Post('/')
create(@Body() data: Partial<IOrder>): Promise<IOrder> {
return this.ordersService.create(data);
}
@Delete('/:id')
cancel(@Param('id') id: string): Promise<void> {
return this.ordersService.cancel(id);
}
}
如大家所见,代码变得更加简洁。 不再需要将 customer
传递给每个方法,它在 OrdersService
中可用。
你可能会问,当某个方法被调用时,我们如何确定 customer
已经被初始化了? 这是一个公平的问题。 如果在控制器类中添加@UseGuards(AuthGuard)
,Nest.JS 会先执行 guard 的canActivate
方法。 此方法检查当前授权数据并将其添加到请求对象中。 换句话说,如果使用授权保护,我们可以确定客户数据在运行 OrdersService
中的某些方法之前已经被初始化。
下一步做什么?
如果我们可以注入一个用户,我们可以以同样的方式注入它的权限。 它可以很容易地用 CASL 实现。 我们甚至可以更进一步,将用户注入存储库,并根据用户在数据库级别的权限过滤数据。 这种技术的潜在用例仅受我们的想象力限制。
缺点
任何方法都有优缺点,“用户注入”也不例外。
- 性能 : 这种方法基于请求范围注入,这意味着将在每个传入请求上创建所有必需服务的新实例。 使用默认注入范围时,所有服务都会在应用程序启动时初始化。 但是,实例创建仍然是一个相当便宜的操作。 我想添加一些真实的性能比较,但用例多种多样。 性能测试结果只会显示我的项目的差异,对于我们的项目,它们可能会显示完全不同的画面。
- 测试 : 为请求范围的服务编写测试比具有默认注入范围的服务要难一些。 但另一方面,我们可以在一个地方模拟用户数据,而不是将模拟传递给每个服务的方法。
总结
Nest.JS 是一个灵活且功能强大的框架。 其依赖注入机制的潜力几乎是无限的。 用户注入是许多技巧之一,可以与 Nest.JS 一起使用。 我并不是说这是组织代码的唯一正确方法。 但我希望这种方法对你有用,并能帮助大家重新思考自己的代码结构。
相关文章
如何在 NestJS 中使用 DTO 进行验证
发布时间:2022/04/24 浏览次数:609 分类:WEB前端
-
这里我们将讨论 NestJS 中的数据传输对象 (DTO) 以及如何使用它们来验证请求。
在 NestJS 中实现外部定义的配置设置
发布时间:2022/04/21 浏览次数:498 分类:WEB前端
-
你可能已经花了一些时间使用了很棒的 NestJS 框架,现在是时候看看如何为你的配置设置获取一些外部定义的值,比如一些键或全局变量
如何在 NestJS 中使用版本控制
发布时间:2022/04/19 浏览次数:316 分类:WEB前端
-
NestJS 是一个用于构建 Node.js 服务器端应用程序的框架。我不会在本文中详细介绍 NestJS 是什么以及为什么要使用它,但我建议查看他们的文档!版本控制已添加到 2021 年 7 月发布的 Nes