// services/IdentityService.ts // 统一身份识别服务(内存索引),用于 /api/ingest 流程中匹配或创建用户。 // 与 v1.4 文档的统一用户结构保持一致(source_profiles / ai_insights / crm_info)[^5] export interface IdentityMatch { userId: string confidence: number reasons: string[] } type IndexMaps = { emailToUser: Map phoneToUser: Map usernameToUser: Map userStore: Map> } export class IdentityService { private static instance: IdentityService private index: IndexMaps private constructor() { this.index = { emailToUser: new Map(), phoneToUser: new Map(), usernameToUser: new Map(), userStore: new Map(), } } public static getInstance(): IdentityService { if (!IdentityService.instance) { IdentityService.instance = new IdentityService() } return IdentityService.instance } // 基于映射数据寻找可能的用户匹配 public async findMatchingIdentity(mappedData: Record): Promise { const matches: IdentityMatch[] = [] const seen = new Set() const tryAdd = (userId: string | undefined, confidence: number, reason: string) => { if (!userId) return if (seen.has(userId)) return seen.add(userId) matches.push({ userId, confidence, reasons: [reason] }) } if (mappedData.email) { tryAdd(this.index.emailToUser.get(String(mappedData.email).toLowerCase()), 0.95, "email") } if (mappedData.phone) { tryAdd(this.index.phoneToUser.get(String(mappedData.phone)), 0.92, "phone") } if (mappedData.username) { tryAdd(this.index.usernameToUser.get(String(mappedData.username).toLowerCase()), 0.7, "username") } // 简单加权:若同一userId命中多个关键字段,提升置信度 const aggregated = new Map() for (const m of matches) { const exists = aggregated.get(m.userId) if (!exists) { aggregated.set(m.userId, { ...m }) } else { exists.confidence = Math.min(0.99, exists.confidence + 0.05) exists.reasons = Array.from(new Set([...exists.reasons, ...m.reasons])) } } return Array.from(aggregated.values()).sort((a, b) => b.confidence - a.confidence) } // 创建新身份 public async createNewIdentity(mappedData: Record): Promise { const base = (mappedData.phone && `phone_${mappedData.phone}`) || (mappedData.email && `email_${String(mappedData.email).toLowerCase()}`) || (mappedData.username && `uname_${String(mappedData.username).toLowerCase()}`) || `anon_${Date.now()}` const userId = `user_${this.shortHash(base)}` this.index.userStore.set(userId, { userId, ...mappedData }) this.bindIndexes(userId, mappedData) return userId } // 更新身份(同步索引) public async updateIdentity(userId: string, mappedData: Record): Promise { const cur = this.index.userStore.get(userId) || { userId } const updated = { ...cur, ...mappedData } this.index.userStore.set(userId, updated) this.bindIndexes(userId, mappedData) } private bindIndexes(userId: string, mappedData: Record) { if (mappedData.email) { this.index.emailToUser.set(String(mappedData.email).toLowerCase(), userId) } if (mappedData.phone) { this.index.phoneToUser.set(String(mappedData.phone), userId) } if (mappedData.username) { this.index.usernameToUser.set(String(mappedData.username).toLowerCase(), userId) } } private shortHash(str: string): string { // 简单、稳定的字符串哈希(djb2 变体),避免依赖 let h = 5381 for (let i = 0; i < str.length; i++) { h = (h * 33) ^ str.charCodeAt(i) } // 转为正数并截断 return (h >>> 0).toString(36) } }