'use strict'; const { RedisClient } = require('./redis'); const KEY_PREFIX = 'bridge:lead:'; const TTL_SEC = 24 * 60 * 60; /** * Lead 槽位存储: * - 配了 REDIS_URL:写 Redis(bridge 重启/多副本共享) * - 未配或 Redis 暂时不可用:退回进程内存(仅 POC,日志会告警) */ function createLeadStore(cfg, opts = {}) { const memory = new Map(); const client = cfg.redisUrl ? new RedisClient(cfg.redisUrl, { timeoutMs: opts.timeoutMs || 2000 }) : null; const warn = opts.warn || ((msg) => console.warn(JSON.stringify({ ts: new Date().toISOString(), warn: msg }))); function normalizeState(state) { if (!state || !state.slots) return null; return { slots: { ...state.slots }, createdFingerprints: Array.isArray(state.createdFingerprints) ? state.createdFingerprints : [], opportunityId: state.opportunityId || null, stage: state.stage || 'NEW', updatedAt: state.updatedAt || Date.now(), }; } function memoryGet(key) { const state = memory.get(key); if (!state) return null; if (Date.now() - state.updatedAt > TTL_SEC * 1000) { memory.delete(key); return null; } return state; } async function get(conversationUuid) { const key = String(conversationUuid || ''); if (!key) return null; if (client) { try { const raw = await client.get(KEY_PREFIX + key); if (raw) { const state = JSON.parse(raw); if (state && state.slots && Date.now() - (state.updatedAt || 0) <= TTL_SEC * 1000) return normalizeState(state); await client.del(KEY_PREFIX + key).catch(() => {}); return null; } return null; } catch (err) { warn(`lead store redis get failed, fallback to memory: ${err.message}`); } } const memoryState = memoryGet(key); return memoryState ? normalizeState(memoryState) : null; } async function saveState(conversationUuid, state) { const key = String(conversationUuid || ''); if (!key) return null; const next = { ...normalizeState(state), updatedAt: Date.now() }; memory.set(key, next); if (client) { try { await client.setEx(KEY_PREFIX + key, JSON.stringify(next), TTL_SEC); } catch (err) { warn(`lead store redis save failed, kept in memory: ${err.message}`); } } return next; } async function save(conversationUuid, slots) { const key = String(conversationUuid || ''); if (!key) return null; const existing = (await get(conversationUuid)) || { slots: {}, createdFingerprints: [], opportunityId: null, stage: 'NEW' }; return saveState(conversationUuid, { ...existing, slots: { ...slots } }); } async function remove(conversationUuid) { const key = String(conversationUuid || ''); if (!key) return; memory.delete(key); if (client) { try { await client.del(KEY_PREFIX + key); } catch (err) { warn(`lead store redis delete failed: ${err.message}`); } } } return { get, getState: get, save, saveState, remove, redis: Boolean(client) }; } module.exports = { createLeadStore };