feat: 初始化项目结构并添加核心功能模块
- 新增文档模板和导航结构 - 实现服务器基础API路由和控制器 - 添加扩展插件配置和前端框架 - 引入多租户和权限管理模块 - 集成日志和数据库配置 - 添加核心业务模型和类型定义
This commit is contained in:
96
server/src/services/MarketingService.ts
Normal file
96
server/src/services/MarketingService.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user