chore: CRM-NEW baseline

This commit is contained in:
wangliang
2026-10-23 10:35:28 +08:00
commit aa499f6dbf
111 changed files with 15126 additions and 0 deletions
+98
View File
@@ -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 };