Files
makemd/server/src/services/MarketingService.ts
wurenzhi 136c2fa579 feat: 初始化项目结构并添加核心功能模块
- 新增文档模板和导航结构
- 实现服务器基础API路由和控制器
- 添加扩展插件配置和前端框架
- 引入多租户和权限管理模块
- 集成日志和数据库配置
- 添加核心业务模型和类型定义
2026-03-17 22:07:19 +08:00

97 lines
3.2 KiB
TypeScript

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<void> {
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<void> {
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<number> {
// 模拟根据历史 LTV 获取的用户得分 (0-100)
return Math.floor(Math.random() * 100);
}
}