Files
bigwo/test2/server/services/redisClient.js

214 lines
5.4 KiB
JavaScript
Raw Normal View History

const Redis = require('ioredis');
// ============ 配置 ============
const REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
const REDIS_PASSWORD = process.env.REDIS_PASSWORD || '';
const REDIS_DB = parseInt(process.env.REDIS_DB) || 0;
const KEY_PREFIX = process.env.REDIS_KEY_PREFIX || 'bigwo:';
const HISTORY_MAX_LEN = 10; // 5轮 × 2条/轮
const HISTORY_TTL_S = 1800; // 30分钟
const KB_CACHE_HIT_TTL_S = 300; // 5分钟
const KB_CACHE_NOHIT_TTL_S = 120; // 2分钟
const SUMMARY_TTL_S = parseInt(process.env.SUMMARY_REDIS_TTL_SECONDS) || 7200; // 2小时
// ============ 连接管理 ============
let client = null;
let isReady = false;
function createClient() {
if (client) return client;
const opts = {
db: REDIS_DB,
keyPrefix: KEY_PREFIX,
retryStrategy(times) {
if (times > 10) {
console.error('[Redis] max retries reached, giving up');
return null;
}
return Math.min(times * 200, 3000);
},
reconnectOnError(err) {
const targetErrors = ['READONLY', 'ECONNRESET'];
return targetErrors.some(e => err.message.includes(e));
},
lazyConnect: true,
maxRetriesPerRequest: 2,
connectTimeout: 5000,
commandTimeout: 3000,
};
if (REDIS_PASSWORD) {
opts.password = REDIS_PASSWORD;
}
client = new Redis(REDIS_URL, opts);
client.on('ready', () => {
isReady = true;
console.log('[Redis] connected and ready');
});
client.on('error', (err) => {
console.warn('[Redis] error:', err.message);
});
client.on('close', () => {
isReady = false;
console.warn('[Redis] connection closed');
});
client.on('reconnecting', () => {
console.log('[Redis] reconnecting...');
});
client.connect().catch((err) => {
console.warn('[Redis] initial connect failed:', err.message);
});
return client;
}
function getClient() {
if (!client) createClient();
return client;
}
function isAvailable() {
return isReady && client && client.status === 'ready';
}
// ============ 对话历史 ============
const historyKey = (sessionId) => `voice:history:${sessionId}`;
async function pushMessage(sessionId, msg) {
if (!isAvailable()) return false;
try {
const key = historyKey(sessionId);
const value = JSON.stringify({
role: msg.role,
content: msg.content,
source: msg.source || '',
ts: Date.now(),
});
await client.lpush(key, value);
await client.ltrim(key, 0, HISTORY_MAX_LEN - 1);
await client.expire(key, HISTORY_TTL_S);
return true;
} catch (err) {
console.warn('[Redis] pushMessage failed:', err.message);
return false;
}
}
async function getRecentHistory(sessionId, maxRounds = 5) {
if (!isAvailable()) return null;
try {
const key = historyKey(sessionId);
const items = await client.lrange(key, 0, maxRounds * 2 - 1);
if (!items || items.length === 0) return [];
// lpush 是倒序插入lrange 取出的也是最新在前,需要 reverse 恢复时间顺序
return items
.map((item) => {
try { return JSON.parse(item); } catch { return null; }
})
.filter(Boolean)
.reverse();
} catch (err) {
console.warn('[Redis] getRecentHistory failed:', err.message);
return null;
}
}
async function clearSession(sessionId) {
if (!isAvailable()) return false;
try {
await client.del(historyKey(sessionId));
return true;
} catch (err) {
console.warn('[Redis] clearSession failed:', err.message);
return false;
}
}
// ============ 对话摘要 ============
const summaryKey = (sessionId) => `voice:summary:${sessionId}`;
async function setSummary(sessionId, summary) {
if (!isAvailable() || !summary) return false;
try {
const key = summaryKey(sessionId);
await client.set(key, summary, 'EX', SUMMARY_TTL_S);
return true;
} catch (err) {
console.warn('[Redis] setSummary failed:', err.message);
return false;
}
}
async function getSummary(sessionId) {
if (!isAvailable()) return null;
try {
const key = summaryKey(sessionId);
return await client.get(key);
} catch (err) {
console.warn('[Redis] getSummary failed:', err.message);
return null;
}
}
// ============ KB 缓存 ============
const kbCacheKey = (cacheKey) => `kb_cache:${cacheKey}`;
async function setKbCache(cacheKey, result) {
// 只缓存 hit 结果no-hit 不写入 Redis避免阻止后续 retry
if (!isAvailable() || !result.hit) return false;
try {
const key = kbCacheKey(cacheKey);
await client.set(key, JSON.stringify(result), 'EX', KB_CACHE_HIT_TTL_S);
return true;
} catch (err) {
console.warn('[Redis] setKbCache failed:', err.message);
return false;
}
}
async function getKbCache(cacheKey) {
if (!isAvailable()) return null;
try {
const key = kbCacheKey(cacheKey);
const data = await client.get(key);
if (!data) return null;
return JSON.parse(data);
} catch (err) {
console.warn('[Redis] getKbCache failed:', err.message);
return null;
}
}
// ============ 优雅关闭 ============
async function disconnect() {
if (client) {
try {
await client.quit();
} catch {
client.disconnect();
}
client = null;
isReady = false;
console.log('[Redis] disconnected');
}
}
module.exports = {
createClient,
getClient,
isAvailable,
pushMessage,
getRecentHistory,
clearSession,
setSummary,
getSummary,
setKbCache,
getKbCache,
disconnect,
};