You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何简化带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

火山引擎 最新活动