From b6f14959811b1d43c5086ed07ba7387c7f6d4ddf Mon Sep 17 00:00:00 2001 From: tone <3341154833@qq.com> Date: Wed, 7 May 2025 23:14:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9D=83=E9=99=90=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E5=AE=88=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../decorators/permissions.decorator.ts | 3 ++ .../src/common/decorators/role.decorator.ts | 3 ++ .../src/common/guard/permission.guard.ts | 40 +++++++++++++++++++ .../src/common/guard/roles.guard.ts | 36 +++++++++++++++++ .../entities/user-role.entity.ts | 0 tone-page-server/src/role/role.module.ts | 9 ++++- .../src/role/services/permission.service.ts | 26 ++++++++++++ .../role/services/role-permission.service.ts | 23 +++++++++++ .../src/role/services/role.service.ts | 26 ++++++++++++ .../src/role/services/user-role.service.ts | 35 ++++++++++++++++ tone-page-server/src/user/user.module.ts | 3 +- 11 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 tone-page-server/src/common/decorators/permissions.decorator.ts create mode 100644 tone-page-server/src/common/decorators/role.decorator.ts create mode 100644 tone-page-server/src/common/guard/permission.guard.ts create mode 100644 tone-page-server/src/common/guard/roles.guard.ts rename tone-page-server/src/{user => role}/entities/user-role.entity.ts (100%) create mode 100644 tone-page-server/src/role/services/permission.service.ts create mode 100644 tone-page-server/src/role/services/role-permission.service.ts create mode 100644 tone-page-server/src/role/services/role.service.ts create mode 100644 tone-page-server/src/role/services/user-role.service.ts diff --git a/tone-page-server/src/common/decorators/permissions.decorator.ts b/tone-page-server/src/common/decorators/permissions.decorator.ts new file mode 100644 index 0000000..4d9055a --- /dev/null +++ b/tone-page-server/src/common/decorators/permissions.decorator.ts @@ -0,0 +1,3 @@ +import { SetMetadata } from "@nestjs/common"; + +export const Permissions = (...permissions: string[]) => SetMetadata('permissions', permissions); \ No newline at end of file diff --git a/tone-page-server/src/common/decorators/role.decorator.ts b/tone-page-server/src/common/decorators/role.decorator.ts new file mode 100644 index 0000000..fe0b8c3 --- /dev/null +++ b/tone-page-server/src/common/decorators/role.decorator.ts @@ -0,0 +1,3 @@ +import { SetMetadata } from "@nestjs/common"; + +export const Roles = (...roles: string[]) => SetMetadata('roles', roles); \ No newline at end of file diff --git a/tone-page-server/src/common/guard/permission.guard.ts b/tone-page-server/src/common/guard/permission.guard.ts new file mode 100644 index 0000000..2f6013c --- /dev/null +++ b/tone-page-server/src/common/guard/permission.guard.ts @@ -0,0 +1,40 @@ +import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { Reflector } from '@nestjs/core'; +import { PermissionService } from "src/role/services/permission.service"; +import { RolePermissionService } from "src/role/services/role-permission.service"; +import { UserRoleService } from "src/role/services/user-role.service"; + +@Injectable() +export class PermissionGuard implements CanActivate { + constructor( + private reflector: Reflector, + private readonly userRoleService: UserRoleService, + private readonly rolePermissionService: RolePermissionService, + private readonly permissionService: PermissionService, + ) { } + + async canActivate(context: ExecutionContext): Promise { + const requiredPermissions = this.reflector.getAllAndOverride('permissions', [ + context.getHandler(), + context.getClass(), + ]); + + if (!requiredPermissions) return true; + + const request = context.switchToHttp().getRequest(); + const userId = request.user?.userId; + + if (!userId) return false; + + // 查询用户拥有的有效角色ID + const userRoleIds = await this.userRoleService.findValidRoleIdsByUserId(userId); + + // 查询用户拥有的有效角色ID对应的权限ID + const userPermissionIds = await this.rolePermissionService.findPermissionIdsByRoleIds(userRoleIds); + + // 查询用户拥有的权限ID对应的权限名 + const userPermissionNames = await this.permissionService.findPermissionNamesByPermissionIds(userPermissionIds); + + return requiredPermissions.every(permission => userPermissionNames.includes(permission)) + } +} \ No newline at end of file diff --git a/tone-page-server/src/common/guard/roles.guard.ts b/tone-page-server/src/common/guard/roles.guard.ts new file mode 100644 index 0000000..214496b --- /dev/null +++ b/tone-page-server/src/common/guard/roles.guard.ts @@ -0,0 +1,36 @@ +import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { Reflector } from '@nestjs/core'; +import { RoleService } from "src/role/services/role.service"; +import { UserRoleService } from "src/role/services/user-role.service"; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor( + private reflector: Reflector, + private readonly userRoleService: UserRoleService, + private readonly roleService: RoleService, + ) { } + + async canActivate(context: ExecutionContext): Promise { + const requiredRoles = this.reflector.getAllAndOverride('roles', [ + context.getHandler(), + context.getClass(), + ]); + + if (!requiredRoles) return true; + + + const request = context.switchToHttp().getRequest(); + const userId = request.user?.userId; + + if (!userId) return false; + + // 查询用户拥有的有效角色Id + const userRoleIds = await this.userRoleService.findValidRoleIdsByUserId(userId); + + // 查询用户角色Id对应的角色名 + const userRoleNames = await this.roleService.findRoleNamesByRoleIds(userRoleIds); + + return requiredRoles.some(role => userRoleNames.includes(role)); + } +} \ No newline at end of file diff --git a/tone-page-server/src/user/entities/user-role.entity.ts b/tone-page-server/src/role/entities/user-role.entity.ts similarity index 100% rename from tone-page-server/src/user/entities/user-role.entity.ts rename to tone-page-server/src/role/entities/user-role.entity.ts diff --git a/tone-page-server/src/role/role.module.ts b/tone-page-server/src/role/role.module.ts index 8c0dd79..ba66ff0 100644 --- a/tone-page-server/src/role/role.module.ts +++ b/tone-page-server/src/role/role.module.ts @@ -3,8 +3,15 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Role } from './entities/role.entity'; import { Permission } from './entities/permission.entity'; import { RolePermission } from './entities/role-permission.entity'; +import { RolePermissionService } from './services/role-permission.service'; +import { RoleService } from './services/role.service'; +import { UserRoleService } from './services/user-role.service'; +import { UserRole } from './entities/user-role.entity'; +import { PermissionService } from './services/permission.service'; @Module({ - imports: [TypeOrmModule.forFeature([Role, Permission, RolePermission])] + imports: [TypeOrmModule.forFeature([Role, Permission, RolePermission, UserRole])], + providers: [RolePermissionService, RoleService, UserRoleService, PermissionService], + exports: [RolePermissionService, RoleService, UserRoleService, PermissionService], }) export class RoleModule { } diff --git a/tone-page-server/src/role/services/permission.service.ts b/tone-page-server/src/role/services/permission.service.ts new file mode 100644 index 0000000..f6642f8 --- /dev/null +++ b/tone-page-server/src/role/services/permission.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Permission } from "../entities/permission.entity"; +import { In, Repository } from "typeorm"; + +@Injectable() +export class PermissionService { + + constructor( + @InjectRepository(Permission) + private readonly permissionRepository: Repository, + ) { } + + async findPermissionNamesByPermissionIds(permissionIds: string[]): Promise { + const permissions = await this.findPermissionsByPermissionIds(permissionIds); + return permissions.map(permission => permission.name); + } + + async findPermissionsByPermissionIds(permissionIds: string[]): Promise { + return this.permissionRepository.find({ + where: { + id: In(permissionIds), + } + }) + } +} \ No newline at end of file diff --git a/tone-page-server/src/role/services/role-permission.service.ts b/tone-page-server/src/role/services/role-permission.service.ts new file mode 100644 index 0000000..82a1066 --- /dev/null +++ b/tone-page-server/src/role/services/role-permission.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { RolePermission } from "../entities/role-permission.entity"; +import { In, Repository } from "typeorm"; + +@Injectable() +export class RolePermissionService { + + constructor( + @InjectRepository(RolePermission) + private readonly rolePermissionRepository: Repository, + ) { } + + async findPermissionIdsByRoleIds(roleIds: string[]): Promise { + const rolePermissions = await this.rolePermissionRepository.find({ + where: { + roleId: In(roleIds), + } + }); + + return rolePermissions.map(rp => rp.permissionId); + } +} \ No newline at end of file diff --git a/tone-page-server/src/role/services/role.service.ts b/tone-page-server/src/role/services/role.service.ts new file mode 100644 index 0000000..0324e7e --- /dev/null +++ b/tone-page-server/src/role/services/role.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Role } from "../entities/role.entity"; +import { In, Repository } from "typeorm"; + +@Injectable() +export class RoleService { + + constructor( + @InjectRepository(Role) + private readonly roleRepository: Repository, + ) { } + + async findRoleNamesByRoleIds(roleIds: string[]): Promise { + const roles = await this.findRolesByRoleIds(roleIds); + return roles.map(role => role.name); + } + + async findRolesByRoleIds(roleIds: string[]): Promise { + return this.roleRepository.find({ + where: { + id: In(roleIds), + } + }) + } +} \ No newline at end of file diff --git a/tone-page-server/src/role/services/user-role.service.ts b/tone-page-server/src/role/services/user-role.service.ts new file mode 100644 index 0000000..7e558cd --- /dev/null +++ b/tone-page-server/src/role/services/user-role.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { UserRole } from "src/role/entities/user-role.entity"; +import { IsNull, MoreThanOrEqual, Repository } from "typeorm"; + +@Injectable() +export class UserRoleService { + constructor( + @InjectRepository(UserRole) + private readonly userRoleRepository: Repository, + ) { } + + async findValidRoleIdsByUserId(userId: string): Promise { + return (await this.findValidRolesByUserId(userId)).map(ur => ur.roleId); + } + + async findValidRolesByUserId(userId: string) { + const now = new Date(); + + return this.userRoleRepository.find({ + where: [ + { + userId, + isEnabled: true, + expiredAt: MoreThanOrEqual(now), + }, + { + userId, + isEnabled: true, + expiredAt: IsNull(), + } + ] + }) + } +} \ No newline at end of file diff --git a/tone-page-server/src/user/user.module.ts b/tone-page-server/src/user/user.module.ts index f697452..00f7280 100644 --- a/tone-page-server/src/user/user.module.ts +++ b/tone-page-server/src/user/user.module.ts @@ -6,11 +6,10 @@ import { UserService } from './user.service'; import { UserSession } from './entities/user-session.entity'; import { AuthModule } from 'src/auth/auth.module'; import { UserSessionService } from './services/user-session.service'; -import { UserRole } from './entities/user-role.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([User, UserSession, UserRole]), + TypeOrmModule.forFeature([User, UserSession]), forwardRef(() => AuthModule),// 解决循环依赖问题 ], controllers: [UserController],