25

我正在使用 NestJS 开发后端(顺便说一句,这太棒了)。我有一个类似于以下示例的“标准获取实体情况的单个实例”。

@Controller('user')
export class UserController {
    constructor(private readonly userService: UserService) {}
    ..
    ..
    ..
    @Get(':id')
    async findOneById(@Param() params): Promise<User> {
        return userService.findOneById(params.id);
    }

这非常简单且有效 - 但是,如果用户不存在,则服务返回 undefined 并且控制器返回 200 状态代码和空响应。

为了使控制器返回 404,我想出了以下内容:

    @Get(':id')
    async findOneById(@Res() res, @Param() params): Promise<User> {
        const user: User = await this.userService.findOneById(params.id);
        if (user === undefined) {
            res.status(HttpStatus.NOT_FOUND).send();
        }
        else {
            res.status(HttpStatus.OK).json(user).send();
        }
    }
    ..
    ..

这可行,但代码更多(是的,它可以重构)。

这真的可以使用装饰器来处理这种情况:

    @Get(':id')
    @OnUndefined(404)
    async findOneById(@Param() params): Promise<User> {
        return userService.findOneById(params.id);
    }

任何人都知道这样做的装饰器,或者比上述解决方案更好的解决方案?

4

7 回答 7

23

做到这一点的最短方法是

@Get(':id')
async findOneById(@Param() params): Promise<User> {
    const user: User = await this.userService.findOneById(params.id);
    if (user === undefined) {
        throw new BadRequestException('Invalid user');
    }
    return user;
}

装饰器在这里没有意义,因为它会有相同的代码。

注意: BadRequestException是从@nestjs/common;导入的

编辑

一段时间后,我提出了另一个解决方案,它是 DTO 中的装饰器:

import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint } from 'class-validator';
import { createQueryBuilder } from 'typeorm';

@ValidatorConstraint({ async: true })
export class IsValidIdConstraint {

    validate(id: number, args: ValidationArguments) {
        const tableName = args.constraints[0];
        return createQueryBuilder(tableName)
            .where({ id })
            .getOne()
            .then(record => {
                return record ? true : false;
            });
    }
}

export function IsValidId(tableName: string, validationOptions?: ValidationOptions) {
    return (object, propertyName: string) => {
        registerDecorator({
            target: object.constructor,
            propertyName,
            options: validationOptions,
            constraints: [tableName],
            validator: IsValidIdConstraint,
        });
    };
}

然后在你的 DTO 中:

export class GetUserParams {
    @IsValidId('user', { message: 'Invalid User' })
    id: number;
}

希望它可以帮助某人。

于 2018-04-12T08:29:52.727 回答
11

没有内置的装饰器,但你可以创建一个拦截器来检查返回值并抛出一个NotFoundExceptionon undefined

拦截器

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle()
      .pipe(tap(data => {
        if (data === undefined) throw new NotFoundException();
      }));
  }
}

然后,您可以Interceptor通过将其添加到单个端点来使用:

@Get(':id')
@UseInterceptors(NotFoundInterceptor)
findUserById(@Param() params): Promise<User> {
    return this.userService.findOneById(params.id);
}

或您的所有端点Controller

@Controller('user')
@UseInterceptors(NotFoundInterceptor)
export class UserController {

动态拦截器

您还可以将值传递给您的拦截器,以自定义每个端点的行为。

在构造函数中传递参数:

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  constructor(private errorMessage: string) {}
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
    return stream$
      .pipe(tap(data => {
        if (data === undefined) throw new NotFoundException(this.errorMessage);
                                                            ^^^^^^^^^^^^^^^^^
      }));
  }
}

然后使用以下命令创建拦截器new

@Get(':id')
@UseInterceptors(new NotFoundInterceptor('No user found for given userId'))
findUserById(@Param() params): Promise<User> {
    return this.userService.findOneById(params.id);
}
于 2018-08-19T13:54:37.003 回答
4

@Kim Kern对最新 Nestjs 版本的回答的更新版本:

正如Nestjs 文档中所说:

拦截器 API 也得到了简化。此外,由于社区报告了此问题,因此需要进行更改。

更新代码:

import { Injectable, NestInterceptor, ExecutionContext, NotFoundException, CallHandler } from '@nestjs/common';
import { Observable, pipe } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  constructor(private errorMessage: string) { }

  intercept(context: ExecutionContext, stream$: CallHandler): Observable<any> {
    return stream$
      .handle()
      .pipe(tap(data => {
        if (data === undefined) { throw new NotFoundException(this.errorMessage); }
      }));
  }
}


于 2020-03-13T11:10:57.543 回答
1

如果是简单的情况,我通常会以这种懒惰的方式来做,而不会添加额外的绒毛:

import {NotFoundException} from '@nestjs/common'
...
@Get(':id')
async findOneById(@Param() params): Promise<User> {
    const user: User = await this.userService.findOneById(params.id)
    if (!user) throw new NotFoundException('User Not Found')
    return user
}
于 2019-12-24T21:10:13.817 回答
0

您可以使用以下功能连同标题内的正确状态代码一起发送所需的响应。

在控制器类中的路由处理程序内部:

this.whateverService.getYourEntity(
  params.id
)
.then(result => {
  return res.status(HttpStatus.OK).json(result)
})
.catch(err => {
  return res.status(HttpStatus.NOT_FOUND).json(err)
})

为此,您必须在服务方法中拒绝Promise,如下所示:

const entity = await this.otherService
  .getEntityById(id)

if (!entity) {
  return Promise.reject({
    statusCode: 404,
    message: 'Entity not found'
  })
} 

return Promise.resolve(entity)

在这里,我只是在服务类中使用了另一个服务。你当然可以直接获取你的数据库或者做任何需要的事情来获取你的实体。

于 2021-06-29T02:26:50.713 回答
0
export const OnUndefined = (
  Error: new () => HttpException = NotFoundException,
) => {
  return (
    _target: unknown,
    _propKey: string,
    descriptor: PropertyDescriptor,
  ) => {
    const original = descriptor.value;
    const mayThrow = (r: unknown) => {
      if (undefined === r) throw new Error();
      return r;
    };
    descriptor.value = function (...args: unknown[]) {
      const r = Reflect.apply(original, this, args);
      if ('function' === typeof r?.then) return r.then(mayThrow);
      return mayThrow(r);
    };
  };
};

然后像这样使用

@Get(':id')
@OnUndefined()
async findOneById(@Param() params): Promise<User> {
    return userService.findOneById(params.id);
}
于 2021-10-30T12:20:24.590 回答
0

OnUndefined 函数创建 e 装饰器,必须如上所述使用。

如果服务返回未定义的响应(搜索到的 id 不存在),则控制器返回 404 (NotFoundException) 或作为参数传递给 @OnUndefined 装饰器的任何其他异常

于 2021-11-08T12:09:22.543 回答