gongxiangp2/lib/upload-sessions-redis.ts.example

165 lines
3.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Redis实现示例(生产环境推荐)
*
* 使用方法:
* 1. 安装依赖:npm install ioredis
* 2. 配置环境变量:REDIS_URL=redis://localhost:6379
* 3. 在upload-sessions.ts中导入此模块
*
* 优势:
* - 数据持久化(可配置RDB/AOF)
* - 支持多实例部署
* - 自动过期(TTL)
* - 高可用性(支持主从复制、集群)
* - 跨服务共享
*/
import Redis from 'ioredis'
import type { UploadSession } from './upload-sessions'
// 初始化Redis连接
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000)
return delay
},
maxRetriesPerRequest: 3,
})
// Redis键前缀
const KEY_PREFIX = 'upload:session:'
const EXPIRE_TIME = 24 * 60 * 60 // 24小时(秒)
/**
* 保存上传会话
*/
export async function saveUploadSession(
uploadId: string,
session: UploadSession
): Promise<void> {
const key = `${KEY_PREFIX}${uploadId}`
// 将Map转换为数组以便序列化
const data = {
...session,
chunks: Array.from(session.chunks.entries()),
}
// 使用SETEX设置键值并指定过期时间
await redis.setex(key, EXPIRE_TIME, JSON.stringify(data))
console.log(`[Redis] 保存上传会话: ${uploadId}, 过期时间: ${EXPIRE_TIME}秒`)
}
/**
* 获取上传会话
*/
export async function getUploadSession(
uploadId: string
): Promise<UploadSession | null> {
const key = `${KEY_PREFIX}${uploadId}`
const data = await redis.get(key)
if (!data) {
return null
}
const session = JSON.parse(data)
// 将数组转换回Map
return {
...session,
chunks: new Map(session.chunks),
}
}
/**
* 删除上传会话
*/
export async function deleteUploadSession(uploadId: string): Promise<void> {
const key = `${KEY_PREFIX}${uploadId}`
await redis.del(key)
console.log(`[Redis] 删除上传会话: ${uploadId}`)
}
/**
* 更新块信息
*/
export async function updateChunk(
uploadId: string,
chunkIndex: number,
chunkPath: string
): Promise<void> {
const session = await getUploadSession(uploadId)
if (!session) {
throw new Error(`上传会话不存在: ${uploadId}`)
}
session.chunks.set(chunkIndex, chunkPath)
await saveUploadSession(uploadId, session)
console.log(`[Redis] 更新块信息: ${uploadId}, 块 ${chunkIndex}`)
}
/**
* 检查会话是否存在
*/
export async function hasUploadSession(uploadId: string): Promise<boolean> {
const key = `${KEY_PREFIX}${uploadId}`
const exists = await redis.exists(key)
return exists === 1
}
/**
* 获取会话的剩余过期时间
*/
export async function getSessionTTL(uploadId: string): Promise<number> {
const key = `${KEY_PREFIX}${uploadId}`
const ttl = await redis.ttl(key)
return ttl
}
/**
* 获取所有活跃会话数量
*/
export async function getActiveSessionsCount(): Promise<number> {
const keys = await redis.keys(`${KEY_PREFIX}*`)
return keys.length
}
/**
* 清理所有过期会话(Redis会自动清理,此函数用于统计)
*/
export async function cleanupExpiredSessions(): Promise<number> {
// Redis使用TTL自动清理,这里只是统计
const keys = await redis.keys(`${KEY_PREFIX}*`)
let expiredCount = 0
for (const key of keys) {
const ttl = await redis.ttl(key)
if (ttl === -2) {
// 键已过期但未删除(理论上不会发生)
expiredCount++
}
}
return expiredCount
}
// 错误处理
redis.on('error', (err) => {
console.error('[Redis] 连接错误:', err)
})
redis.on('connect', () => {
console.log('[Redis] 连接成功')
})
redis.on('ready', () => {
console.log('[Redis] 准备就绪')
})
// 导出Redis实例(用于其他操作)
export { redis }