Files
users/services/IdentityService.ts
v0 afc77439bb feat: enhance user profile with detailed tags and asset evaluation
Optimize user detail page for asset assessment and tag info.

#VERCEL_SKIP

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-08-21 05:32:37 +00:00

119 lines
3.9 KiB
TypeScript
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.

// 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<string, string>
phoneToUser: Map<string, string>
usernameToUser: Map<string, string>
userStore: Map<string, Record<string, any>>
}
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<string, any>): Promise<IdentityMatch[]> {
const matches: IdentityMatch[] = []
const seen = new Set<string>()
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<string, IdentityMatch>()
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<string, any>): Promise<string> {
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<string, any>): Promise<void> {
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<string, any>) {
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)
}
}