Files
gamegroup/权限管理文档.md
UGREEN USER b25aa5b143 初始化游戏小组管理系统后端项目
- 基于 NestJS + TypeScript + MySQL + Redis 架构
- 完整的模块化设计(认证、用户、小组、游戏、预约等)
- JWT 认证和 RBAC 权限控制系统
- Docker 容器化部署支持
- 添加 CLAUDE.md 项目开发指南
- 配置 .gitignore 忽略文件

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 10:42:06 +08:00

19 KiB
Raw Permalink Blame History

权限管理文档

一、权限架构概述

本系统采用 NestJS 框架,基于 JWT 认证 + 角色权限 的多层权限控制体系。

权限层级

全局守卫APP_GUARD
    ├─ JwtAuthGuardJWT 认证守卫)
    │   └─ 验证用户身份,检查 Token 有效性
    │
    └─ RolesGuard角色守卫
        └─ 验证用户角色权限

二、权限角色定义

2.1 系统级角色UserRole

定义位置:src/common/enums/index.ts

角色 枚举值 描述 权限范围
管理员 admin 系统管理员 全部系统功能
普通用户 user 普通用户 基础功能

用户角色存储在 users 表的 role 字段类型enum

2.2 小组级角色GroupMemberRole

定义位置:src/common/enums/index.ts

角色 枚举值 描述 权限范围
组长 owner 小组创建者 小组所有管理权限
管理员 admin 小组管理员 大部分管理权限
普通成员 member 普通小组成员 基础功能

小组角色存储在 group_members 表的 role 字段类型enum

2.3 会员权限isMember

  • 字段users.isMember(布尔值)
  • 到期时间users.memberExpireAt(日期时间)
  • 特权
    • 创建更多小组非会员1个会员10个
    • 创建子组
    • 审核黑名单
    • 删除任意黑名单记录

三、权限控制组件

3.1 守卫Guards

JwtAuthGuard - JWT 认证守卫

文件位置src/common/guards/jwt-auth.guard.ts

功能

  • 默认所有接口都需要认证
  • 验证 JWT Token 有效性
  • 支持通过 @Public() 装饰器标记公开接口

执行逻辑

1. 检查接口是否标记为 @Public()
2. 如果是公开接口,直接放行
3. 否则,调用 passport  JWT 策略验证 Token
4. 验证失败,抛出 UNAUTHORIZED 异常(错误码:10006
5. 验证成功,将用户信息注入到 request.user

错误响应

{
  "code": 10006,
  "message": "未授权"
}

RolesGuard - 角色守卫

文件位置src/common/guards/roles.guard.ts

功能

  • 检查用户是否拥有所需的系统角色
  • 配合 @Roles() 装饰器使用

执行逻辑

1. 获取接口要求的角色列表(通过 @Roles() 装饰器)
2. 如果未设置角色要求,直接放行
3. 检查 request.user 是否存在
4. 检查用户角色是否在要求的角色列表中
5. 不满足,抛出 NO_PERMISSION 异常(错误码:20003

错误响应

{
  "code": 20003,
  "message": "无权限操作"
}

3.2 装饰器Decorators

@Public() - 公开接口装饰器

文件位置src/common/decorators/public.decorator.ts

用途:标记不需要认证的公开接口

使用示例

@Public()
@Post('login')
async login(@Body() loginDto: LoginDto) {
  return this.authService.login(loginDto);
}

@Roles() - 角色装饰器

文件位置src/common/decorators/roles.decorator.ts

用途:限制接口只允许特定角色访问

使用示例

@Roles(UserRole.ADMIN)
@Delete(':id')
async deleteUser(@Param('id') id: string) {
  return this.usersService.remove(id);
}

@CurrentUser() - 当前用户装饰器

文件位置src/common/decorators/current-user.decorator.ts

用途:获取当前登录用户信息

使用示例

@Get('me')
async getProfile(@CurrentUser() user: User) {
  return this.usersService.findOne(user.id);
}

四、权限检查接口分类

4.1 公开接口(无需认证)

认证模块Auth

方法 路径 功能 权限
POST /auth/register 用户注册 公开
POST /auth/login 用户登录 公开
POST /auth/refresh 刷新令牌 公开

游戏模块Games

方法 路径 功能 权限
GET /games 获取游戏列表 公开
GET /games/popular 获取热门游戏 公开
GET /games/tags 获取游戏标签 公开
GET /games/platforms 获取游戏平台 公开
GET /games/:id 获取游戏详情 公开

4.2 需要认证的接口JWT

用户模块Users

方法 路径 功能 权限要求
GET /users/me 获取当前用户信息 登录用户
GET /users/:id 获取用户信息 登录用户
PUT /users/me 更新用户信息 本人
PUT /users/me/password 修改密码 本人

小组模块Groups

方法 路径 功能 权限要求
POST /groups 创建小组 登录用户(有创建限制)
POST /groups/join 加入小组 登录用户
GET /groups/my 获取我的小组 登录用户
GET /groups/:id 获取小组详情 登录用户
PUT /groups/:id 更新小组信息 Owner/Admin
PUT /groups/:id/members/role 设置成员角色 Owner/Admin
DELETE /groups/:id/members 踢出成员 Owner/Admin
DELETE /groups/:id/leave 退出小组 本人
DELETE /groups/:id 解散小组 Owner

预约模块Appointments

方法 路径 功能 权限要求
POST /appointments 创建预约 登录用户
GET /appointments 获取预约列表 登录用户
GET /appointments/my 获取我的预约 登录用户
GET /appointments/:id 获取预约详情 登录用户
POST /appointments/join 加入预约 登录用户
PUT /appointments/:id 更新预约 发起人
PUT /appointments/:id/confirm 确认预约 发起人
PUT /appointments/:id/complete 完成预约 发起人
DELETE /appointments/:id/leave 退出预约 参与者
DELETE /appointments/:id 取消预约 发起人

黑名单模块Blacklist

方法 路径 功能 权限要求
POST /blacklist 提交举报 登录用户
GET /blacklist 查询黑名单列表 登录用户
GET /blacklist/check/:targetGameId 检查游戏ID 登录用户
GET /blacklist/:id 查询记录详情 登录用户
PATCH /blacklist/:id/review 审核黑名单 会员
DELETE /blacklist/:id 删除记录 举报人或会员

资产模块Assets

方法 路径 功能 权限要求
POST /assets 创建资产 小组Owner/Admin
GET /assets/group/:groupId 查询小组资产 登录用户
GET /assets/:id 查询资产详情 登录用户
PATCH /assets/:id 更新资产 小组Owner/Admin
POST /assets/:id/borrow 借用资产 小组成员
POST /assets/:id/return 归还资产 借用人
GET /assets/:id/logs 查询借还记录 登录用户
DELETE /assets/:id 删除资产 小组Owner/Admin

游戏模块Games - 管理功能)

方法 路径 功能 权限要求
POST /games 创建游戏 登录用户
PUT /games/:id 更新游戏 登录用户
DELETE /games/:id 删除游戏 登录用户

五、业务层权限检查

5.1 小组权限检查

实现位置src/modules/groups/groups.service.ts

创建小组限制

// 非会员最多1个小组
// 会员最多10个小组
if (!user.isMember && ownedGroupsCount >= 1) {
  throw new BadRequestException('非会员最多只能创建1个小组');
}

if (user.isMember && ownedGroupsCount >= 10) {
  throw new BadRequestException('会员最多只能创建10个小组');
}

创建子组权限

// 只有会员才能创建子组
if (createGroupDto.parentId && !user.isMember) {
  throw new ForbiddenException('非会员不能创建子组');
}

小组管理权限

// 检查用户是否为组长或管理员
const member = await this.groupMemberRepository.findOne({
  where: { groupId, userId },
});

if (!member || (member.role !== GroupMemberRole.OWNER && 
                member.role !== GroupMemberRole.ADMIN)) {
  throw new ForbiddenException('需要管理员权限');
}

解散小组权限

// 只有组长可以解散小组
if (member.role !== GroupMemberRole.OWNER) {
  throw new ForbiddenException('只有组长可以解散小组');
}

5.2 黑名单权限检查

实现位置src/modules/blacklist/blacklist.service.ts

审核权限

// 只有会员才能审核黑名单
if (!user || !user.isMember) {
  throw new ForbiddenException('需要会员权限');
}

删除权限

// 举报人或会员可以删除记录
if (blacklist.reporterId !== userId && !user.isMember) {
  throw new ForbiddenException('无权限操作');
}

5.3 资产管理权限检查

实现位置src/modules/assets/assets.service.ts

创建/更新/删除资产

// 需要是小组的 Owner 或 Admin
const membership = await this.groupMemberRepository.findOne({
  where: { groupId, userId },
});

if (!membership || 
    (membership.role !== GroupMemberRole.ADMIN && 
     membership.role !== GroupMemberRole.OWNER)) {
  throw new ForbiddenException('需要管理员权限');
}

借用资产

// 必须是小组成员才能借用
const membership = await this.groupMemberRepository.findOne({
  where: { groupId: asset.groupId, userId },
});

if (!membership) {
  throw new ForbiddenException('您不是该小组成员');
}

六、权限相关错误码

定义位置:src/common/interfaces/response.interface.ts

错误码 枚举值 消息 说明
10004 TOKEN_INVALID Token无效 JWT验证失败
10005 TOKEN_EXPIRED Token已过期 JWT已过期
10006 UNAUTHORIZED 未授权 未登录或认证失败
20003 NO_PERMISSION 无权限操作 角色权限不足
60002 INVALID_OPERATION 无效操作 业务逻辑不允许

七、权限配置

7.1 全局守卫注册

文件位置src/app.module.ts

providers: [
  // 全局 JWT 认证守卫
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
  // 全局角色守卫
  {
    provide: APP_GUARD,
    useClass: RolesGuard,
  },
]

执行顺序

  1. JwtAuthGuard先执行- 验证身份
  2. RolesGuard后执行- 验证角色

7.2 JWT 策略配置

文件位置src/modules/auth/jwt.strategy.ts

从 JWT Payload 中提取用户信息并注入到 request.user


八、权限使用最佳实践

8.1 Controller 层

// 1. 使用 @Public() 标记公开接口
@Public()
@Post('login')
async login(@Body() loginDto: LoginDto) {
  return this.authService.login(loginDto);
}

// 2. 使用 @CurrentUser() 获取当前用户
@Get('me')
async getProfile(@CurrentUser() user: User) {
  return this.usersService.findOne(user.id);
}

// 3. 使用 @Roles() 限制角色(目前项目中未使用)
@Roles(UserRole.ADMIN)
@Delete(':id')
async deleteUser(@Param('id') id: string) {
  return this.usersService.remove(id);
}

// 4. 使用 @UseGuards() 应用特定守卫
@UseGuards(JwtAuthGuard)
@Controller('users')
export class UsersController {}

8.2 Service 层

// 1. 在业务逻辑中检查权限
async update(userId: string, groupId: string, updateDto: UpdateGroupDto) {
  // 检查用户是否有权限
  const member = await this.groupMemberRepository.findOne({
    where: { groupId, userId },
  });
  
  if (!member || member.role === GroupMemberRole.MEMBER) {
    throw new ForbiddenException({
      code: ErrorCode.NO_PERMISSION,
      message: ErrorMessage[ErrorCode.NO_PERMISSION],
    });
  }
  
  // 执行业务逻辑
  // ...
}

// 2. 根据用户角色返回不同数据
async findOne(assetId: string, userId: string) {
  const asset = await this.assetRepository.findOne({ 
    where: { id: assetId } 
  });
  
  // 检查是否为小组管理员,决定是否显示加密信息
  const membership = await this.groupMemberRepository.findOne({
    where: { groupId: asset.groupId, userId },
  });
  
  if (membership?.role === GroupMemberRole.OWNER || 
      membership?.role === GroupMemberRole.ADMIN) {
    // 解密敏感信息
    asset.accountCredentials = this.decrypt(asset.accountCredentials);
  } else {
    // 隐藏敏感信息
    delete asset.accountCredentials;
  }
  
  return asset;
}

九、权限扩展建议

9.1 当前缺失的权限功能

  1. 系统管理员角色未充分利用

    • UserRole.ADMIN 已定义但未在Controller层使用 @Roles() 装饰器
    • 建议:对管理功能使用 @Roles(UserRole.ADMIN) 限制
  2. 小组级权限检查不够细化

    • 当前只区分 Owner/Admin/Member
    • 建议:可以增加更细粒度的权限点(如:财务管理、成员管理等)
  3. 缺少权限审计日志

    • 建议:记录敏感操作的权限检查日志

9.2 推荐改进

1. 使用 @Roles() 装饰器

// 在控制器中明确标注需要管理员权限的接口
@Roles(UserRole.ADMIN)
@Patch(':id/review')
async review(@CurrentUser() user, @Param('id') id: string) {
  return this.blacklistService.review(user.id, id, reviewDto);
}

2. 创建自定义权限守卫

// group-permission.guard.ts
@Injectable()
export class GroupPermissionGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredPermissions = this.reflector.get<string[]>(
      'permissions',
      context.getHandler(),
    );
    
    if (!requiredPermissions) {
      return true;
    }
    
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const groupId = request.params.groupId;
    
    // 检查用户在小组中的权限
    // ...
    
    return hasPermission;
  }
}

3. 添加权限装饰器

// group-permissions.decorator.ts
export const GroupPermissions = (...permissions: string[]) => 
  SetMetadata('permissions', permissions);

// 使用
@GroupPermissions('manage_members', 'manage_assets')
@Put(':groupId/settings')
async updateSettings() {}

十、权限测试建议

10.1 单元测试

  • 测试 JwtAuthGuard 的认证逻辑
  • 测试 RolesGuard 的角色验证
  • 测试各 Service 的权限检查方法

10.2 集成测试

  • 测试未登录访问受保护接口
  • 测试普通用户访问管理员接口
  • 测试跨小组权限隔离
  • 测试会员与非会员权限差异

10.3 E2E 测试

  • 完整的用户注册-登录-操作流程
  • 权限升级后的功能可用性
  • Token 过期后的行为

十一、安全建议

  1. Token 管理

    • JWT Secret 使用环境变量配置
    • 设置合理的 Token 过期时间
    • 实现 Refresh Token 机制(已实现)
  2. 密码安全

    • 使用强加密算法存储密码
    • 实施密码强度策略
    • 防止暴力破解
  3. 敏感信息保护

    • 资产凭据加密存储(已实现)
    • 根据权限动态脱敏
    • API 响应中排除敏感字段
  4. 防护措施

    • 实施请求限流
    • 记录异常访问
    • 添加 CORS 配置
    • 使用 Helmet 中间件

十二、权限流程图

JWT 认证流程

用户请求 → JwtAuthGuard 
           ↓
    检查 @Public()
           ↓
    是 → 直接放行
    否 → 验证 JWT Token
           ↓
      有效 → 注入 user 到 request
      无效 → 返回 401 错误
           ↓
    RolesGuard
           ↓
    检查 @Roles()
           ↓
    未设置 → 放行
    已设置 → 验证用户角色
           ↓
      匹配 → 放行到 Controller
      不匹配 → 返回 403 错误

小组权限检查流程

用户操作小组资源
      ↓
获取小组成员信息
      ↓
检查成员角色
      ↓
  Owner → 全部权限
  Admin → 管理权限(不含解散)
  Member → 基础权限
  非成员 → 拒绝访问

附录:相关文件清单

核心文件

配置文件

业务模块


文档版本v1.0
更新日期2025-12-20
维护者:开发团队