# 微信小程序订阅消息状态检查解决方案 ## 问题背景 在微信小程序开发中,订阅消息是一种重要的用户触达方式。然而,开发者经常面临一个问题:如何准确地检测用户是否订阅了特定的模板消息?当用户在设置中手动关闭订阅权限时,如何在前端及时反映这一状态变化? 传统的做法是依赖本地存储来记录用户订阅状态,但这存在明显的缺陷:当用户在微信设置中手动关闭订阅权限时,应用无法感知这一变化,导致界面上显示的状态与实际状态不一致。 ## 解决方案 ### 核心API:uni.getSetting withSubscriptions参数 微信小程序提供了检查订阅消息状态的官方API,关键在于使用 `withSubscriptions: true` 参数: ```javascript uni.getSetting({ withSubscriptions: true, // 关键参数 success(res) { console.log(res.subscriptionsSetting); // 包含 mainSwitch 和 itemSettings 字段 } }); ``` ### subscriptionsSetting结构说明 返回的 [subscriptionsSetting](file:///D:/慢病APP/uniapp-ts/node_modules/@dcloudio/types/uni-app.d.ts#L150-L150) 对象包含以下重要字段: 1. **[mainSwitch](file:///D:/慢病APP/uniapp-ts/node_modules/@dcloudio/types/uni-app.d.ts#L151-L151)**: Boolean类型,表示订阅消息的总开关 2. **[itemSettings](file:///D:/慢病APP/uniapp-ts/node_modules/@dcloudio/types/uni-app.d.ts#L152-L152)**: 对象类型,包含每个模板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"`:说明权限已消耗或未获得。**只有在服务器 `notificationsEnabled` 为 `true` 时**,才发起订阅请求。成功后根据微信侧 `getSetting` 的真实结果设定为 `"ONCE"` 或 `"MULTI"`。 - 如果为 `"ONCE"`:保持现状,无需重复请求。 - 如果为 `"MULTI"`:状态不一致(服务器认为是长期但微信本地不是),先将服务器设定为 `"NONE"`。**只有在服务器 `notificationsEnabled` 为 `true` 时**,才重新发起订阅请求。成功后根据微信侧 `getSetting` 的真实结果设定为 `"ONCE"` 或 `"MULTI"`。 - **目的**:在用户允许通知的前提下,确保至少拥有一次发送权限,并修正不一致的长期订阅状态。同时遵循“若用户关闭通知则不主动打扰”的原则。 #### 情况三:用户同意长期发送消息 (mainSwitch: true, templateStatus === 'accept') - **操作**: - 确保服务器的 `subscriptionAvailable` 为 `"MULTI"`。如果不是,则进行更正。 - **目的**:确保服务器充分利用用户的长期订阅授权。 ### 特别说明 - **notificationsEnabled 的变更限制**:在自动同步逻辑中,只有在本地微信获取到 `mainSwitch: false` 且服务器为 `true` 时,才会将服务器状态改为 `false`。 - **UI 状态回滚与引导**:在手动或自动触发订阅请求(`requestSubscription`)时: - 若因主开关关闭导致请求失败(如 `errCode: 20004`),程序会自动将 `notificationsEnabled` 设为 `false` 并同步至服务器,同时弹出权限引导。 - 若用户在手动开启开关时拒绝了订阅弹窗,程序会回滚开关状态,并**强制弹出权限引导**,以确保用户了解如何恢复通知能力。 - **自动同步时的引导**:若服务器记录的 `notificationsEnabled` 为 `true`,但在自动检查过程中发现微信权限已关闭或订阅请求失败,程序也会弹出权限引导,帮助用户恢复预期的通知状态。 - 这确保了 UI 上的开关状态始终真实反映用户当前的推送接收能力,避免用户误以为通知已开启。 ## 代码实现参考 (src/pages/patient/health/reminder.vue) 项目中实现了统一的 `checkSubscriptionStatus` 函数来执行上述逻辑: ```typescript 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()`(默认主动模式),以便在用户在系统设置中开启权限后,将本地状态同步为已开启并关闭权限引导。 示例调用: ```javascript // 页面返回前台时(被动检查) checkSubscriptionStatus(true) // 用户在设置页返回时(主动检查) checkSubscriptionStatus() ``` 为什么这样做: - 被动检查(如 onShow)不应在检测到接受时主动修改用户在界面上的显式选择,避免悖论或意外更改;但当检测到权限被明确关闭时,应被动同步并关闭本地功能,防止应用继续假定有推送能力。 - 主动检查(如用户明确进入设置或手动刷新)则可以安全地将本地状态与系统设置对齐,并在必要时显示权限引导帮助用户恢复权限。 已在代码中完成该改动,且相关逻辑已保存到 `reminder.vue` 中的注释与实现里。