如何在NestJS与MongoDB中实现用户限时封禁功能?
NestJS + MongoDB 实现用户限时封禁方案
1. 数据库模型调整
仅靠role字段无法满足限时封禁需求,需要扩展用户集合字段:
role: Number,保留原逻辑(1=user,0=ban)banExpireAt: Date,存储封禁到期时间,未封禁时设为null
Mongoose Schema 示例:
import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose'; import { Document } from 'mongoose'; export type UserDocument = User & Document; @Schema() export class User { @Prop({ required: true, enum: [0, 1], default: 1 }) role: number; @Prop({ type: Date, default: null }) banExpireAt: Date; // 其他用户字段(如用户名、密码等)... } export const UserSchema = SchemaFactory.createForClass(User);
2. 管理员封禁接口实现
实现管理员专属的封禁接口,核心逻辑是将用户角色改为封禁状态,并设置7天后的到期时间。
控制器示例
import { Controller, Post, Param, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { AdminGuard } from '../auth/admin.guard'; @Controller('admin/users') @UseGuards(AdminGuard) // 仅管理员可访问 export class AdminUserController { constructor(private readonly userService: UserService) {} @Post(':id/ban') async banUser(@Param('id') userId: string) { const sevenDaysLater = new Date(); sevenDaysLater.setDate(sevenDaysLater.getDate() + 7); return this.userService.updateUser(userId, { role: 0, banExpireAt: sevenDaysLater, }); } }
服务层方法示例
async updateUser(userId: string, updateData: Partial<User>) { return this.userModel.findByIdAndUpdate(userId, updateData, { new: true }).exec(); }
3. 自动解封机制
提供两种可选方案,按需选择:
方案A:定时任务(主动触发)
利用NestJS的@nestjs/schedule模块,定期扫描并解封到期用户。
- 在
AppModule中启用Schedule模块:
import { ScheduleModule } from '@nestjs/schedule'; @Module({ imports: [ScheduleModule.forRoot(), ...], }) export class AppModule {}
- 创建定时任务:
import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { UserService } from './user.service'; @Injectable() export class BanScheduleService { private readonly logger = new Logger(BanScheduleService.name); constructor(private readonly userService: UserService) {} // 每天凌晨1点执行一次 @Cron('0 0 1 * * *') async handleBanExpiration() { this.logger.log('开始检查到期封禁用户'); const now = new Date(); const result = await this.userService.updateMany( { role: 0, banExpireAt: { $lte: now } }, { role: 1, banExpireAt: null }, ); this.logger.log(`完成解封,共处理${result.modifiedCount}个用户`); } }
方案B:惰性处理(被动触发)
用户登录或发起请求时自动检查封禁状态,若到期则先解封再处理请求,适合中小规模应用。
在登录守卫中添加逻辑:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { UserService } from '../user/user.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private readonly userService: UserService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const user = request.user; // 假设已通过JWT解析获取用户信息 if (user.role === 0) { const now = new Date(); if (user.banExpireAt <= now) { // 封禁到期,自动解封 await this.userService.updateUser(user._id, { role: 1, banExpireAt: null }); user.role = 1; // 更新内存中的用户状态 return true; } else { // 仍在封禁期,拒绝访问并返回剩余时长 const remainingTime = user.banExpireAt.getTime() - now.getTime(); request.banRemaining = this.formatRemainingTime(remainingTime); return false; } } return true; } // 格式化剩余时长为天时分秒 private formatRemainingTime(milliseconds: number): string { const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24)); const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); return `${days}天${hours}小时${minutes}分`; } }
4. 封禁剩余时长查询接口
给用户提供查询自身封禁剩余时长的接口:
import { Controller, Get, UseGuards, Request } from '@nestjs/common'; import { AuthGuard } from '../auth/auth.guard'; @Controller('users') @UseGuards(AuthGuard) export class UserController { @Get('ban-remaining') getBanRemainingTime(@Request() req) { if (req.user.role === 0) { const now = new Date(); const remainingTime = req.user.banExpireAt.getTime() - now.getTime(); if (remainingTime <= 0) { return { message: '封禁已到期', remainingTime: '0' }; } return { remainingTime: this.formatRemainingTime(remainingTime) }; } return { message: '未被封禁', remainingTime: '0' }; } private formatRemainingTime(milliseconds: number): string { const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24)); const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); return `${days}天${hours}小时${minutes}分`; } }
5. 注意事项
- 时区处理:存储
banExpireAt时建议使用UTC时间,避免时区差异导致到期时间计算错误 - 重复封禁:若用户已处于封禁状态,再次调用封禁接口可选择覆盖原到期时间或返回"已封禁"提示
- 性能优化:定时任务的执行频率根据用户量调整,用户量大时可分批次处理;惰性处理无需额外资源消耗,适合中小规模应用
内容的提问来源于stack exchange,提问作者M Nouman
相关产品推荐
相关产品推荐

