GlobalModule 全局模块使用文档
概述
GlobalModule 是一个 NestJS 全局模块,使用 @Global() 装饰器标记,使得模块中导出的服务可以在整个应用程序中直接使用,无需在其他模块中重复导入。该模块主要提供 Redis 缓存服务和一些全局共享的功能模块。
模块结构
src/modules/global/
├── global.module.ts # 全局模块定义
└── redis.service.ts # Redis 服务实现基础配置
模块定义
import { Global, Module } from '@nestjs/common'
import { RedisService } from './redis.service'
import { LoginLogsModule } from '../loginLogs/module'
import { MenusModule } from '../menus/menus.module'
@Global()
@Module({
imports: [LoginLogsModule, MenusModule],
controllers: [],
providers: [RedisService],
exports: [RedisService],
})
export class GlobalModule {}关键特性:
- 使用
@Global()装饰器标记为全局模块 - 导入
LoginLogsModule和MenusModule提供相关功能 - 导出
RedisService供整个应用使用
在 AppModule 中引入
import { Module } from '@nestjs/common'
import { GlobalModule } from './modules/global/global.module'
@Module({
imports: [
GlobalModule,
// ... 其他模块
],
})
export class AppModule {}RedisService API 参考
初始化配置
Redis 服务在构造函数中自动初始化连接:
constructor(
private loginLogsService: LoginLogsService,
private menusService: MenusService,
) {
this.redis = new Redis({
port: 6379, // Redis 端口
host: '127.0.0.1', // Redis 主机地址
db: 1, // 数据库索引
})
}注意: 生产环境建议将 Redis 配置提取到配置文件中进行管理。
基础操作方法
set(key: string, value: any, time?: number)
设置键值对,支持过期时间。
参数:
key: 字符串类型的键value: 任意类型的值(对象会自动序列化为 JSON)time: 可选,过期时间(秒)
返回: Promise<any>
// 基本使用
await redisService.set('username', 'admin')
// 带过期时间(5分钟)
await redisService.set('session:token', tokenData, 300)
// 存储对象
await redisService.set('user:1', { id: 1, name: 'Admin' })get(key: string)
获取指定键的值。
参数:
key: 字符串类型的键
返回: Promise<string | null> - 如果值是 JSON 字符串,需要手动解析
const username = await redisService.get('username')
const userData = JSON.parse(await redisService.get('user:1'))del(key: string)
删除指定键。
参数:
key: 字符串类型的键
返回: Promise<number> - 删除的键数量
await redisService.del('username')ttl(key: string)
获取键的剩余生存时间。
参数:
key: 字符串类型的键
返回: Promise<number> - 剩余秒数,-1 表示永不过期,-2 表示键不存在
const remainingTime = await redisService.ttl('session:token')keys(pattern: string)
查找匹配模式的键。
参数:
pattern: 匹配模式(支持通配符*)
返回: Promise<string[]>
// 获取所有用户在线键
const onlineUsers = await redisService.keys('user.online:*')expire(key: string, time: number)
设置键的过期时间。
参数:
key: 字符串类型的键time: 过期时间(秒)
返回: Promise<number>
// 延长会话时间到 10 分钟
await redisService.expire('session:token', 600)exists(key: string)
检查键是否存在。
参数:
key: 字符串类型的键
返回: Promise<number> - 1 表示存在,0 表示不存在
const isExists = await redisService.exists('username')
if (isExists) {
console.log('键存在')
}Hash 操作方法
hset(key: string, field: string, value: string)
设置哈希表中字段的值。
参数:
key: 哈希表的键field: 字段名value: 字段值
返回: Promise<number>
await redisService.hset('user:1', 'name', 'Admin')
await redisService.hset('user:1', 'email', 'admin@example.com')hget(key: string, field: string)
获取哈希表中指定字段的值。
参数:
key: 哈希表的键field: 字段名
返回: Promise<string | null>
const name = await redisService.hget('user:1', 'name')hdel(key: string, field: string)
删除哈希表中指定的字段。
参数:
key: 哈希表的键field: 字段名
返回: Promise<number>
await redisService.hdel('user:1', 'email')高级查询方法
getNotExpiredValues(pattern = '*')
获取所有未过期的键值对。
参数:
pattern: 可选,匹配模式,默认为'*'
返回: Promise<any[]> - 解析后的对象数组
// 获取所有未过期的用户会话
const activeSessions = await redisService.getNotExpiredValues('user.online:*')
// 遍历处理
activeSessions.forEach(session => {
console.log(session.account, session.ip)
})实现原理:
- 使用 SCAN 命令分批遍历键,避免阻塞 Redis
- 检查每个键的 TTL,只返回未过期的键
- 自动解析 JSON 字符串为对象
用户会话管理方法
getRedisOnlineUser(query: QueryListDto = {})
获取在线用户列表,支持分页和筛选。
参数:
query: 查询条件对象pageNum: 页码(从 1 开始)pageSize: 每页数量createTimeRange: 时间范围[startTime, endTime]account: 账号模糊搜索ip: IP 地址模糊搜索address: 地址模糊搜索
返回: Promise<[any[], number]> - [数据数组, 总数]
// 基本查询
const [users, total] = await redisService.getRedisOnlineUser({
pageNum: 1,
pageSize: 10,
})
// 带筛选条件
const [filteredUsers, total] = await redisService.getRedisOnlineUser({
pageNum: 1,
pageSize: 10,
account: 'admin',
ip: '192.168',
createTimeRange: ['2024-01-01', '2024-12-31'],
})setRedisOnlineUser(reqOrData, user: any = {})
设置用户在线状态。
参数:
reqOrData: 请求对象或数据对象user: 可选,用户信息对象
返回: Promise<any>
// 方式一:传入包含 session 的数据
await redisService.setRedisOnlineUser({
session: 'abc123',
account: 'admin',
ip: '192.168.1.1',
address: '北京',
createTime: new Date(),
})
// 方式二:传入请求对象和用户信息
await redisService.setRedisOnlineUser(request, userInfo)
// 会自动创建登录日志并存储功能说明:
- 如果传入的数据包含
session,直接存储该数据 - 否则,调用
LoginLogsService创建登录日志后存储 - 默认过期时间为 5 分钟(300 秒)
delRedisOnlineUser(session: string)
删除用户在线状态。
参数:
session: 用户会话 ID
返回: Promise<number>
// 用户登出时调用
await redisService.delRedisOnlineUser(userSession)权限管理方法
getPermissions(): Promise<string[]>
获取系统所有按钮权限标识。
返回: Promise<string[]> - 权限标识数组
// 获取所有权限
const permissions = await redisService.getPermissions()
console.log(permissions)
// ['user:add', 'user:update', 'user:delete', ...]功能说明:
- 首先尝试从 Redis 缓存中读取权限数据
- 如果缓存不存在,从数据库查询所有激活的按钮类型菜单
- 提取菜单的
permissionKey字段 - 将结果缓存到 Redis,键名为
permissions - 后续请求直接从缓存读取,提高性能
使用场景:
// 在守卫中使用
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private redisService: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const permissions = await this.redisService.getPermissions()
// 验证用户是否有对应权限
return permissions.includes(requiredPermission)
}
}实际应用场景
1. 用户登录会话管理
import { Injectable } from '@nestjs/common'
import { RedisService } from '../global/redis.service'
@Injectable()
export class AuthService {
constructor(private redisService: RedisService) {}
async login(username: string, password: string) {
// 验证用户...
const user = await this.validateUser(username, password)
// 生成会话 ID
const session = this.generateSession()
// 记录在线状态
await this.redisService.setRedisOnlineUser({
session,
account: user.username,
ip: this.getRequestIp(),
address: await this.getIpAddress(),
createTime: new Date(),
}, user)
return { session, user }
}
async logout(session: string) {
// 删除在线状态
await this.redisService.delRedisOnlineUser(session)
}
}2. 接口访问频率限制
import { Injectable } from '@nestjs/common'
import { RedisService } from '../global/redis.service'
@Injectable()
export class RateLimitService {
constructor(private redisService: RedisService) {}
async checkRateLimit(userId: string, limit = 100, windowSeconds = 3600) {
const key = `rate.limit:${userId}`
const count = await this.redisService.get(key)
if (!count) {
// 首次访问,设置计数器和过期时间
await this.redisService.set(key, 1, windowSeconds)
return true
}
const currentCount = parseInt(count)
if (currentCount >= limit) {
return false // 超出限制
}
// 增加计数
await this.redisService.set(key, currentCount + 1)
return true
}
}3. 数据缓存优化
import { Injectable } from '@nestjs/common'
import { RedisService } from '../global/redis.service'
import { UserService } from '../users/user.service'
@Injectable()
export class CacheService {
constructor(
private redisService: RedisService,
private userService: UserService,
) {}
async getUserWithCache(userId: number) {
const cacheKey = `user:${userId}`
// 尝试从缓存获取
let user = await this.redisService.get(cacheKey)
if (!user) {
// 缓存未命中,查询数据库
user = await this.userService.findById(userId)
// 存入缓存,过期时间 10 分钟
await this.redisService.set(cacheKey, user, 600)
} else {
user = JSON.parse(user)
}
return user
}
async clearUserCache(userId: number) {
await this.redisService.del(`user:${userId}`)
}
}4. 实时在线用户统计
import { Controller, Get, Query } from '@nestjs/common'
import { RedisService } from '../global/redis.service'
@Controller('monitor')
export class MonitorController {
constructor(private redisService: RedisService) {}
@Get('online-users')
async getOnlineUsers(@Query() query) {
const [users, total] = await this.redisService.getRedisOnlineUser({
pageNum: query.pageNum || 1,
pageSize: query.pageSize || 20,
account: query.account,
ip: query.ip,
})
return {
code: 200,
data: {
list: users,
total,
},
}
}
}注意事项
1. Redis 连接配置
当前 Redis 配置是硬编码在代码中的,生产环境建议:
// 推荐:从配置文件读取
import { ConfigService } from '@nestjs/config'
constructor(private configService: ConfigService) {
this.redis = new Redis({
port: this.configService.get('REDIS_PORT') || 6379,
host: this.configService.get('REDIS_HOST') || '127.0.0.1',
password: this.configService.get('REDIS_PASSWORD'),
db: this.configService.get('REDIS_DB') || 0,
})
}2. 键命名规范
建议使用统一的键命名规范,避免冲突:
- 用户会话:
user.online:{session} - 用户信息缓存:
user:{id} - 权限缓存:
permissions - 速率限制:
rate.limit:{userId} - 业务数据:
{module}:{type}:{id}
3. 过期时间管理
- 会话数据:建议 5-30 分钟
- 用户信息缓存:建议 10-60 分钟
- 权限数据:建议较长(如 24 小时)或手动更新
- 临时数据:根据业务需求设置
4. 性能优化
- 使用
SCAN而非KEYS命令遍历键(已实现) - 批量操作时使用 pipeline
- 合理设置过期时间,避免内存泄漏
- 监控 Redis 内存使用情况
5. 错误处理
建议在调用 Redis 方法时添加错误处理:
try {
await this.redisService.set('key', 'value')
} catch (error) {
console.error('Redis 操作失败:', error)
// 降级处理逻辑
}依赖模块
LoginLogsModule
用于记录和管理用户登录日志,在 setRedisOnlineUser 方法中被调用。
MenusModule
用于管理系统菜单和权限,在 getPermissions 方法中被调用。
总结
GlobalModule 通过提供全局可用的 RedisService,为应用程序提供了强大的缓存和会话管理能力。合理使用 Redis 可以显著提升系统性能和用户体验,特别是在高并发场景下。
核心优势:
- ✅ 全局可用,无需重复导入
- ✅ 封装常用 Redis 操作,简化开发
- ✅ 内置用户会话管理功能
- ✅ 支持权限缓存,提升性能
- ✅ 提供灵活的查询和筛选能力
最佳实践:
- 合理设计缓存策略和过期时间
- 使用统一的键命名规范
- 做好错误处理和降级方案
- 定期监控和优化 Redis 性能