chore: CRM-NEW baseline
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
'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 };
|
||||
Reference in New Issue
Block a user