Ensure compatibility with both default and named imports for hook. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
233 lines
6.7 KiB
TypeScript
233 lines
6.7 KiB
TypeScript
// 身份识别与合并服务
|
|
// 实现基于数据字典标识的关键字段的匹配逻辑
|
|
|
|
import { getIdentityKeyFields } from "@/lib/data-dictionary"
|
|
|
|
export interface IdentityMatch {
|
|
userId: string
|
|
confidence: number
|
|
matchedFields: string[]
|
|
matchType: "exact" | "fuzzy" | "partial"
|
|
}
|
|
|
|
export interface UserIdentity {
|
|
userId: string
|
|
identityFields: Record<string, any>
|
|
}
|
|
|
|
export class IdentityService {
|
|
private static instance: IdentityService
|
|
private userIdentities: Map<string, UserIdentity> = new Map()
|
|
|
|
private constructor() {
|
|
// 初始化一些模拟数据
|
|
this.initializeMockData()
|
|
}
|
|
|
|
public static getInstance(): IdentityService {
|
|
if (!IdentityService.instance) {
|
|
IdentityService.instance = new IdentityService()
|
|
}
|
|
return IdentityService.instance
|
|
}
|
|
|
|
private initializeMockData() {
|
|
// 模拟已存在的用户身份数据
|
|
const mockUsers = [
|
|
{
|
|
userId: "user_global_id_1",
|
|
identityFields: {
|
|
phone: "13800138000",
|
|
email: "zhangsan@example.com",
|
|
username: "zhangsan",
|
|
fullName: "张三",
|
|
},
|
|
},
|
|
{
|
|
userId: "user_global_id_2",
|
|
identityFields: {
|
|
phone: "13912345678",
|
|
email: "lisi@example.com",
|
|
username: "lisi",
|
|
fullName: "李四",
|
|
},
|
|
},
|
|
]
|
|
|
|
mockUsers.forEach((user) => {
|
|
this.userIdentities.set(user.userId, user)
|
|
})
|
|
}
|
|
|
|
// 根据输入数据查找匹配的用户身份
|
|
public async findMatchingIdentity(inputData: Record<string, any>): Promise<IdentityMatch[]> {
|
|
const identityKeyFields = getIdentityKeyFields()
|
|
const matches: IdentityMatch[] = []
|
|
|
|
for (const [userId, userIdentity] of this.userIdentities) {
|
|
const matchResult = this.calculateMatch(inputData, userIdentity.identityFields, identityKeyFields)
|
|
if (matchResult.confidence > 0) {
|
|
matches.push({
|
|
userId,
|
|
...matchResult,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 按置信度排序
|
|
return matches.sort((a, b) => b.confidence - a.confidence)
|
|
}
|
|
|
|
// 计算匹配度
|
|
private calculateMatch(
|
|
inputData: Record<string, any>,
|
|
existingData: Record<string, any>,
|
|
keyFields: string[],
|
|
): { confidence: number; matchedFields: string[]; matchType: "exact" | "fuzzy" | "partial" } {
|
|
const matchedFields: string[] = []
|
|
let exactMatches = 0
|
|
let fuzzyMatches = 0
|
|
let totalFields = 0
|
|
|
|
for (const field of keyFields) {
|
|
if (inputData[field] && existingData[field]) {
|
|
totalFields++
|
|
|
|
if (this.isExactMatch(inputData[field], existingData[field])) {
|
|
exactMatches++
|
|
matchedFields.push(field)
|
|
} else if (this.isFuzzyMatch(inputData[field], existingData[field])) {
|
|
fuzzyMatches++
|
|
matchedFields.push(field)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (totalFields === 0) {
|
|
return { confidence: 0, matchedFields: [], matchType: "partial" }
|
|
}
|
|
|
|
const confidence = (exactMatches * 1.0 + fuzzyMatches * 0.7) / totalFields
|
|
|
|
let matchType: "exact" | "fuzzy" | "partial" = "partial"
|
|
if (exactMatches > 0 && fuzzyMatches === 0) {
|
|
matchType = "exact"
|
|
} else if (exactMatches > 0 || fuzzyMatches > 0) {
|
|
matchType = "fuzzy"
|
|
}
|
|
|
|
return { confidence, matchedFields, matchType }
|
|
}
|
|
|
|
// 精确匹配
|
|
private isExactMatch(value1: any, value2: any): boolean {
|
|
if (typeof value1 === "string" && typeof value2 === "string") {
|
|
return value1.toLowerCase().trim() === value2.toLowerCase().trim()
|
|
}
|
|
return value1 === value2
|
|
}
|
|
|
|
// 模糊匹配
|
|
private isFuzzyMatch(value1: any, value2: any): boolean {
|
|
if (typeof value1 === "string" && typeof value2 === "string") {
|
|
const str1 = value1.toLowerCase().trim()
|
|
const str2 = value2.toLowerCase().trim()
|
|
|
|
// 简单的相似度计算
|
|
const similarity = this.calculateStringSimilarity(str1, str2)
|
|
return similarity > 0.8
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 计算字符串相似度
|
|
private calculateStringSimilarity(str1: string, str2: string): number {
|
|
const longer = str1.length > str2.length ? str1 : str2
|
|
const shorter = str1.length > str2.length ? str2 : str1
|
|
|
|
if (longer.length === 0) {
|
|
return 1.0
|
|
}
|
|
|
|
const editDistance = this.levenshteinDistance(longer, shorter)
|
|
return (longer.length - editDistance) / longer.length
|
|
}
|
|
|
|
// 计算编辑距离
|
|
private levenshteinDistance(str1: string, str2: string): number {
|
|
const matrix = []
|
|
|
|
for (let i = 0; i <= str2.length; i++) {
|
|
matrix[i] = [i]
|
|
}
|
|
|
|
for (let j = 0; j <= str1.length; j++) {
|
|
matrix[0][j] = j
|
|
}
|
|
|
|
for (let i = 1; i <= str2.length; i++) {
|
|
for (let j = 1; j <= str1.length; j++) {
|
|
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
matrix[i][j] = matrix[i - 1][j - 1]
|
|
} else {
|
|
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
return matrix[str2.length][str1.length]
|
|
}
|
|
|
|
// 创建新的用户身份
|
|
public async createNewIdentity(inputData: Record<string, any>): Promise<string> {
|
|
const userId = `user_global_id_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
const identityKeyFields = getIdentityKeyFields()
|
|
|
|
const identityFields: Record<string, any> = {}
|
|
identityKeyFields.forEach((field) => {
|
|
if (inputData[field]) {
|
|
identityFields[field] = inputData[field]
|
|
}
|
|
})
|
|
|
|
this.userIdentities.set(userId, {
|
|
userId,
|
|
identityFields,
|
|
})
|
|
|
|
return userId
|
|
}
|
|
|
|
// 更新用户身份信息
|
|
public async updateIdentity(userId: string, updateData: Record<string, any>): Promise<void> {
|
|
const existingIdentity = this.userIdentities.get(userId)
|
|
if (existingIdentity) {
|
|
const identityKeyFields = getIdentityKeyFields()
|
|
identityKeyFields.forEach((field) => {
|
|
if (updateData[field]) {
|
|
existingIdentity.identityFields[field] = updateData[field]
|
|
}
|
|
})
|
|
this.userIdentities.set(userId, existingIdentity)
|
|
}
|
|
}
|
|
|
|
// 合并用户身份
|
|
public async mergeIdentities(primaryUserId: string, secondaryUserId: string): Promise<void> {
|
|
const primaryIdentity = this.userIdentities.get(primaryUserId)
|
|
const secondaryIdentity = this.userIdentities.get(secondaryUserId)
|
|
|
|
if (primaryIdentity && secondaryIdentity) {
|
|
// 合并身份字段,优先保留主身份的数据
|
|
Object.keys(secondaryIdentity.identityFields).forEach((field) => {
|
|
if (!primaryIdentity.identityFields[field] && secondaryIdentity.identityFields[field]) {
|
|
primaryIdentity.identityFields[field] = secondaryIdentity.identityFields[field]
|
|
}
|
|
})
|
|
|
|
this.userIdentities.set(primaryUserId, primaryIdentity)
|
|
this.userIdentities.delete(secondaryUserId)
|
|
}
|
|
}
|
|
}
|