} 用户通知偏好 */ async learnUserPreferences(userId) { try { // 1. 从 Redis 获取用户通知交互历史 const interactionKey =user:${userId}:notification_interactions; const interactions = await this.redis.lRange(interactionKey, 0, -100);if (interactions.length < 10) { // 数据不足,返回默认策略 return this.getDefaultPreferences(); }
// 2. 解析交互数据 const parsedInteractions = interactions.map(i => JSON.parse(i));
// 3. 使用 AI 分析模式 const preferences = await this.aiAnalyzePreferences(parsedInteractions);
// 4. 缓存计算结果(1小时有效期) const cacheKey =user:${userId}:notification_preferences; await this.redis.setEx( cacheKey, 3600, JSON.stringify(preferences) );
return preferences;
} catch (error) { console.error('用户偏好学习失败:', error); return this.getDefaultPreferences(); }}
async aiAnalyzePreferences(interactions) { const prompt = ` 分析以下用户的通知交互历史,提取通知偏好特征。
交互数据 ${interactions.map((i, idx) =>${idx + 1}. 通知类型: ${i.type} 发送时间: ${new Date(i.sentAt).toLocaleString()} 是否打开: ${i.opened} 是否点击: ${i.clicked} 设备类型: ${i.deviceType}).join('\n')}
分析要求 请返回 JSON 格式的分析结果: { "optimalTimeWindows": [ { "start": "09:00", "end": "11:00", "probability": 0.8 } ], "preferredTypes": ["comment", "mention"], "unwantedTypes": ["marketing", "digest"], "devicePreference": "mobile", "frequencyPreference": "max_3_per_day", "bestDayOfWeek": ["Tuesday", "Wednesday", "Thursday"] } `;
const response = await this.ai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' }, temperature: 0.2 }); return JSON.parse(response.choices[0].message.content);}
getDefaultPreferences() { return { optimalTimeWindows: [ { start: '09:00', end: '11:00', probability: 0.5 }, { start: '14:00', end: '16:00', probability: 0.5 } ], preferredTypes: ['comment', 'mention', 'direct_message'], unwantedTypes: ['marketing'], devicePreference: 'any', frequencyPreference: 'max_5_per_day', bestDayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] }; }
/**
记录用户通知交互(供后续学习使用) */ async recordInteraction(userId, interaction) { const key =user:${userId}:notification_interactions; await this.redis.lPush(key, JSON.stringify({ ...interaction, recordedAt: new Date().toISOString() })); // 保留最近 200 条记录 await this.redis.lTrim(key, 0, 199);} }
### 2.2 通知决策引擎核心逻辑 ```javascript /** * AI 通知决策引擎 * 决定是否发送、何时发送、如何发送通知 */ class AINotificationDecisionEngine { constructor(preferenceLearner, options = {}) { this.learner = preferenceLearner; this.options = { // 每日最大通知数(硬上限) maxDailyNotifications: 5, // AI 决策开关 enableAIDecision: true, // 静默时间(不发送通知) quietHours: { start: 22, end: 8 }, ...options }; } /** * 处理通知发送请求 * @param {Object} notification - 通知对象 * @returns {Promise<Object>} 决策结果 */ async decide(notification) { try { const { userId, type, content, priority } = notification; // 1. 检查静默时间 if (this.isQuietHours()) { return { decision: 'defer', reason: '当前处于静默时间', scheduledTime: this.getNextActiveTime() }; } // 2. 检查用户当日已接收通知数 const dailyCount = await this.getDailyNotificationCount(userId); if (dailyCount >= this.options.maxDailyNotifications && priority !== 'urgent') { return { decision: 'defer', reason: `已达到每日通知上限(${this.options.maxDailyNotifications})`, scheduledTime: this.getTomorrowActiveTime() }; } // 3. 获取用户偏好 const preferences = await this.learner.learnUserPreferences(userId); // 4. 检查通知类型是否被用户屏蔽 if (preferences.unwantedTypes.includes(type)) { return { decision: 'suppress', reason: `用户不接收 ${type} 类型通知` }; } // 5. AI 决策:综合评估发送时机和方式 if (this.options.enableAIDecision) { const aiDecision = await this.aiDecide(notification, preferences); if (aiDecision.decision === 'suppress') { return aiDecision; } if (aiDecision.decision === 'defer') { return { ...aiDecision, scheduledTime: aiDecision.suggestedTime || this.getNextActiveTime() }; } } // 6. 批准发送 return { decision: 'send', reason: '通过所有检查', channel: this.selectChannel(notification, preferences), scheduledTime: this.getOptimalSendTime(preferences) }; } catch (error) { console.error('通知决策失败:', error); // 失败时使用保守策略:延迟发送 return { decision: 'defer', reason: '决策引擎异常,延迟发送', scheduledTime: new Date(Date.now() + 30 * 60 * 1000) // 30分钟后 }; } } /** * AI 综合决策 */ async aiDecide(notification, preferences) { const prompt = ` 作为一个通知决策系统,判断是否应该发送以下通知。 ## 通知信息 - 类型: ${notification.type} - 优先级: ${notification.priority} - 内容摘要: ${notification.content.summary} ## 用户偏好 - 偏好的通知类型: ${preferences.preferredTypes.join(', ')} - 不想要的类型: ${preferences.unwantedTypes.join(', ')} - 最佳发送时间窗口: ${preferences.optimalTimeWindows.map(w => `${w.start}-${w.end}`).join(', ')} - 频率偏好: ${preferences.frequencyPreference} ## 当前时间 ${new Date().toLocaleString()} ## 决策要求 请返回 JSON: { "decision": "send" | "defer" | "suppress", "reason": "决策原因", "suggestedTime": "如果 defer,建议的发送时间(ISO 8601)" } `; const response = await this.learner.ai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' }, temperature: 0.2 }); return JSON.parse(response.choices[0].message.content); } isQuietHours() { const hour = new Date().getHours(); const { start, end } = this.options.quietHours; if (start > end) { // 跨天情况(如 22:00 - 08:00) return hour >= start || hour < end; } return hour >= start && hour < end; } getDailyNotificationCount(userId) { // 实际实现中查询 Redis 或数据库 return Promise.resolve(0); // 示意 } getOptimalSendTime(preferences) { // 基于用户偏好时间窗口选择最佳发送时间 const now = new Date(); const currentHour = now.getHours(); for (const window of preferences.optimalTimeWindows) { const [startHour] = window.start.split(':').map(Number); if (currentHour < startHour) { const scheduled = new Date(now); scheduled.setHours(startHour, 0, 0, 0); return scheduled; } } // 没有合适时间窗口,推迟到明天第一个窗口 const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const [firstStart] = preferences.optimalTimeWindows[0].start.split(':').map(Number); tomorrow.setHours(firstStart, 0, 0, 0); return tomorrow; } selectChannel(notification, preferences) { // 根据通知优先级和用户设备偏好选择发送渠道 if (notification.priority === 'urgent') { return 'push'; // 紧急通知强制推送 } if (preferences.devicePreference === 'mobile') { return 'push'; } return 'in-app'; // 默认使用应用内通知 } }三、通知内容个性化:从模板到 AI 生成 优质的 notification 内容是提高打开率的关键。AI 可以根据通知上下文和用户特征生成高度个性化的通知文案。
3.1 动态内容生成 /** * AI 通知内容生成器 * 为每个用户生成个性化的通知文案 */ class AINotificationContentGenerator { constructor(openaiClient) { this.client = openaiClient; } /** * 生成个性化通知内容 * @param {Object} params - 生成参数 * @returns {Promise<Object>} 生成的内容 */ async generateContent(params) { const { notificationType, triggerEvent, recipientUser, senderUser, entityInfo, language = 'zh-CN' } = params; const prompt = this.buildContentPrompt({ notificationType, triggerEvent, recipientUser, senderUser, entityInfo, language }); try { const response = await this.client.chat.completions.create({ model: 'gpt-4', messages: [ { role: 'system', content: '你是一位专业的通知文案撰写者。你的目标是撰写简洁、相关且不会引起用户反感的通知内容。保持口语化,避免营销套话。' }, { role: 'user', content: prompt } ], response_format: { type: 'json_object' }, temperature: 0.7 }); const result = JSON.parse(response.choices[0].message.content); // 验证生成结果 if (!result.title || !result.body) { throw new Error('生成的内容格式不完整'); } return { title: result.title.slice(0, 50), // 标题限制 50 字符 body: result.body.slice(0, 150), // 正文限制 150 字符 actionText: result.actionText || '查看详情', metadata: result.metadata || {} }; } catch (error) { console.error('通知内容生成失败:', error); // 返回默认内容 return this.getDefaultContent(notificationType, triggerEvent); } } buildContentPrompt(params) { return ` 为以下场景生成通知内容(简体中文)。 ## 场景信息 - 通知类型: ${params.notificationType} - 触发事件: ${params.triggerEvent} - 接收用户名称: ${params.recipientUser.name} - 发送用户名称: ${params.senderUser?.name || '系统'} - 相关实体: ${params.entityInfo.type} - ${params.entityInfo.title} ## 要求 1. 标题不超过 50 个字符 2. 正文不超过 150 个字符 3. 使用简体中文,口语化表达 4. 避免使用"尊敬的用户"等通用称呼 5. 包含具体的价值信息(如评论内容摘要) ## 返回 JSON 格式 { "title": "通知标题", "body": "通知正文", "actionText": "操作按钮文字", "metadata": {} } `; } getDefaultContent(type, event) { const defaults = { comment: { title: '新的评论', body: '有人评论了你的内容', actionText: '查看评论' }, mention: { title: '有人提到了你', body: '有人在内容中提到了你', actionText: '查看' } }; return defaults[type] || { title: '新通知', body: '你有新的通知', actionText: '查看' }; } /** * 多语言和语气适配 * 根据用户所在地区和交互历史,动态调整通知的语言和语气 */ async adaptLanguageAndTone(params) { const { recipientUser, baseContent } = params; // 1. 检测用户语言偏好 const userLocale = recipientUser.locale || 'zh-CN'; const preferredTone = await this.getUserTonePreference(recipientUser.id); // 2. 本地化适配 const adapted = await this.client.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: `将以下通知文案适配为 ${userLocale},使用${preferredTone}的语气: 标题: ${baseContent.title} 正文: ${baseContent.body} 要求: - 保持原意不变 - 使用当地化的表达方式(非直译) - ${preferredTone === 'casual' ? '使用口语化、亲近的表达' : '使用正式、得体的表达'}` }], temperature: 0.5 }); return JSON.parse(adapted.choices[0].message.content); } async getUserTonePreference(userId) { // 基于用户历史交互判断偏好的语气 const recentInteractions = await this.redis.lRange( `user:${userId}:notification_interactions`, 0, -50 ); if (recentInteractions.length < 5) return 'neutral'; // 分析用户对"casual"通知的打开率 vs "formal"通知的打开率 const parsed = recentInteractions.map(i => JSON.parse(i)); const casualOpenRate = getOpenRate(parsed, 'casual'); const formalOpenRate = getOpenRate(parsed, 'formal'); return casualOpenRate > formalOpenRate ? 'casual' : 'formal'; } }四、通知聚合与智能摘要:减少打扰频率 对于高频触发的通知(如"收到 20 条新评论"),逐条发送会造成严重的通知疲劳。AI 可以将相似通知聚合并生成摘要。
聚合维度与分组策略
通知聚合的关键在于选择合适的聚合维度。常见的分组策略包括:按通知类型(同类评论聚合成一拨)、按时间窗口(15分钟内)、按关联实体(同一篇文章的互动)。选择错误的聚合维度常见场景:将"好友私信"和"系统更新"混在一起聚合,结果摘要写成了"你收到 3 条消息和 1 条系统通知",用户完全搞不清楚状况,实际上私信和系统通知应该独立聚合、独立推送。
聚合时效性约束
并非所有通知都适合长时间等待聚合。安全类通知(异常登录、密码变更)必须即时发送,不能进入聚合批次。在tryAggregate流程中需要加入紧急通知跳过开关,确保安全通知零延迟到达用户设备。
/** * 聚合决策器:根据通知类型判断是否需要即时发送 */ function shouldBypassAggregation(notification) { const immediateTypes = [ 'security_alert', // 安全告警 'payment_failure', // 支付失败 'account_suspension', // 账号异常 'otp_verification' // 验证码 ]; return immediateTypes.includes(notification.type) || notification.priority === 'critical'; }踩坑:聚合摘要的质量控制
AI 生成的摘要在某些极端场景下会"过度总结",丢失关键信息。例如:5 条通知中有 3 条来自不同用户对同一话题的讨论、各有不同观点,摘要如果只输出"收到 5 条评论",用户无法判断是否值得打开。解决方案是携带预览数据——摘要正文里嵌入第一条/最有价值的单条通知的截断内容,帮助用户做打开决策。
async function generateSummary(batchData) { // 1. 从批次中提取最有价值的 1-2 条详情作为预览 const previewItems = batchData .sort((a, b) => (b.priorityScore || 0) - (a.priorityScore || 0)) .slice(0, 2) .map(item => item.body?.slice(0, 30) || item.title); const prompt = ` 将以下 ${batchData.length} 条通知聚合成一条摘要通知。 ## 通知列表 ${batchData.map((n, i) => `${i + 1}. ${n.title}: ${n.body}`).join('\n')} ## 关键预览 ${previewItems.map((p, i) => `- ${p}`).join('\n')} ## 要求 生成简洁的摘要,说明有多少条通知、涉及哪些内容。 标题不超过 50 字,正文不超过 100 字。 必须在正文中包含前几条的关键信息摘要。 返回 JSON: { "title": "摘要标题", "body": "摘要正文(含关键预览)" } `; const response = await this.ai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' } }); return JSON.parse(response.choices[0].message.content); } class NotificationAggregator { constructor(redisClient, openaiClient) { this.redis = redisClient; this.ai = openaiClient; } /** * 尝试聚合通知 * @param {Object} newNotification - 新通知 * @returns {Promise<Object|null>} 聚合后的通知或 null(表示不聚合) */ async tryAggregate(newNotification) { const { userId, type, groupKey } = newNotification; // 1. 查找可聚合的通知批次 const batchKey = `notification:batch:${userId}:${type}:${groupKey || 'default'}`; const batch = await this.redis.get(batchKey); if (!batch) { // 没有可聚合的批次,创建新批次 await this.redis.setEx( batchKey, 3600, // 1小时内的同类通知会被聚合 JSON.stringify([newNotification]) ); return null; // 返回 null 表示暂不发送,等待更多通知 } // 2. 将新通知加入批次 const batchData = JSON.parse(batch); batchData.push(newNotification); // 3. 判断是否达到发送阈值 if (batchData.length >= 5 || this.isBatchOldEnough(batchData)) { // 生成聚合摘要 const summary = await this.generateSummary(batchData); // 清除批次 await this.redis.del(batchKey); return { type: `${type}_summary`, title: summary.title, body: summary.body, metadata: { originalCount: batchData.length, notifications: batchData.slice(0, 5) // 携带前 5 条详情 } }; } // 4. 更新批次 await this.redis.setEx(batchKey, 3600, JSON.stringify(batchData)); return null; } async generateSummary(batchData) { const prompt = ` 将以下 ${batchData.length} 条通知聚合成一条摘要通知。 ## 通知列表 ${batchData.map((n, i) => `${i + 1}. ${n.title}: ${n.body}`).join('\n')} ## 要求 生成简洁的摘要,说明有多少条通知、涉及哪些内容。 标题不超过 50 字,正文不超过 100 字。 返回 JSON: { "title": "摘要标题", "body": "摘要正文" } `; const response = await this.ai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' } }); return JSON.parse(response.choices[0].message.content); } isBatchOldEnough(batchData) { const firstTime = new Date(batchData[0].createdAt).getTime(); const now = Date.now(); return (now - firstTime) > 30 * 60 * 1000; // 超过 30 分钟就发送 } }五、总结 AI 驱动的智能通知系统通过用户行为学习、内容个性化生成和通知聚合策略,在提升通知价值的同时显著降低对用户打扰。核心设计原则是"尊重用户注意力":每次通知都应该是用户想要的、在合适的时间收到的、内容相关的信息。
落地路线:建立用户通知交互数据收集 → 实现偏好学习和决策引擎 → 集成 AI 内容生成 → 添加通知聚合逻辑 → 建立 A/B 测试持续优化决策策略。