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

如何在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模块,定期扫描并解封到期用户。

  1. 在AppModule中启用Schedule模块:
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [ScheduleModule.forRoot(), ...],
})
export class AppModule {}
  1. 创建定时任务:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 09:05:16