refactor(服务): 重构多个服务类并添加数据库表初始化方法 style(日志): 优化日志输出格式和内容 docs(任务概览): 更新恶意买家黑名单闭环任务状态 fix(ImageRecognitionService): 修复错误处理中的变量名错误 chore: 移除冗余代码并合并相似功能
627 lines
21 KiB
TypeScript
627 lines
21 KiB
TypeScript
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<string, number>;
|
|
by_risk_level: Record<string, number>;
|
|
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<void> {
|
|
// 初始化风险规则表
|
|
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<RiskAssessmentResult> {
|
|
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<void> {
|
|
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<RiskStatistics> {
|
|
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<string, number>),
|
|
by_risk_level: byRiskLevel.reduce((acc, item) => {
|
|
acc[item.risk_level] = parseInt(item.count as string);
|
|
return acc;
|
|
}, {} as Record<string, number>),
|
|
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<RiskRule, 'id' | 'created_at' | 'updated_at'>): Promise<RiskRule> {
|
|
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<RiskRule>): Promise<boolean> {
|
|
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<RiskRule[]> {
|
|
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';
|