import { logger } from '../utils/logger'; import BlacklistDatabaseService from './BlacklistDatabaseService'; import BlacklistService from './BlacklistService'; export interface BuyerBehavior { buyer_id: string; platform: string; platform_buyer_id: string; order_count: number; return_rate: number; chargeback_rate: number; complaint_rate: number; refund_rate: number; average_order_value: number; purchase_frequency: number; review_score: number; suspicious_behavior: boolean; abnormal_activity: boolean; location_mismatch: boolean; payment_issues: number; account_age_days: number; device_changes: number; ip_changes: number; velocity_score: number; risk_indicators: string[]; last_purchase_date: Date; first_purchase_date: Date; } export interface RiskAssessmentResult { buyer_id: string; platform: string; platform_buyer_id: string; risk_score: number; risk_level: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; risk_factors: string[]; recommendations: string[]; assessment_date: Date; is_blacklisted: boolean; blacklist_reasons?: string[]; confidence_score: number; } export interface RiskRule { id: string; name: string; description: string; condition: string; weight: number; threshold: number; action: 'ALERT' | 'BLOCK' | 'MONITOR'; enabled: boolean; created_by: string; created_at: Date; updated_at: Date; } export interface RiskStatistics { total_assessments: number; high_risk_count: number; medium_risk_count: number; low_risk_count: number; critical_risk_count: number; blacklist_conversion_rate: number; false_positives: number; false_negatives: number; by_platform: Record; by_risk_level: Record; average_risk_score: number; risk_trend: { date: string; average_score: number; }[]; } export interface RiskAssessmentRequest { tenant_id: string; shop_id: string; task_id?: string; trace_id: string; buyer_behavior: BuyerBehavior; include_blacklist_check: boolean; assessment_reason: string; } export default class RiskAssessmentService { private static instance: RiskAssessmentService; private blacklistService: BlacklistService; static getInstance(): RiskAssessmentService { if (!RiskAssessmentService.instance) { RiskAssessmentService.instance = new RiskAssessmentService(); } return RiskAssessmentService.instance; } constructor() { this.blacklistService = BlacklistService.getInstance(); } async initTables(): Promise { // 初始化风险规则表 const hasRiskRulesTable = await db.schema.hasTable('cf_risk_rule'); if (!hasRiskRulesTable) { logger.info('[RiskAssessmentService] Creating cf_risk_rule table...'); await db.schema.createTable('cf_risk_rule', (table) => { table.string('id', 36).primary(); table.string('name', 255).notNullable(); table.text('description').notNullable(); table.text('condition').notNullable(); table.decimal('weight', 5, 2).notNullable(); table.decimal('threshold', 5, 2).notNullable(); table.enum('action', ['ALERT', 'BLOCK', 'MONITOR']).notNullable(); table.boolean('enabled').notNullable().defaultTo(true); table.string('created_by', 64).notNullable(); table.datetime('created_at').notNullable().defaultTo(db.fn.now()); table.datetime('updated_at').notNullable().defaultTo(db.fn.now()); }); } // 初始化风险评估记录表 const hasRiskAssessmentsTable = await db.schema.hasTable('cf_risk_assessment'); if (!hasRiskAssessmentsTable) { logger.info('[RiskAssessmentService] Creating cf_risk_assessment table...'); await db.schema.createTable('cf_risk_assessment', (table) => { table.string('id', 36).primary(); table.string('tenant_id', 64).notNullable().index(); table.string('shop_id', 64).notNullable(); table.string('task_id', 36); table.string('trace_id', 64).notNullable(); table.string('buyer_id', 64).notNullable(); table.string('platform', 64).notNullable().index(); table.string('platform_buyer_id', 64).notNullable().index(); table.decimal('risk_score', 5, 2).notNullable().index(); table.enum('risk_level', ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).notNullable().index(); table.json('risk_factors').notNullable(); table.json('recommendations').notNullable(); table.boolean('is_blacklisted').notNullable(); table.json('blacklist_reasons'); table.decimal('confidence_score', 5, 2).notNullable(); table.text('assessment_reason').notNullable(); table.json('buyer_behavior').notNullable(); table.datetime('assessment_date').notNullable().defaultTo(db.fn.now()); table.datetime('created_at').notNullable().defaultTo(db.fn.now()); }); } } async assessRisk(request: RiskAssessmentRequest): Promise { try { logger.info(`[RiskAssessmentService] Assessing risk for buyer: ${request.buyer_behavior.platform_buyer_id}, platform=${request.buyer_behavior.platform}, traceId=${request.trace_id}`); // 计算基础风险分数 let riskScore = this.calculateBaseRiskScore(request.buyer_behavior); // 应用风险规则 const { adjustedScore, riskFactors } = await this.applyRiskRules(request.buyer_behavior, riskScore); riskScore = adjustedScore; // 检查黑名单状态 let isBlacklisted = false; let blacklistReasons: string[] = []; if (request.include_blacklist_check) { const blacklistCheck = await this.blacklistService.checkBlacklist( request.buyer_behavior.platform, request.buyer_behavior.platform_buyer_id, request.tenant_id ); isBlacklisted = blacklistCheck.is_blacklisted; blacklistReasons = blacklistCheck.reasons || []; // 如果在黑名单中,提高风险分数 if (isBlacklisted) { riskScore = Math.min(100, riskScore + 30); riskFactors.push('Buyer is in blacklist'); } } // 确定风险等级 const riskLevel = this.calculateRiskLevel(riskScore); // 生成建议 const recommendations = this.generateRecommendations(riskLevel, riskFactors, isBlacklisted); // 计算置信度分数 const confidenceScore = this.calculateConfidenceScore(request.buyer_behavior, riskFactors.length); // 保存评估记录 await this.saveAssessmentRecord({ ...request, risk_score: riskScore, risk_level: riskLevel, risk_factors: riskFactors, recommendations: recommendations, is_blacklisted: isBlacklisted, blacklist_reasons: blacklistReasons, confidence_score: confidenceScore }); const result: RiskAssessmentResult = { buyer_id: request.buyer_behavior.buyer_id, platform: request.buyer_behavior.platform, platform_buyer_id: request.buyer_behavior.platform_buyer_id, risk_score: riskScore, risk_level: riskLevel, risk_factors: riskFactors, recommendations: recommendations, assessment_date: new Date(), is_blacklisted: isBlacklisted, blacklist_reasons: blacklistReasons, confidence_score: confidenceScore }; logger.info(`[RiskAssessmentService] Risk assessment completed: score=${riskScore}, level=${riskLevel}, confidence=${confidenceScore}, traceId=${request.trace_id}`); return result; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to assess risk: ${error.message}, traceId=${request.trace_id}`); throw error; } } private calculateBaseRiskScore(behavior: BuyerBehavior): number { let score = 0; // 退货率 (0-20分) score += Math.min(20, behavior.return_rate * 20); // 退款率 (0-20分) score += Math.min(20, behavior.refund_rate * 20); // 拒付率 (0-25分) score += Math.min(25, behavior.chargeback_rate * 25); // 投诉率 (0-15分) score += Math.min(15, behavior.complaint_rate * 15); // 可疑行为 (0-10分) if (behavior.suspicious_behavior) score += 10; // 异常活动 (0-10分) if (behavior.abnormal_activity) score += 10; // 位置不匹配 (0-8分) if (behavior.location_mismatch) score += 8; // 支付问题 (0-5分 per issue) score += Math.min(15, behavior.payment_issues * 5); // 设备变更 (0-5分 per change) score += Math.min(10, behavior.device_changes * 2); // IP变更 (0-5分 per change) score += Math.min(10, behavior.ip_changes * 2); // 速度评分 (0-10分) score += Math.min(10, behavior.velocity_score); // 账户年龄 (新账户风险更高) if (behavior.account_age_days < 30) score += 10; else if (behavior.account_age_days < 90) score += 5; return Math.min(100, score); } private async applyRiskRules(behavior: BuyerBehavior, baseScore: number): Promise<{ adjustedScore: number; riskFactors: string[] }> { const riskFactors: string[] = []; let adjustedScore = baseScore; // 获取启用的风险规则 const rules = await db('cf_risk_rule').where({ enabled: true }); for (const rule of rules) { try { // 简单的规则评估逻辑 if (this.evaluateRule(rule, behavior)) { adjustedScore = Math.min(100, adjustedScore + rule.weight); riskFactors.push(rule.name); } } catch (error) { logger.warn(`[RiskAssessmentService] Failed to evaluate rule ${rule.name}: ${error}`); } } return { adjustedScore, riskFactors }; } private evaluateRule(rule: any, behavior: BuyerBehavior): boolean { // 简单的规则评估实现 // 实际应用中可能需要更复杂的规则引擎 switch (rule.name) { case 'High Return Rate': return behavior.return_rate > 0.3; case 'Chargeback History': return behavior.chargeback_rate > 0; case 'Suspicious Activity': return behavior.suspicious_behavior; case 'New Account': return behavior.account_age_days < 30; case 'Multiple Device Changes': return behavior.device_changes > 3; case 'Multiple IP Changes': return behavior.ip_changes > 3; default: return false; } } private calculateRiskLevel(score: number): 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' { if (score >= 80) return 'CRITICAL'; if (score >= 60) return 'HIGH'; if (score >= 30) return 'MEDIUM'; return 'LOW'; } private generateRecommendations(riskLevel: string, riskFactors: string[], isBlacklisted: boolean): string[] { const recommendations: string[] = []; switch (riskLevel) { case 'CRITICAL': recommendations.push('Block buyer immediately'); recommendations.push('Add to blacklist'); recommendations.push('Review all past transactions'); recommendations.push('Consider legal action if fraud is suspected'); break; case 'HIGH': recommendations.push('Place order on hold for manual review'); recommendations.push('Request additional verification'); recommendations.push('Limit order amount'); recommendations.push('Monitor future transactions closely'); break; case 'MEDIUM': recommendations.push('Monitor transaction closely'); recommendations.push('Set lower order limits'); recommendations.push('Require signature for delivery'); break; case 'LOW': recommendations.push('Process order normally'); recommendations.push('Continue monitoring'); break; } if (isBlacklisted) { recommendations.push('Review blacklist entry details'); recommendations.push('Consider permanent block if repeat offender'); } if (riskFactors.includes('High Return Rate')) { recommendations.push('Consider restocking fees'); recommendations.push('Review return policy compliance'); } if (riskFactors.includes('Chargeback History')) { recommendations.push('Use secure payment methods'); recommendations.push('Keep detailed transaction records'); } return recommendations; } private calculateConfidenceScore(behavior: BuyerBehavior, riskFactorCount: number): number { let confidence = 50; // 基础置信度 // 数据完整性提高置信度 if (behavior.order_count > 0) confidence += 10; if (behavior.account_age_days > 30) confidence += 10; if (riskFactorCount > 0) confidence += 15; if (behavior.review_score > 0) confidence += 5; // 数据不足降低置信度 if (behavior.order_count === 0) confidence -= 20; if (behavior.account_age_days < 7) confidence -= 15; return Math.max(10, Math.min(95, confidence)); } private async saveAssessmentRecord(data: any): Promise { const id = uuidv4(); await db('cf_risk_assessment').insert({ id, tenant_id: data.tenant_id, shop_id: data.shop_id, task_id: data.task_id, trace_id: data.trace_id, buyer_id: data.buyer_behavior.buyer_id, platform: data.buyer_behavior.platform, platform_buyer_id: data.buyer_behavior.platform_buyer_id, risk_score: data.risk_score, risk_level: data.risk_level, risk_factors: data.risk_factors, recommendations: data.recommendations, is_blacklisted: data.is_blacklisted, blacklist_reasons: data.blacklist_reasons, confidence_score: data.confidence_score, assessment_reason: data.assessment_reason, buyer_behavior: data.buyer_behavior, assessment_date: new Date(), created_at: new Date() }); } async getRiskAssessments(tenantId: string, filter?: { platform?: string; risk_level?: string; start_date?: Date; end_date?: Date; limit?: number; offset?: number; }): Promise<{ assessments: any[]; total: number; }> { try { let query = db('cf_risk_assessment').where({ tenant_id: tenantId }); if (filter) { if (filter.platform) { query = query.where({ platform: filter.platform }); } if (filter.risk_level) { query = query.where({ risk_level: filter.risk_level }); } if (filter.start_date) { query = query.where('assessment_date', '>=', filter.start_date); } if (filter.end_date) { query = query.where('assessment_date', '<=', filter.end_date); } } const total = await query.count('id as count').first(); const assessments = await query .orderBy('assessment_date', 'desc') .limit(filter?.limit || 100) .offset(filter?.offset || 0); return { assessments, total: total ? parseInt(total.count as string) : 0 }; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to get risk assessments: ${error.message}`); return { assessments: [], total: 0 }; } } async getRiskStatistics(tenantId: string, days: number = 30): Promise { try { const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const totalAssessments = await db('cf_risk_assessment') .where({ tenant_id: tenantId }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const highRiskCount = await db('cf_risk_assessment') .where({ tenant_id: tenantId, risk_level: 'HIGH' }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const mediumRiskCount = await db('cf_risk_assessment') .where({ tenant_id: tenantId, risk_level: 'MEDIUM' }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const lowRiskCount = await db('cf_risk_assessment') .where({ tenant_id: tenantId, risk_level: 'LOW' }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const criticalRiskCount = await db('cf_risk_assessment') .where({ tenant_id: tenantId, risk_level: 'CRITICAL' }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const blacklistConversion = await db('cf_risk_assessment') .where({ tenant_id: tenantId, is_blacklisted: true }) .where('assessment_date', '>=', startDate) .count('id as count') .first(); const byPlatform = await db('cf_risk_assessment') .where({ tenant_id: tenantId }) .where('assessment_date', '>=', startDate) .select('platform', db.raw('count(*) as count')) .groupBy('platform'); const byRiskLevel = await db('cf_risk_assessment') .where({ tenant_id: tenantId }) .where('assessment_date', '>=', startDate) .select('risk_level', db.raw('count(*) as count')) .groupBy('risk_level'); const averageScore = await db('cf_risk_assessment') .where({ tenant_id: tenantId }) .where('assessment_date', '>=', startDate) .avg('risk_score as avg_score') .first(); // 生成风险趋势数据 const riskTrend = []; for (let i = days - 1; i >= 0; i--) { const date = new Date(); date.setDate(date.getDate() - i); const dateStr = date.toISOString().split('T')[0]; const dayStart = new Date(date); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(date); dayEnd.setHours(23, 59, 59, 999); const dayAverage = await db('cf_risk_assessment') .where({ tenant_id: tenantId }) .whereBetween('assessment_date', [dayStart, dayEnd]) .avg('risk_score as avg_score') .first(); riskTrend.push({ date: dateStr, average_score: dayAverage?.avg_score ? parseFloat(dayAverage.avg_score as string) : 0 }); } const total = totalAssessments ? parseInt(totalAssessments.count as string) : 0; const blacklistCount = blacklistConversion ? parseInt(blacklistConversion.count as string) : 0; return { total_assessments: total, high_risk_count: highRiskCount ? parseInt(highRiskCount.count as string) : 0, medium_risk_count: mediumRiskCount ? parseInt(mediumRiskCount.count as string) : 0, low_risk_count: lowRiskCount ? parseInt(lowRiskCount.count as string) : 0, critical_risk_count: criticalRiskCount ? parseInt(criticalRiskCount.count as string) : 0, blacklist_conversion_rate: total > 0 ? (blacklistCount / total) * 100 : 0, false_positives: 0, // 需要额外逻辑计算 false_negatives: 0, // 需要额外逻辑计算 by_platform: byPlatform.reduce((acc, item) => { acc[item.platform] = parseInt(item.count as string); return acc; }, {} as Record), by_risk_level: byRiskLevel.reduce((acc, item) => { acc[item.risk_level] = parseInt(item.count as string); return acc; }, {} as Record), average_risk_score: averageScore?.avg_score ? parseFloat(averageScore.avg_score as string) : 0, risk_trend: riskTrend }; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to get risk statistics: ${error.message}`); return { total_assessments: 0, high_risk_count: 0, medium_risk_count: 0, low_risk_count: 0, critical_risk_count: 0, blacklist_conversion_rate: 0, false_positives: 0, false_negatives: 0, by_platform: {}, by_risk_level: {}, average_risk_score: 0, risk_trend: [] }; } } async createRiskRule(rule: Omit): Promise { try { const id = uuidv4(); const now = new Date(); const newRule: RiskRule = { ...rule, id, created_at: now, updated_at: now }; await db('cf_risk_rule').insert(newRule); logger.info(`[RiskAssessmentService] Risk rule created: id=${id}, name=${rule.name}`); return newRule; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to create risk rule: ${error.message}`); throw error; } } async updateRiskRule(id: string, updates: Partial): Promise { try { const result = await db('cf_risk_rule') .where({ id }) .update({ ...updates, updated_at: new Date() }); if (result > 0) { logger.info(`[RiskAssessmentService] Risk rule updated: id=${id}`); } return result > 0; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to update risk rule: ${error.message}`); return false; } } async getRiskRules(): Promise { try { const rules = await db('cf_risk_rule').where({ enabled: true }).orderBy('weight', 'desc'); return rules as RiskRule[]; } catch (error: any) { logger.error(`[RiskAssessmentService] Failed to get risk rules: ${error.message}`); return []; } } } // 导入必要的依赖 import db from '../config/database'; import { v4 as uuidv4 } from 'uuid';