Files
crm-ai/bridge/src/leads.js
T
2026-10-23 10:35:28 +08:00

99 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 };