This commit is contained in:
龟男日记\www 2026-02-12 03:38:08 +08:00
parent b26fe1e117
commit fa30986946
12 changed files with 754 additions and 7 deletions

View File

@ -1,2 +1,22 @@
DATABASE_URL=mongodb://127.0.0.1/your-database-name
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/database
# Payload
PAYLOAD_SECRET=YOUR_SECRET_HERE
# Redis 配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# 公开 API 密钥(用于外部调用 /api/public/* 端点)
PUBLIC_API_KEY=your-secret-api-key-here
# Cloudflare R2 配置
CLOUDFLARE_R2_BUCKET=your-bucket
CLOUDFLARE_R2_ACCESS_KEY_ID=your-access-key
CLOUDFLARE_R2_SECRET_ACCESS_KEY=your-secret-key
CLOUDFLARE_R2_REGION=auto
CLOUDFLARE_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
CLOUDFLARE_R2_PUBLIC_URL=https://your-public-domain.com

View File

@ -34,6 +34,7 @@
"payload": "3.75.0",
"react": "19.2.1",
"react-dom": "19.2.1",
"redis": "^5.10.0",
"sharp": "0.34.2"
},
"devDependencies": {

View File

@ -56,6 +56,9 @@ importers:
react-dom:
specifier: 19.2.1
version: 19.2.1(react@19.2.1)
redis:
specifier: ^5.10.0
version: 5.10.0
sharp:
specifier: 0.34.2
version: 0.34.2
@ -1640,6 +1643,34 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@redis/bloom@5.10.0':
resolution: {integrity: sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==}
engines: {node: '>= 18'}
peerDependencies:
'@redis/client': ^5.10.0
'@redis/client@5.10.0':
resolution: {integrity: sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==}
engines: {node: '>= 18'}
'@redis/json@5.10.0':
resolution: {integrity: sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==}
engines: {node: '>= 18'}
peerDependencies:
'@redis/client': ^5.10.0
'@redis/search@5.10.0':
resolution: {integrity: sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==}
engines: {node: '>= 18'}
peerDependencies:
'@redis/client': ^5.10.0
'@redis/time-series@5.10.0':
resolution: {integrity: sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==}
engines: {node: '>= 18'}
peerDependencies:
'@redis/client': ^5.10.0
'@rolldown/pluginutils@1.0.0-beta.11':
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
@ -2550,6 +2581,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -4133,6 +4168,10 @@ packages:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
redis@5.10.0:
resolution: {integrity: sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==}
engines: {node: '>= 18'}
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
@ -6750,6 +6789,26 @@ snapshots:
dependencies:
playwright: 1.56.1
'@redis/bloom@5.10.0(@redis/client@5.10.0)':
dependencies:
'@redis/client': 5.10.0
'@redis/client@5.10.0':
dependencies:
cluster-key-slot: 1.1.2
'@redis/json@5.10.0(@redis/client@5.10.0)':
dependencies:
'@redis/client': 5.10.0
'@redis/search@5.10.0(@redis/client@5.10.0)':
dependencies:
'@redis/client': 5.10.0
'@redis/time-series@5.10.0(@redis/client@5.10.0)':
dependencies:
'@redis/client': 5.10.0
'@rolldown/pluginutils@1.0.0-beta.11': {}
'@rollup/rollup-android-arm-eabi@4.57.1':
@ -7759,6 +7818,8 @@ snapshots:
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -9634,6 +9695,14 @@ snapshots:
real-require@0.2.0: {}
redis@5.10.0:
dependencies:
'@redis/bloom': 5.10.0(@redis/client@5.10.0)
'@redis/client': 5.10.0
'@redis/json': 5.10.0(@redis/client@5.10.0)
'@redis/search': 5.10.0(@redis/client@5.10.0)
'@redis/time-series': 5.10.0(@redis/client@5.10.0)
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.8

98
src/app/api/cache/route.ts vendored Normal file
View File

@ -0,0 +1,98 @@
import { NextRequest, NextResponse } from 'next/server'
import { getCacheStats, clearAllCache, deleteCachePattern, connectRedis } from '@/lib/redis'
import { getPayload } from 'payload'
import config from '@payload-config'
/**
* GET /api/cache
*
*/
export async function GET(req: NextRequest) {
try {
// 验证用户权限
const payload = await getPayload({ config })
const headers = req.headers
const token = headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
}
// 验证用户是否为管理员
const { user } = await payload.auth({ headers: req.headers })
if (!user || !user.roles?.includes('admin')) {
return NextResponse.json({ success: false, error: 'Forbidden' }, { status: 403 })
}
// 获取统计信息
const stats = await getCacheStats()
return NextResponse.json({
success: true,
stats,
})
} catch (error) {
console.error('Cache stats error:', error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to get cache stats',
},
{ status: 500 },
)
}
}
/**
* DELETE /api/cache
*
* Query params:
* - pattern: 匹配模式 "products:*"
*/
export async function DELETE(req: NextRequest) {
try {
// 验证用户权限
const payload = await getPayload({ config })
const { user } = await payload.auth({ headers: req.headers })
if (!user || !user.roles?.includes('admin')) {
return NextResponse.json({ success: false, error: 'Forbidden' }, { status: 403 })
}
// 确保 Redis 已连接
await connectRedis()
// 获取查询参数
const searchParams = req.nextUrl.searchParams
const pattern = searchParams.get('pattern')
let deletedCount = 0
let message = ''
if (pattern) {
// 删除匹配模式的缓存
deletedCount = await deleteCachePattern(pattern)
message = `已清除 ${deletedCount} 个匹配 "${pattern}" 的缓存键`
} else {
// 清除所有缓存
deletedCount = await clearAllCache()
message = `已清除所有缓存,共 ${deletedCount} 个键`
}
return NextResponse.json({
success: true,
message,
deletedCount,
})
} catch (error) {
console.error('Cache clear error:', error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to clear cache',
},
{ status: 500 },
)
}
}

View File

@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { getCache, setCache } from '@/lib/redis'
/**
* GET /api/public/products/[id]
*
*/
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
try {
const { id } = params
// 生成缓存 key
const cacheKey = `products:detail:${id}`
// 尝试从缓存获取
const cached = await getCache(cacheKey)
if (cached) {
return NextResponse.json({
success: true,
data: cached,
cached: true,
})
}
// 从数据库获取
const payload = await getPayload({ config })
const result = await payload.findByID({
collection: 'products',
id,
depth: 2,
})
// 只有已发布的产品才返回
if (result.status !== 'published') {
return NextResponse.json(
{
success: false,
error: 'Product not found or not published',
},
{ status: 404 },
)
}
// 缓存结果1 小时)
await setCache(cacheKey, result, 3600)
return NextResponse.json({
success: true,
data: result,
cached: false,
})
} catch (error) {
console.error('Product detail API error:', error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch product',
},
{ status: 500 },
)
}
}

View File

@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { getCache, setCache } from '@/lib/redis'
/**
* GET /api/public/products
*
*/
export async function GET(req: NextRequest) {
try {
const searchParams = req.nextUrl.searchParams
const page = parseInt(searchParams.get('page') || '1', 10)
const limit = parseInt(searchParams.get('limit') || '10', 10)
const status = searchParams.get('status') || 'published'
// 生成缓存 key
const cacheKey = `products:list:page=${page}:limit=${limit}:status=${status}`
// 尝试从缓存获取
const cached = await getCache(cacheKey)
if (cached) {
return NextResponse.json({
success: true,
data: cached,
cached: true,
})
}
// 从数据库获取
const payload = await getPayload({ config })
const result = await payload.find({
collection: 'products',
where: {
status: { equals: status },
},
page,
limit,
depth: 1,
})
// 缓存结果1 小时)
await setCache(cacheKey, result, 3600)
return NextResponse.json({
success: true,
data: result,
cached: false,
})
} catch (error) {
console.error('Products API error:', error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch products',
},
{ status: 500 },
)
}
}

View File

@ -1,5 +1,6 @@
import type { CollectionConfig } from 'payload'
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
import {
BlocksFeature,
BoldFeature,

View File

@ -1,5 +1,6 @@
import type { CollectionConfig } from 'payload'
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
import {
BlocksFeature,
BoldFeature,
@ -331,7 +332,7 @@ export const Articles: CollectionConfig = {
return data
},
],
afterChange: [logAfterChange],
afterDelete: [logAfterDelete],
afterChange: [logAfterChange, cacheAfterChange],
afterDelete: [logAfterDelete, cacheAfterDelete],
},
}

View File

@ -1,5 +1,6 @@
import type { CollectionConfig } from 'payload'
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
import {
AlignFeature,
BlocksFeature,
@ -202,8 +203,8 @@ export const Products: CollectionConfig = {
},
],
hooks: {
afterChange: [logAfterChange],
afterDelete: [logAfterDelete],
afterChange: [logAfterChange, cacheAfterChange],
afterDelete: [logAfterDelete, cacheAfterDelete],
},
timestamps: true,
}

View File

@ -12,6 +12,11 @@ export default function AdminPanel() {
const [clearMessage, setClearMessage] = useState('')
const [showClearConfirm, setShowClearConfirm] = useState(false)
// 缓存相关状态
const [cacheStats, setCacheStats] = useState<any>(null)
const [cacheLoading, setCacheLoading] = useState(false)
const [cacheMessage, setCacheMessage] = useState('')
const handleClearData = () => {
setShowClearConfirm(true)
setClearMessage('')
@ -46,6 +51,79 @@ export default function AdminPanel() {
setClearMessage('')
}
// 获取缓存状态
const fetchCacheStats = async () => {
setCacheLoading(true)
try {
const response = await fetch('/api/cache')
const data = await response.json()
if (data.success) {
setCacheStats(data.stats)
}
} catch (error) {
console.error('Failed to fetch cache stats:', error)
} finally {
setCacheLoading(false)
}
}
// 清除所有缓存
const handleClearAllCache = async () => {
if (!confirm('确定要清除所有 Redis 缓存吗?')) return
setCacheLoading(true)
setCacheMessage('')
try {
const response = await fetch('/api/cache', {
method: 'DELETE',
})
const data = await response.json()
if (data.success) {
setCacheMessage(data.message)
fetchCacheStats() // 刷新统计
} else {
setCacheMessage(`清除失败: ${data.error}`)
}
} catch (error) {
setCacheMessage(`清除出错: ${error instanceof Error ? error.message : '未知错误'}`)
} finally {
setCacheLoading(false)
}
}
// 清除产品缓存
const handleClearProductsCache = async () => {
setCacheLoading(true)
setCacheMessage('')
try {
const response = await fetch('/api/cache?pattern=products:*', {
method: 'DELETE',
})
const data = await response.json()
if (data.success) {
setCacheMessage(data.message)
fetchCacheStats() // 刷新统计
} else {
setCacheMessage(`清除失败: ${data.error}`)
}
} catch (error) {
setCacheMessage(`清除出错: ${error instanceof Error ? error.message : '未知错误'}`)
} finally {
setCacheLoading(false)
}
}
// 组件加载时获取缓存状态
React.useEffect(() => {
fetchCacheStats()
}, [])
return (
<div style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '2rem', fontSize: '2rem', fontWeight: 'bold' }}>
@ -151,6 +229,155 @@ export default function AdminPanel() {
</div>
</div>
{/* Redis 缓存管理区域 */}
<div
style={{
backgroundColor: 'var(--theme-elevation-50)',
borderRadius: '8px',
padding: '1.5rem',
marginBottom: '1.5rem',
border: '1px solid var(--theme-elevation-100)',
}}
>
<h2 style={{ marginBottom: '1rem', fontSize: '1.25rem', fontWeight: '600' }}>
🚀 Redis
</h2>
{/* 缓存统计 */}
<div
style={{
backgroundColor: 'var(--theme-elevation-0)',
borderRadius: '6px',
padding: '1.5rem',
marginBottom: '1rem',
border: '1px solid var(--theme-elevation-150)',
}}
>
<h3 style={{ marginBottom: '1rem', fontSize: '1rem', fontWeight: '600' }}>📊 </h3>
{cacheStats ? (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
gap: '1rem',
}}
>
<div>
<div
style={{
fontSize: '0.75rem',
color: 'var(--theme-elevation-600)',
marginBottom: '0.25rem',
}}
>
</div>
<div style={{ fontSize: '1.25rem', fontWeight: '600' }}>
{cacheStats.connected ? '✅ 已连接' : '❌ 未连接'}
</div>
</div>
<div>
<div
style={{
fontSize: '0.75rem',
color: 'var(--theme-elevation-600)',
marginBottom: '0.25rem',
}}
>
</div>
<div style={{ fontSize: '1.25rem', fontWeight: '600' }}>{cacheStats.totalKeys}</div>
</div>
<div>
<div
style={{
fontSize: '0.75rem',
color: 'var(--theme-elevation-600)',
marginBottom: '0.25rem',
}}
>
使
</div>
<div style={{ fontSize: '1.25rem', fontWeight: '600' }}>
{cacheStats.memoryUsage}
</div>
</div>
</div>
) : (
<div
style={{ textAlign: 'center', padding: '1rem', color: 'var(--theme-elevation-600)' }}
>
{cacheLoading ? '加载中...' : '无法获取缓存统计'}
</div>
)}
<div style={{ marginTop: '1rem' }}>
<Button onClick={fetchCacheStats} disabled={cacheLoading} buttonStyle="secondary">
{cacheLoading ? '刷新中...' : '刷新统计'}
</Button>
</div>
</div>
{/* 清除缓存操作 */}
<div
style={{
backgroundColor: 'var(--theme-elevation-0)',
borderRadius: '6px',
padding: '1.5rem',
border: '1px solid var(--theme-elevation-150)',
}}
>
<h3 style={{ marginBottom: '0.5rem', fontSize: '1rem', fontWeight: '600' }}>
🗑
</h3>
<p
style={{
marginBottom: '1rem',
fontSize: '0.875rem',
color: 'var(--theme-elevation-600)',
}}
>
Redis key payload:
</p>
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
<Button
onClick={handleClearProductsCache}
disabled={cacheLoading}
buttonStyle="primary"
>
</Button>
<Button onClick={handleClearAllCache} disabled={cacheLoading} buttonStyle="error">
</Button>
</div>
{cacheMessage && (
<div
style={{
marginTop: '1rem',
padding: '1rem',
backgroundColor:
cacheMessage.includes('失败') || cacheMessage.includes('出错')
? 'var(--theme-error-50)'
: 'var(--theme-success-50)',
borderRadius: '6px',
fontSize: '0.875rem',
border: `1px solid ${
cacheMessage.includes('失败') || cacheMessage.includes('出错')
? 'var(--theme-error-500)'
: 'var(--theme-success-500)'
}`,
}}
>
{cacheMessage}
</div>
)}
</div>
</div>
{/* 系统信息区域 */}
<div
style={{
@ -163,13 +390,13 @@ export default function AdminPanel() {
<h2 style={{ marginBottom: '1rem', fontSize: '1.25rem', fontWeight: '600' }}>
</h2>
<div
style={{
backgroundColor: 'var(--theme-elevation-0)',
borderRadius: '6px',
padding: '1rem',
padding: '1.5rem',
border: '1px solid var(--theme-elevation-150)',
fontSize: '0.875rem',
}}
>
<div style={{ marginBottom: '0.5rem' }}>
@ -181,6 +408,9 @@ export default function AdminPanel() {
<div style={{ marginBottom: '0.5rem' }}>
<strong>:</strong> Cloudflare R2 (S3 API)
</div>
<div style={{ marginBottom: '0.5rem' }}>
<strong>:</strong> Redis
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,46 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'
import { deleteCachePattern } from '../lib/redis'
/**
* afterChange hook -
*/
export const cacheAfterChange: CollectionAfterChangeHook = async ({ doc, req, collection }) => {
const collectionSlug = collection?.slug
if (!collectionSlug) return doc
try {
// 清除该 collection 的所有缓存
const deletedCount = await deleteCachePattern(`${collectionSlug}:*`)
console.log(
`[Cache] Cleared ${deletedCount} cache keys for collection: ${collectionSlug} after change`,
)
} catch (error) {
console.error(`[Cache] Failed to clear cache for ${collectionSlug}:`, error)
}
return doc
}
/**
* afterDelete hook -
*/
export const cacheAfterDelete: CollectionAfterDeleteHook = async ({ doc, req, id, collection }) => {
const collectionSlug = collection?.slug
if (!collectionSlug) return doc
try {
// 清除该 collection 的所有缓存
const deletedCount = await deleteCachePattern(`${collectionSlug}:*`)
console.log(
`[Cache] Cleared ${deletedCount} cache keys for collection: ${collectionSlug} after delete`,
)
} catch (error) {
console.error(`[Cache] Failed to clear cache for ${collectionSlug}:`, error)
}
return doc
}

156
src/lib/redis.ts Normal file
View File

@ -0,0 +1,156 @@
import { createClient } from 'redis'
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'
// 创建 Redis 客户端
const redisClient = createClient({
url: redisUrl,
})
redisClient.on('error', (err) => console.error('Redis Client Error:', err))
redisClient.on('connect', () => console.log('Redis Client Connected'))
// 连接 Redis
let isConnecting = false
let isConnected = false
export async function connectRedis() {
if (isConnected) return redisClient
if (isConnecting) {
// 等待连接完成
while (isConnecting) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
return redisClient
}
isConnecting = true
try {
await redisClient.connect()
isConnected = true
console.log('Redis connected successfully')
} catch (error) {
console.error('Failed to connect to Redis:', error)
throw error
} finally {
isConnecting = false
}
return redisClient
}
// 缓存 key 前缀
const PREFIX = 'payload:'
/**
*
*/
export async function getCache<T = any>(key: string): Promise<T | null> {
try {
const client = await connectRedis()
const data = await client.get(`${PREFIX}${key}`)
if (!data) return null
return JSON.parse(data) as T
} catch (error) {
console.error('Redis GET error:', error)
return null
}
}
/**
*
* @param key
* @param value
* @param ttl 1
*/
export async function setCache<T = any>(key: string, value: T, ttl: number = 3600): Promise<void> {
try {
const client = await connectRedis()
await client.setEx(`${PREFIX}${key}`, ttl, JSON.stringify(value))
} catch (error) {
console.error('Redis SET error:', error)
}
}
/**
*
*/
export async function deleteCache(key: string): Promise<void> {
try {
const client = await connectRedis()
await client.del(`${PREFIX}${key}`)
} catch (error) {
console.error('Redis DEL error:', error)
}
}
/**
*
*/
export async function deleteCachePattern(pattern: string): Promise<number> {
try {
const client = await connectRedis()
const keys = await client.keys(`${PREFIX}${pattern}`)
if (keys.length === 0) return 0
await client.del(keys)
return keys.length
} catch (error) {
console.error('Redis DELETE PATTERN error:', error)
return 0
}
}
/**
*
*/
export async function clearAllCache(): Promise<number> {
return deleteCachePattern('*')
}
/**
*
*/
export async function getCacheStats() {
try {
const client = await connectRedis()
// 获取所有 payload 前缀的 key
const keys = await client.keys(`${PREFIX}*`)
// 获取内存使用情况
const info = await client.info('memory')
const memoryMatch = info.match(/used_memory_human:(.+)/)
const memoryUsage = memoryMatch ? memoryMatch[1].trim() : 'N/A'
return {
connected: isConnected,
totalKeys: keys.length,
memoryUsage,
prefix: PREFIX,
}
} catch (error) {
console.error('Redis STATS error:', error)
return {
connected: false,
totalKeys: 0,
memoryUsage: 'N/A',
prefix: PREFIX,
}
}
}
/**
* Redis
*/
export function getRedisClient() {
return redisClient
}
// 优雅退出时断开连接
process.on('beforeExit', async () => {
if (isConnected) {
await redisClient.quit()
console.log('Redis connection closed')
}
})