微信订阅消息状态检测与同步方案.md 8.6 KB

微信小程序订阅消息状态检查解决方案

问题背景

在微信小程序开发中,订阅消息是一种重要的用户触达方式。然而,开发者经常面临一个问题:如何准确地检测用户是否订阅了特定的模板消息?当用户在设置中手动关闭订阅权限时,如何在前端及时反映这一状态变化?

传统的做法是依赖本地存储来记录用户订阅状态,但这存在明显的缺陷:当用户在微信设置中手动关闭订阅权限时,应用无法感知这一变化,导致界面上显示的状态与实际状态不一致。

解决方案

核心API:uni.getSetting withSubscriptions参数

微信小程序提供了检查订阅消息状态的官方API,关键在于使用 withSubscriptions: true 参数:

uni.getSetting({
  withSubscriptions: true, // 关键参数
  success(res) {
    console.log(res.subscriptionsSetting);
    // 包含 mainSwitch 和 itemSettings 字段
  }
});

subscriptionsSetting结构说明

返回的 subscriptionsSetting 对象包含以下重要字段:

  1. mainSwitch: Boolean类型,表示订阅消息的总开关
  2. itemSettings: 对象类型,包含每个模板ID的具体状态
    • 模板ID作为键名
    • 状态值包括:'accept'(接受)、'reject'(拒绝)、'ban'(封禁)

核心逻辑

我们通过 uni.getSetting({ withSubscriptions: true }) 获取微信本地状态,并与服务器返回的 overview 数据进行对比同步。

状态定义

  • mainSwitch: 微信订阅消息总开关。
  • templateStatus: 特定模板(TEMPLATE_ID)的订阅状态('accept' 表示长期订阅)。
  • notificationsEnabled: 服务器记录的通知总开关。
  • subscriptionAvailable: 服务器记录的订阅可用性('NONE' | 'ONCE' | 'MULTI')。

同步策略

根据微信本地获取的状态,分为以下三种情况进行处理:

情况一:用户不允许推送消息 (mainSwitch: false)

  • 操作
    • 检查服务器的 notificationsEnabled。如果是开启的(true),先将其设定为关闭(false),然后积极尝试请求订阅(调用 requestSubscribeMessage),因为这可能是用户误操作关闭的。
    • 检查服务器的 subscriptionAvailable。如果不为 "NONE",则修正为 "NONE"
  • 目的:确保服务器状态反映用户已关闭通知,同时尝试挽回订阅。

情况二:用户同意发通知,但没有长期订阅 (mainSwitch: true, templateStatus !== 'accept')

  • 操作
    • 检查服务器的 subscriptionAvailable
    • 如果为 "NONE":说明权限已消耗或未获得。只有在服务器 notificationsEnabledtrue,才发起订阅请求。成功后根据微信侧 getSetting 的真实结果设定为 "ONCE""MULTI"
    • 如果为 "ONCE":保持现状,无需重复请求。
    • 如果为 "MULTI":状态不一致(服务器认为是长期但微信本地不是),先将服务器设定为 "NONE"只有在服务器 notificationsEnabledtrue,才重新发起订阅请求。成功后根据微信侧 getSetting 的真实结果设定为 "ONCE""MULTI"
  • 目的:在用户允许通知的前提下,确保至少拥有一次发送权限,并修正不一致的长期订阅状态。同时遵循“若用户关闭通知则不主动打扰”的原则。

情况三:用户同意长期发送消息 (mainSwitch: true, templateStatus === 'accept')

  • 操作
    • 确保服务器的 subscriptionAvailable"MULTI"。如果不是,则进行更正。
  • 目的:确保服务器充分利用用户的长期订阅授权。

特别说明

  • notificationsEnabled 的变更限制:在自动同步逻辑中,只有在本地微信获取到 mainSwitch: false 且服务器为 true 时,才会将服务器状态改为 false
  • UI 状态回滚与引导:在手动或自动触发订阅请求(requestSubscription)时:
    • 若因主开关关闭导致请求失败(如 errCode: 20004),程序会自动将 notificationsEnabled 设为 false 并同步至服务器,同时弹出权限引导。
    • 若用户在手动开启开关时拒绝了订阅弹窗,程序会回滚开关状态,并强制弹出权限引导,以确保用户了解如何恢复通知能力。
    • 自动同步时的引导:若服务器记录的 notificationsEnabledtrue,但在自动检查过程中发现微信权限已关闭或订阅请求失败,程序也会弹出权限引导,帮助用户恢复预期的通知状态。
    • 这确保了 UI 上的开关状态始终真实反映用户当前的推送接收能力,避免用户误以为通知已开启。

代码实现参考 (src/pages/patient/health/reminder.vue)

项目中实现了统一的 checkSubscriptionStatus 函数来执行上述逻辑:

const checkSubscriptionStatus = async (passive = false) => {
  const info = await fetchSubscriptionSettings()
  const { mainSwitch, templateStatus } = info
  
  let needsSave = false
  let shouldRequest = false
  const wasEnabledBeforeCheck = notificationsEnabled.value

  if (mainSwitch === false) {
    if (notificationsEnabled.value === true) {
      notificationsEnabled.value = false
      needsSave = true
      shouldRequest = true // 积极请求
    }
    if (subscriptionAvailable.value !== 'NONE') {
      subscriptionAvailable.value = 'NONE'
      needsSave = true
    }
  } else if (mainSwitch === true && templateStatus !== 'accept') {
    if (subscriptionAvailable.value === 'NONE' && notificationsEnabled.value === true) {
      shouldRequest = true
    } else if (subscriptionAvailable.value === 'MULTI') {
      subscriptionAvailable.value = 'NONE'
      needsSave = true
      if (notificationsEnabled.value === true) {
        shouldRequest = true
      }
    }
  } else if (mainSwitch === true && templateStatus === 'accept') {
    if (subscriptionAvailable.value !== 'MULTI') {
      subscriptionAvailable.value = 'MULTI'
      needsSave = true
    }
  }

  if (needsSave) {
    await saveReminders()
  }
  if (shouldRequest) {
    await requestSubscription(false, wasEnabledBeforeCheck)
  }
}

注意事项

  1. 触发时机:该检查在页面 onShow(被动检查)和用户手动操作(主动检查)时触发。
  2. 用户交互requestSubscribeMessage 在某些平台上必须由用户点击触发。在 onShow 中自动触发可能会受到平台限制或弹出频率限制。
  3. 权限引导:当用户彻底拒绝或关闭开关时,通过 showPermissionGuide 弹窗引导用户前往系统设置开启。

注意事项

  1. API兼容性:确保目标平台支持 withSubscriptions 参数
  2. 状态同步:定期检查状态以确保与用户设置同步
  3. 用户体验:当检测到订阅状态变更时,应给予用户适当提示
  4. 错误处理:妥善处理API调用失败的情况

总结

通过使用 uni.getSetting 配合 withSubscriptions: true 参数,我们可以准确地检测用户对订阅消息的设置,实现前端独立的状态管理,无需依赖后端服务。这种方法能够实时反映用户在微信设置中的操作,提升用户体验和应用的准确性。

修改要点:

  • onShow 中原本重复的 uni.getSetting({...}) 代码替换为 checkSubscriptionStatus(true),实现代码复用和行为统一(onShow 执行被动检查)。
  • openSettings 中在用户从系统设置返回时仍然调用 checkSubscriptionStatus()(默认主动模式),以便在用户在系统设置中开启权限后,将本地状态同步为已开启并关闭权限引导。

示例调用:

// 页面返回前台时(被动检查)
checkSubscriptionStatus(true)

// 用户在设置页返回时(主动检查)
checkSubscriptionStatus()

为什么这样做:

  • 被动检查(如 onShow)不应在检测到接受时主动修改用户在界面上的显式选择,避免悖论或意外更改;但当检测到权限被明确关闭时,应被动同步并关闭本地功能,防止应用继续假定有推送能力。
  • 主动检查(如用户明确进入设置或手动刷新)则可以安全地将本地状态与系统设置对齐,并在必要时显示权限引导帮助用户恢复权限。

已在代码中完成该改动,且相关逻辑已保存到 reminder.vue 中的注释与实现里。