import db from '../config/database'; import { logger } from '../utils/logger'; import { AuditService } from './AuditService'; export interface AbandonedCartInfo { userId: string; cartId: string; productId: string; abandonedAt: Date; recoveryEmailSent?: boolean; } /** * MarketingService: 自动营销挽留系统 (BIZ_EXT_12) * 监听流失行为并自动执行阶梯优惠挽留策略 */ export class MarketingService { private static TABLE_CART = 'cf_cart'; /** * 扫描 24 小时前未下单的加购记录并触发挽留 */ static async processAbandonedCarts(): Promise { try { const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000); const dayBeforeYesterday = new Date(Date.now() - 48 * 60 * 60 * 1000); // 获取 24-48 小时内未结账且未发送挽留邮件的记录 const abandonedCarts = await db(this.TABLE_CART) .where({ status: 'ABANDONED', recoverySent: false }) .whereBetween('updated_at', [dayBeforeYesterday, yesterday]) .select('*'); for (const cart of abandonedCarts) { await this.triggerRecoveryStrategy(cart); } } catch (error: any) { logger.error(`[AbandonedRecovery] Failed to process carts: ${error.message}`); } } /** * 执行阶梯优惠挽留策略 */ private static async triggerRecoveryStrategy(cart: any): Promise { try { // 1. 获取用户历史价值 (LTV) 评分 const customerScore = await this.getCustomerScore(cart.userId); let discountCode: string; let emailContent: string; // 2. 阶梯策略:根据用户价值决定优惠力度 if (customerScore > 80) { discountCode = 'VIP_RECOVER_20'; // 8折 emailContent = `[VIP Exclusive] Still thinking about it? Here's 20% off just for you!`; } else if (customerScore > 50) { discountCode = 'SAVE_10_NOW'; // 9折 emailContent = `We noticed you left something behind. Enjoy 10% off if you order now!`; } else { discountCode = 'COME_BACK_5'; // 95折 emailContent = `Complete your purchase and get 5% off!`; } // 3. 记录审计并标记已发送 (Mock 邮件发送逻辑) logger.info(`[AbandonedRecovery] Sending ${discountCode} to user ${cart.userId} for cart ${cart.id}`); await db(this.TABLE_CART).where({ id: cart.id }).update({ recoverySent: true, recoveryStrategy: discountCode, recovery_at: new Date() }); // 发送审计日志 (BIZ_DEV_04) await AuditService.log({ module: 'MARKETING', action: 'RECOVERY_SENT', userId: cart.userId, tenantId: 'SYSTEM', // 实际应从用户上下文获取 traceId: `RECOVER-${Date.now()}`, resourceType: 'cart', resourceId: String(cart.id), afterSnapshot: { discountCode, emailContent }, source: 'node', result: 'success' }); } catch (error: any) { logger.error(`[AbandonedRecovery] Strategy failed for user ${cart.userId}: ${error.message}`); } } private static async getCustomerScore(userId: string): Promise { // 模拟根据历史 LTV 获取的用户得分 (0-100) return Math.floor(Math.random() * 100); } }