独立产品 AI 智能通知:别用推送轰炸用户

📅 2026/7/11 0:42:11 👁️ 阅读次数 📝 编程学习
独立产品 AI 智能通知:别用推送轰炸用户

独立产品 AI 智能通知:别用推送轰炸用户

一、通知疲劳的恶性循环:为什么要重新思考推送策略

推送通知的打开率正在逐年下降。数据显示,移动应用的推送通知打开率已降至 3% 以下,而用户主动关闭通知权限的比例持续上升。对于独立产品而言,每一次不当的推送都在消耗用户对产品的信任。

传统的通知系统通常采用简单的触发规则:用户触发了某个事件就发送通知。这种"点火即发"的模式忽略了用户的上下文状态和接收意愿,导致用户在非预期时间收到大量无关通知。

AI 驱动的智能通知系统通过理解用户行为模式、内容相关性和时间窗口,实现"在正确的时间、以正确的方式、发送正确的内容"的精准触达。

graph TD A[触发事件] --> B[AI 通知决策引擎] B --> C{用户在线?} C -->|是| D[实时推送] C -->|否| E[延迟队列] B --> F{内容相关性<br/>评分} F -->|高| G[发送] F -->|低| H[丢弃/合并] B --> I{最优发送<br/>时间窗口} I -->|立即| D I -->|延迟| E E --> J[定时任务] J --> D D --> K[用户设备] K --> L{用户行为} L -->|打开| M[正向反馈] L -->|忽略| N[负向反馈] L -->|关闭| O[退订] M --> B N --> B

二、AI 通知决策引擎的架构设计

智能通知系统的核心是一个决策引擎,它综合多个维度来判断是否发送、何时发送、如何发送通知。

2.1 用户行为模式学习

通过分析用户的历史行为,AI 可以学习每个用户的最佳接收时间和内容偏好。但在实际落地中,冷启动阶段是一个核心挑战:新用户没有历史交互数据,偏好学习器无法给出有意义的推荐。

冷启动处理策略

对于数据不足的用户,系统采用基于"相似用户群"的协同策略。通过用户画像(注册渠道、设备类型、时间段偏好)匹配相似用户,借用群体的统计结论作为初始默认值,同时以较低的决策权重参与判断,留给新产品更灵活的首周探索期。cookieId在未登录阶段即可沉淀网页/落地页的行为基线,方便注册后无缝迁移,也让学习器在新用户注册后能立即使用这份预收集信息。

/** * 冷启动场景下的通知策略 * 通过用户画像匹配相似用户群体,借用群体偏好 */ async function resolveColdStartPreferences(userId, userProfile) { try { // 1. 检查是否有最近一周的同群组学习结果 const groupKey = buildGroupKey(userProfile); // 例如 "ios+organic+morning" const cachedGroup = await redis.get(`coldstart:${groupKey}`); if (cachedGroup) { return JSON.parse(cachedGroup); } // 2. 查询相似用户的聚合偏好(从最近活跃用户中采样) const similarUsers = await db.query(` SELECT notification_type, AVG(opened) as avg_open_rate, MODE() WITHIN GROUP (ORDER BY hour_of_day) as best_hour FROM notification_logs WHERE user_id IN ( SELECT user_id FROM user_profiles WHERE acquisition_channel = $1 AND device_type = $2 AND first_session_hour BETWEEN $3 AND $4 ) AND created_at > NOW() - INTERVAL '7 days' GROUP BY notification_type `, [userProfile.channel, userProfile.deviceType, userProfile.sessionHour - 2, userProfile.sessionHour + 2]); const groupPreferences = buildGroupPreferences(similarUsers); // 3. 缓存群组偏好(1小时有效) await redis.setEx( `coldstart:${groupKey}`, 3600, JSON.stringify(groupPreferences) ); return groupPreferences; } catch (error) { console.error('冷启动策略解析失败,使用默认偏好:', error); return getDefaultPreferences(); } } function buildGroupKey(profile) { return `${profile.deviceType}:${profile.channel}:${Math.floor(profile.sessionHour / 4)}`; }

常见踩坑:偏好数据的时效性衰减

用户的通知偏好会随时间变化。一个月前的"偏好晚上10点接收通知",可能因为用户换了作息而不再准确。在实际项目中,需要给近期数据更高的权重:

/** * 时间衰减加权:近7天的交互权重更高 */ function calculateWeightedPreferences(interactions) { const now = Date.now(); let weightedScores = {}; for (const interaction of interactions) { const daysAgo = (now - new Date(interaction.recordedAt).getTime()) / (86400000); // 指数衰减:越久远的数据权重越低 const weight = Math.exp(-daysAgo / 7); // 7天半衰期 if (interaction.opened) { const hour = new Date(interaction.sentAt).getHours(); weightedScores[hour] = (weightedScores[hour] || 0) + weight; } } return weightedScores; }

2.2 通知决策引擎核心逻辑

import { createClient } from '@redis/client';
import OpenAI from 'openai';

class NotificationPreferenceLearner {
constructor(redisClient, openaiClient) {
this.redis = redisClient;
this.ai = openaiClient;
}

/**

  • 分析用户通知交互历史,提取偏好特征

  • @param {string} userId - 用户 ID

  • @returns {Promise} 用户通知偏好
    */
    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 测试持续优化决策策略。