/** * 微信订阅消息工具 * 统一管理订阅消息的请求和状态 * * 服务端记录的是授权登记,不代表微信侧仍有剩余次数。 */ const { api } = require('./request'); // 操作和打卡共用同一模板、同一份一次性授权。 const SUBSCRIBE_TEMPLATES = { notify: 'K_iQMwE_jre0j6QDVL78DZz7wiR1SKVY5h2YJCK6uPU', }; // 只合并正在进行的授权请求,后续用户点击仍可补充一次性授权。 let _pendingSubscribe = null; /** * 请求用户订阅指定模板 * @param {string|string[]} templateTypes - 模板类型或模板类型数组 * @returns {Promise} 订阅结果 { templateType: true/false } */ function requestSubscribe(templateTypes, options = {}) { if (_pendingSubscribe) return _pendingSubscribe; const pending = new Promise((resolve, reject) => { if (!Array.isArray(templateTypes)) { templateTypes = [templateTypes]; } // 获取模板ID列表 const tmplIds = templateTypes .map(type => SUBSCRIBE_TEMPLATES[type]) .filter(id => id && id !== 'TEMPLATE_ID_'); if (tmplIds.length === 0) { console.warn('[subscribe] 模板ID未配置,请在微信小程序管理后台申请模板后更新'); resolve({}); return; } wx.requestSubscribeMessage({ tmplIds: tmplIds, success: (res) => { // 处理每个模板的授权结果 const results = {}; templateTypes.forEach((type) => { const tmplId = SUBSCRIBE_TEMPLATES[type]; const status = res[tmplId]; results[type] = status === 'accept'; }); // 将授权结果同步到后端 syncSubscribeResults(results, options).then(() => resolve(results), reject); }, fail: (err) => { console.error('[subscribe] 请求订阅失败:', err); reject(err); } }); }); _pendingSubscribe = pending.finally(() => { _pendingSubscribe = null; prepareLoginRenewal(); }); return _pendingSubscribe; } /** * 将订阅结果同步到后端 */ function syncSubscribeResults(results, options) { return Promise.all(Object.keys(results).filter(type => results[type]).map(type => { return api.subscribeTemplate({template_type:type, template_id:SUBSCRIBE_TEMPLATES[type]}, options).then(result => { if (!result || !result.success) throw new Error('订阅授权保存失败'); wx.setStorageSync('subscribed_' + type, true); }); })); } /** * 获取用户当前的订阅状态 * 从后端获取 * @param {boolean} forceRefresh - 保留旧调用参数 * @returns {Promise} 订阅状态 { template_type: true/false } */ async function getSubscribeStatus(forceRefresh = false) { try { const res = await api.mySubscribeTemplates(); const status = {}; Object.keys(SUBSCRIBE_TEMPLATES).forEach(type => { status[type] = !!SUBSCRIBE_TEMPLATES[type] && res.templates[type] === SUBSCRIBE_TEMPLATES[type]; }); return status; } catch (e) { return {}; } } module.exports = { requestSubscribe, resetLoginRenewal, prepareLoginRenewal, tryLoginRenewal, getSubscribeStatus, isTemplateConfigured: type => !!SUBSCRIBE_TEMPLATES[type], subscribeErrorMessage }; function subscribeErrorMessage(error) { const code = Number(error && error.errCode); if (code === 20001) return '微信未找到该提醒模板,请核对当前小程序的模板配置'; if (code === 20004) return '微信消息通知总开关已关闭,请在小程序设置中开启'; if (code === 10002 || code === 10003) return '网络异常,暂时无法订阅,请稍后重试'; return error && (error.error || error.message) || '订阅失败,请稍后重试'; } // Reading settings at login never asks for authorization. Only a later real tap // may renew, and only when WeChat reports a remembered acceptance. let renewal = {generation:0,userId:'',allowed:false,attempted:false}; function resetLoginRenewal() { renewal = {generation:renewal.generation+1,userId:'',allowed:false,attempted:false}; } function prepareLoginRenewal() { renewal.allowed = false; if (typeof wx.getSetting !== 'function' || typeof wx.getStorageSync !== 'function') return; const userId = String(wx.getStorageSync('userId') || ''); if (!userId) return; if (renewal.userId && renewal.userId !== userId) resetLoginRenewal(); renewal.userId = userId; const generation = ++renewal.generation; try { wx.getSetting({withSubscriptions:true,success(result){ if (renewal.generation !== generation || String(wx.getStorageSync('userId') || '') !== userId) return; const settings = result.subscriptionsSetting || {}; renewal.allowed = settings.mainSwitch === true && !!settings.itemSettings && settings.itemSettings[SUBSCRIBE_TEMPLATES.notify] === 'accept'; },fail(){ /* Unknown permission means no automatic request. */ }}); } catch(error) { /* Unsupported API: keep the manual entry. */ } } function tryLoginRenewal() { const app = typeof getApp === 'function' ? getApp() : null; if (!app || !app.globalData || app.globalData.authState !== 'ready' || !renewal.allowed || renewal.attempted) return; if (!wx.getStorageSync('coupleId') || wx.getStorageSync('subscribed_notify') === false || String(wx.getStorageSync('userId') || '') !== renewal.userId) return; renewal.attempted = true; // Intentionally no await before requesting: preserve this tap's user gesture. return requestSubscribe('notify', {silent:true}).catch(error => { console.warn('[subscribe] 本次静默续订未完成,保留手动订阅入口:',error); return {}; }); }