This commit is contained in:
payacom
2026-08-31 20:45:41 +03:30
parent fa9f23c61d
commit 3f416a7a60
367 changed files with 621895 additions and 6443 deletions

80
utils/redisClient.js Normal file
View File

@@ -0,0 +1,80 @@
const { createClient } = require('redis')
// یک کلاینتِ Redis مشترک برای کل اپ (presence، کش، rate-limit، session) — Redis
// اختیاریه: اگه سرورش در دسترس نباشه (چه موقع boot، چه بعداً قطع بشه)، هیچ‌جای اپ
// نباید کرش کنه یا هنگ کنه. برای همین getRedisClient() به‌جای خودِ کلاینت، فقط وقتی
// واقعاً "ready"ه اون رو برمی‌گردونه؛ صدازننده‌ها همیشه باید مسیرِ fallback
// (مستقیم Mongo/memory) رو هم داشته باشن، دقیقاً عین چیزی که توی lastOnline.js و
// middlewares/index.js پیاده شده
let client = null
let isReady = false
function buildRedisUrl () {
if (process.env.REDIS_URL) return process.env.REDIS_URL
const host = process.env.REDIS_HOST || '127.0.0.1'
const port = process.env.REDIS_PORT || '6379'
const password = process.env.REDIS_PASSWORD || ''
const db = process.env.REDIS_DB || '0'
const auth = password ? `:${encodeURIComponent(password)}@` : ''
return `redis://${auth}${host}:${port}/${db}`
}
function getClient () {
if (client) return client
client = createClient({
url: buildRedisUrl(),
socket: {
// بک‌آف نمایی با سقف ۱۰ ثانیه — تا ابد retry می‌کنه (وقتی سرورِ Redis بعداً بالا
// اومد خودش وصل می‌شه)، ولی هیچ‌وقت کل اپ رو برای اتصال بلاک نمی‌کنه چون
// connectRedis() پایین await نمی‌شه
reconnectStrategy: (retries) => Math.min(retries * 200, 10000),
// بدون این، وقتی REDIS_HOST به یه آدرسِ اشتباه/غیرقابل‌دسترس (نه صرفاً «رد شد»،
// بلکه ساکت هیچ پاسخی نمی‌ده) اشاره کنه، تلاشِ اتصال می‌تونه تا timeoutِ پیش‌فرضِ
// TCP سطحِ سیستم‌عامل (روی بعضی سیستم‌ها ۱۰۰+ ثانیه) طول بکشه — و هر درخواستی که
// منتظرِ همون تلاشِ اتصاله (مثلاً session store) به همون اندازه کند می‌شه
connectTimeout: 5000
}
})
// کتابخونه‌ی redis v4+: بدون این listener، هر خطای اتصال کل پروسه‌ی Node رو
// کرش می‌کنه (unhandled 'error' event) — این مهم‌ترین خطِ این فایله
client.on('error', (err) => {
isReady = false
console.error('[redis] connection error:', err.message)
})
client.on('ready', () => {
isReady = true
console.log('[redis] connected')
})
client.on('end', () => {
isReady = false
})
return client
}
// عمداً await نمی‌شه توی boot — اگه Redis در دسترس نباشه، بوت‌شدنِ کل سرور نباید
// معطلِ چندین‌بار retry بمونه؛ اتصال توی بک‌گراند انجام می‌شه و هر وقت آماده شد
// isReady=true می‌شه
function connectRedis () {
const c = getClient()
c.connect().catch((err) => {
console.error('[redis] initial connection failed — app continues without Redis:', err.message)
})
}
// همه‌جای اپ باید از همین تابع استفاده کنن، نه مستقیم از getRawClient() — چون فقط
// وقتی واقعاً متصله چیزی برمی‌گردونه
function getRedisClient () {
return isReady ? client : null
}
// فقط برای jaهایی مثل connect-redis که خودشون client رو نگه می‌دارن و باید همون
// اول (حتی قبل از وصل‌شدن) بهش رفرنس بدیم — چون کتابخونه‌ی redis v4 دستورهای
// قبل از اتصال رو صف می‌کنه (offline queue)، نه اینکه فوراً خطا بده
function getRawClient () {
return getClient()
}
module.exports = { connectRedis, getRedisClient, getRawClient }