有没有优雅的NestJS风格方案在TypeORM中实现实体归属权限校验?
NestJS + TypeORM 实现基于角色的实体归属权限控制
方案一:Guard + 自定义装饰器(推荐,贴合NestJS生态)
核心思路是通过Guard拦截请求,根据当前用户角色动态添加数据过滤规则,配合自定义装饰器标记需要控制的实体字段,避免重复代码。
- 创建自定义装饰器,标记需要关联用户ID的实体字段:
import { SetMetadata } from '@nestjs/common'; export const OWNER_FIELD_KEY = 'ownerField'; export const OwnerField = (field: string) => SetMetadata(OWNER_FIELD_KEY, field);
- 实现权限控制Guard,在Guard中判断角色并生成查询条件:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { OWNER_FIELD_KEY } from './owner-field.decorator'; @Injectable() export class OwnerGuard implements CanActivate { constructor(private reflector: Reflector) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const user = request.user; // 假设用户信息已通过AuthGuard注入 // 管理员直接放行 if (user.role === 'admin') { return true; } // 获取装饰器标记的归属字段(如user_id) const ownerField = this.reflector.get<string>(OWNER_FIELD_KEY, context.getHandler()); if (!ownerField) return true; // 给请求添加过滤条件,供后续Service使用 request.queryFilter = { [ownerField]: user.id }; return true; } }
- 在Controller/Service中使用:
import { Controller, Get, UseGuards, Request } from '@nestjs/common'; import { OwnerGuard, OwnerField } from './owner.guard'; import { PostService } from './post.service'; @Controller('posts') @UseGuards(OwnerGuard) export class PostController { constructor(private postService: PostService) {} @Get() @OwnerField('user_id') // 指定该接口的归属字段为user_id async getPosts(@Request() req) { return this.postService.find(req.queryFilter); } } // PostService示例 @Injectable() export class PostService { constructor(@InjectRepository(Post) private postRepo: Repository<Post>) {} async find(filter: any = {}) { return this.postRepo.find({ where: filter }); } }
方案二:TypeORM Subscriber(自动拦截实体操作)
利用TypeORM订阅器,在实体的查询、更新、删除操作前自动添加权限过滤,无需在业务代码中手动处理。
import { EventSubscriber, EntitySubscriberInterface, BeforeLoad, BeforeUpdate, BeforeRemove } from 'typeorm'; import { Post } from './post.entity'; import { getCurrentUser } from './current-user.util'; // 需实现全局获取当前用户的工具类 @EventSubscriber() export class OwnerSubscriber implements EntitySubscriberInterface<Post> { listenTo() { return Post; } async beforeLoad(event) { const user = getCurrentUser(); if (user.role !== 'admin') { event.queryBuilder.andWhere(`${event.metadata.tableName}.user_id = :userId`, { userId: user.id }); } } async beforeUpdate(event) { const user = getCurrentUser(); if (user.role !== 'admin') { event.queryBuilder.andWhere(`${event.metadata.tableName}.user_id = :userId`, { userId: user.id }); } } async beforeRemove(event) { const user = getCurrentUser(); if (user.role !== 'admin') { event.queryBuilder.andWhere(`${event.metadata.tableName}.user_id = :userId`, { userId: user.id }); } } }
注意:需在TypeORM配置中注册订阅器,且要实现全局获取当前用户的逻辑(如通过AsyncLocalStorage)。
方案三:自定义BaseRepository(封装通用CRUD)
封装基础Repository类,内置权限过滤逻辑,所有需要控制的实体Repository继承该类,统一处理权限。
import { Repository, EntityManager } from 'typeorm'; import { getCurrentUser } from './current-user.util'; export class BaseRepository<T> extends Repository<T> { constructor(entity: any, manager: EntityManager) { super(entity, manager); } async find(options: any = {}) { const user = getCurrentUser(); if (user.role !== 'admin') { options.where = { ...options.where, user_id: user.id }; } return super.find(options); } async findOne(options: any = {}) { const user = getCurrentUser(); if (user.role !== 'admin') { options.where = { ...options.where, user_id: user.id }; } return super.findOne(options); } // 同理封装update、delete等方法 }
使用方式:实体Repository继承BaseRepository
import { EntityRepository } from 'typeorm'; import { Post } from './post.entity'; import { BaseRepository } from './base.repository'; @EntityRepository(Post) export class PostRepository extends BaseRepository<Post> { constructor(manager: EntityManager) { super(Post, manager); } }
方案选择建议
- 小项目用方案一,灵活且贴合NestJS的Guard机制;
- 中大型项目推荐方案二或三,实现全局统一控制,减少业务代码冗余。
内容的提问来源于stack exchange,提问作者Konstantin Bodnia
相关产品推荐
相关产品推荐

