99 lines
3.1 KiB
JavaScript
99 lines
3.1 KiB
JavaScript
'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 };
|