You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS多用户后端最优目录结构及项目模块设计咨询

项目模块设计建议:单模块配合API角色守卫更合适

优先选择单项目模块配合API角色守卫的方案,这更符合NestJS的设计理念,也能帮你减少冗余代码、降低后期维护成本,原因如下:

1. 避免无意义的代码冗余

三类用户的核心操作都围绕「项目」实体展开,单独为每个角色建模块,会重复定义项目实体、DTO、基础CRUD逻辑。后续如果要修改项目字段、调整基础业务规则,你得同时改三个模块的代码,工作量翻倍还容易出错。

2. 契合NestJS原生权限控制机制

NestJS本身就提供了Guard(守卫)和Decorator(装饰器)来实现角色权限控制,用这套机制来区分不同角色的接口访问权限,比拆分模块更简洁高效。

简单实现示例

第一步:定义角色枚举

// src/common/enums/role.enum.ts
export enum Role {
  Client = 'client',
  Freelancer = 'freelancer',
  Admin = 'admin',
}

第二步:创建角色装饰器

用来标记接口允许访问的角色:

// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Role } from '../enums/role.enum';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

第三步:实现角色守卫

负责验证当前用户的角色是否符合接口要求:

// src/common/guards/role.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
import { Role } from '../enums/role.enum';

@Injectable()
export class RoleGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    // 没有标记角色的接口,默认允许访问
    if (!requiredRoles) return true;
    
    const { user } = context.switchToHttp().getRequest();
    // 检查用户角色是否在允许的列表中
    return requiredRoles.some(role => user.role === role);
  }
}

第四步:在项目模块中使用

// src/projects/projects.controller.ts
import { Controller, Post, Put, Get, Param, UseGuards } from '@nestjs/common';
import { Roles } from '../common/decorators/roles.decorator';
import { Role } from '../common/enums/role.enum';
import { RoleGuard } from '../common/guards/role.guard';
import { ProjectsService } from './projects.service';

@Controller('projects')
@UseGuards(RoleGuard) // 给整个控制器应用守卫,也可以单独给某个接口加
export class ProjectsController {
  constructor(private readonly projectsService: ProjectsService) {}

  // 客户创建项目
  @Post()
  @Roles(Role.Client)
  createProject() {
    return this.projectsService.create();
  }

  // 客户修改项目
  @Put(':id')
  @Roles(Role.Client)
  updateProject(@Param('id') id: string) {
    return this.projectsService.update(id);
  }

  // 自由职业者申请项目
  @Post(':id/apply')
  @Roles(Role.Freelancer)
  applyForProject(@Param('id') id: string) {
    return this.projectsService.apply(id);
  }

  // 管理员查看所有项目
  @Get()
  @Roles(Role.Admin)
  getAllProjects() {
    return this.projectsService.findAll();
  }
}

例外情况:什么时候需要拆分模块?

如果未来某类用户的项目业务逻辑变得极度独立,比如客户的项目流程和自由职业者的项目流程完全割裂,甚至需要不同的数据库表结构,那时再考虑拆分模块也不迟。初期过度拆分属于设计冗余,反而增加负担。

内容的提问来源于stack exchange,提问作者Ranjit Kumar Pandit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 19:27:23