如何简化带JWT支持的Nest.js控制器实现
如何简化带JWT支持的Nest.js控制器实现
我来帮你梳理下怎么简化这个Nest.js控制器的实现,刚好这两个痛点我之前也踩过坑,分享下实用的解决思路:
一、解决重复添加@Req() request: Request & UserJwtPayload的问题
每次在方法里写这么长的参数确实麻烦,我们可以用自定义参数装饰器来封装这个逻辑,以后直接用装饰器就能拿到用户信息,不用重复写类型和请求参数。
步骤1:创建自定义装饰器
新建一个装饰器文件,比如src/common/decorators/get-current-user.decorator.ts:
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; interface UserJwtPayload { user: { id: number, email: string }, } export const GetCurrentUser = createParamDecorator( // 支持传入字段名,比如只取id或email (data: keyof UserJwtPayload['user'] | undefined, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; // 如果传了字段名就返回对应值,否则返回整个user对象 return data ? user[data] : user; }, );
步骤2:在控制器中使用装饰器
修改你的控制器方法,直接用@GetCurrentUser()替代原来的@Req()参数:
@Post() async create( @Body() createDeviceLocationDto: CreateDeviceLocationDto, // 直接拿到用户id,不用再处理request对象 @GetCurrentUser('id') userId: number, ) { // 后续逻辑... }
这样不管有多少个需要用户信息的方法,都只用加一行装饰器就行,代码清爽多了。
二、避免每次从数据库加载User实体的问题
你完全不用每次都查User实体,因为JWT里已经有用户id了,只要你的DeviceLocation实体和User是关联关系(比如多对一),直接用userId关联即可。
步骤1:调整实体和DTO
首先确保DeviceLocation实体里有外键字段(比如userId):
// src/device-locations/entities/device-location.entity.ts import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { User } from '../../user/entities/user.entity'; @Entity() export class DeviceLocation { @PrimaryGeneratedColumn() id: number; @Column() lat: number; @Column() long: number; @Column() description: string; // 保持关联关系,同时显式定义外键字段 @Column() userId: number; @ManyToOne(() => User, user => user.deviceLocations) user: User; }
然后修改CreateDeviceLocationDto,去掉user字段,换成userId(可选,因为我们会自动赋值):
class CreateDeviceLocationDto { lat: number; long: number; description: string; userId?: number; }
步骤2:自动注入userId到请求体(可选,进一步简化)
如果不想在每个方法里手动给userId赋值,还可以用拦截器自动把JWT里的userId加到请求体中:
// src/common/interceptors/add-user-id-to-body.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AddUserIdToBodyInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); // 把userId加到请求体里 if (request.body) { request.body.userId = request.user.id; } return next.handle(); } }
然后在控制器上全局启用这个拦截器:
@UseGuards(JwtAuthGuard) @Controller('device-locations') // 给整个控制器的所有方法自动注入userId @UseInterceptors(AddUserIdToBodyInterceptor) export class DeviceLocationsController { constructor(private readonly deviceLocationsService: DeviceLocationsService) {} @Post() // 现在方法里完全不用管userId了,DTO里已经自动包含了 async create(@Body() createDeviceLocationDto: CreateDeviceLocationDto) { return this.deviceLocationsService.create(createDeviceLocationDto); } }
步骤3:简化Service逻辑
现在Service里直接保存DTO即可,TypeORM会自动处理外键关联:
@Injectable() export class DeviceLocationsService { constructor( @InjectRepository(DeviceLocation) private readonly devicesLocationRepository: Repository<DeviceLocation>, ) {} create(createDeviceLocationDto: CreateDeviceLocationDto) { const location = this.devicesLocationRepository.create(createDeviceLocationDto); return this.devicesLocationRepository.save(location); } }
这样一来,你的控制器彻底摆脱了重复代码,也不用每次查用户实体,代码简洁又高效。
备注:内容来源于stack exchange,提问作者user1635430




