440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
// 订阅消息管理器 - 微信小程序专用
|
|
// 处理订阅消息的申请、管理和发送
|
|
|
|
/**
|
|
* 微信小程序订阅消息管理器
|
|
* 功能:
|
|
* 1. 管理订阅消息模板
|
|
* 2. 请求用户订阅权限
|
|
* 3. 检查订阅状态
|
|
* 4. 智能订阅策略
|
|
* 5. 订阅数据统计
|
|
*/
|
|
class SubscribeMessageManager {
|
|
constructor() {
|
|
this.isInitialized = false;
|
|
|
|
// 订阅消息模板配置
|
|
this.templates = {
|
|
// 新消息通知
|
|
newMessage: {
|
|
id: 'template_id_for_new_message', // 需要在微信公众平台配置
|
|
name: '新消息通知',
|
|
description: '当您收到新消息时通知您',
|
|
keywords: ['发送人', '消息内容', '发送时间'],
|
|
required: false,
|
|
category: 'message'
|
|
},
|
|
|
|
// 好友请求通知
|
|
friendRequest: {
|
|
id: 'template_id_for_friend_request',
|
|
name: '好友请求通知',
|
|
description: '当有人申请添加您为好友时通知您',
|
|
keywords: ['申请人', '申请时间', '验证消息'],
|
|
required: false,
|
|
category: 'social'
|
|
},
|
|
|
|
// 系统通知
|
|
systemNotice: {
|
|
id: 'template_id_for_system_notice',
|
|
name: '系统通知',
|
|
description: '重要的系统消息和公告',
|
|
keywords: ['通知类型', '通知内容', '通知时间'],
|
|
required: true,
|
|
category: 'system'
|
|
},
|
|
|
|
// 群聊消息
|
|
groupMessage: {
|
|
id: 'template_id_for_group_message',
|
|
name: '群聊消息通知',
|
|
description: '当群聊中有新消息时通知您',
|
|
keywords: ['群名称', '发送人', '消息内容'],
|
|
required: false,
|
|
category: 'message'
|
|
},
|
|
|
|
// 活动提醒
|
|
activityReminder: {
|
|
id: 'template_id_for_activity_reminder',
|
|
name: '活动提醒',
|
|
description: '重要活动和事件提醒',
|
|
keywords: ['活动名称', '活动时间', '活动地点'],
|
|
required: false,
|
|
category: 'reminder'
|
|
}
|
|
};
|
|
|
|
// 订阅状态缓存
|
|
this.subscriptionStatus = new Map();
|
|
|
|
// 订阅策略配置
|
|
this.subscriptionStrategy = {
|
|
// 自动请求订阅的场景
|
|
autoRequestScenes: [
|
|
'first_message_received',
|
|
'first_friend_request',
|
|
'important_system_notice'
|
|
],
|
|
|
|
// 批量请求的最大数量
|
|
maxBatchRequest: 3,
|
|
|
|
// 请求间隔(毫秒)
|
|
requestInterval: 24 * 60 * 60 * 1000, // 24小时
|
|
|
|
// 最大请求次数
|
|
maxRequestTimes: 3
|
|
};
|
|
|
|
this.init();
|
|
}
|
|
|
|
// 初始化订阅消息管理器
|
|
async init() {
|
|
if (this.isInitialized) return;
|
|
|
|
console.log('📝 初始化订阅消息管理器...');
|
|
|
|
try {
|
|
// 加载订阅状态
|
|
await this.loadSubscriptionStatus();
|
|
|
|
// 检查模板配置
|
|
this.validateTemplateConfig();
|
|
|
|
this.isInitialized = true;
|
|
console.log('✅ 订阅消息管理器初始化完成');
|
|
|
|
} catch (error) {
|
|
console.error('❌ 订阅消息管理器初始化失败:', error);
|
|
}
|
|
}
|
|
|
|
// 请求订阅消息权限
|
|
async requestSubscription(templateKeys, options = {}) {
|
|
try {
|
|
console.log('📝 请求订阅消息权限:', templateKeys);
|
|
|
|
// 验证模板键
|
|
const validTemplateKeys = this.validateTemplateKeys(templateKeys);
|
|
if (validTemplateKeys.length === 0) {
|
|
throw new Error('没有有效的模板键');
|
|
}
|
|
|
|
// 获取模板ID
|
|
const templateIds = validTemplateKeys.map(key => this.templates[key].id);
|
|
|
|
// 检查请求频率限制
|
|
if (!this.canRequestSubscription(templateKeys)) {
|
|
console.log('⏰ 请求频率受限,跳过订阅请求');
|
|
return { success: false, reason: 'rate_limited' };
|
|
}
|
|
|
|
// 发起订阅请求
|
|
const result = await this.makeSubscriptionRequest(templateIds, options);
|
|
|
|
// 更新订阅状态
|
|
await this.updateSubscriptionStatus(validTemplateKeys, result);
|
|
|
|
// 记录请求历史
|
|
this.recordSubscriptionRequest(validTemplateKeys);
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error('❌ 请求订阅消息权限失败:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// 发起订阅请求
|
|
async makeSubscriptionRequest(templateIds, options = {}) {
|
|
return new Promise((resolve) => {
|
|
wx.requestSubscribeMessage({
|
|
tmplIds: templateIds,
|
|
success: (res) => {
|
|
console.log('📝 订阅请求成功:', res);
|
|
resolve({
|
|
success: true,
|
|
result: res,
|
|
acceptedCount: this.countAcceptedSubscriptions(res)
|
|
});
|
|
},
|
|
fail: (error) => {
|
|
console.error('❌ 订阅请求失败:', error);
|
|
resolve({
|
|
success: false,
|
|
error: error.errMsg || '订阅请求失败'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 统计接受的订阅数量
|
|
countAcceptedSubscriptions(result) {
|
|
let count = 0;
|
|
for (const templateId in result) {
|
|
if (result[templateId] === 'accept') {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// 智能订阅策略
|
|
async smartSubscriptionRequest(scene, context = {}) {
|
|
try {
|
|
console.log('🧠 智能订阅策略:', scene);
|
|
|
|
// 检查是否需要自动请求
|
|
if (!this.subscriptionStrategy.autoRequestScenes.includes(scene)) {
|
|
console.log('📝 当前场景不需要自动请求订阅');
|
|
return;
|
|
}
|
|
|
|
// 根据场景选择合适的模板
|
|
const templateKeys = this.getTemplatesForScene(scene);
|
|
if (templateKeys.length === 0) {
|
|
console.log('📝 当前场景没有对应的模板');
|
|
return;
|
|
}
|
|
|
|
// 过滤已订阅的模板
|
|
const unsubscribedTemplates = templateKeys.filter(key =>
|
|
!this.isSubscribed(key)
|
|
);
|
|
|
|
if (unsubscribedTemplates.length === 0) {
|
|
console.log('📝 所有相关模板都已订阅');
|
|
return;
|
|
}
|
|
|
|
// 限制批量请求数量
|
|
const templatesToRequest = unsubscribedTemplates.slice(
|
|
0,
|
|
this.subscriptionStrategy.maxBatchRequest
|
|
);
|
|
|
|
// 显示友好的订阅引导
|
|
const shouldRequest = await this.showSubscriptionGuide(scene, templatesToRequest);
|
|
if (!shouldRequest) {
|
|
console.log('📝 用户取消订阅请求');
|
|
return;
|
|
}
|
|
|
|
// 发起订阅请求
|
|
return await this.requestSubscription(templatesToRequest, { scene });
|
|
|
|
} catch (error) {
|
|
console.error('❌ 智能订阅策略执行失败:', error);
|
|
}
|
|
}
|
|
|
|
// 根据场景获取模板
|
|
getTemplatesForScene(scene) {
|
|
switch (scene) {
|
|
case 'first_message_received':
|
|
return ['newMessage', 'groupMessage'];
|
|
case 'first_friend_request':
|
|
return ['friendRequest'];
|
|
case 'important_system_notice':
|
|
return ['systemNotice'];
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 显示订阅引导
|
|
async showSubscriptionGuide(scene, templateKeys) {
|
|
const templateNames = templateKeys.map(key => this.templates[key].name);
|
|
|
|
return new Promise((resolve) => {
|
|
wx.showModal({
|
|
title: '消息通知',
|
|
content: `为了及时通知您重要消息,建议开启以下通知:\n${templateNames.join('、')}`,
|
|
confirmText: '开启通知',
|
|
cancelText: '暂不开启',
|
|
success: (res) => {
|
|
resolve(res.confirm);
|
|
},
|
|
fail: () => {
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 检查是否可以请求订阅
|
|
canRequestSubscription(templateKeys) {
|
|
const now = Date.now();
|
|
|
|
for (const key of templateKeys) {
|
|
const lastRequest = this.getLastRequestTime(key);
|
|
const requestCount = this.getRequestCount(key);
|
|
|
|
// 检查请求间隔
|
|
if (lastRequest && (now - lastRequest) < this.subscriptionStrategy.requestInterval) {
|
|
return false;
|
|
}
|
|
|
|
// 检查请求次数
|
|
if (requestCount >= this.subscriptionStrategy.maxRequestTimes) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 验证模板键
|
|
validateTemplateKeys(templateKeys) {
|
|
const keys = Array.isArray(templateKeys) ? templateKeys : [templateKeys];
|
|
return keys.filter(key => this.templates[key]);
|
|
}
|
|
|
|
// 验证模板配置
|
|
validateTemplateConfig() {
|
|
for (const [key, template] of Object.entries(this.templates)) {
|
|
if (!template.id || template.id.startsWith('template_id_')) {
|
|
console.warn(`⚠️ 模板 ${key} 的ID未配置或为占位符`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查订阅状态
|
|
isSubscribed(templateKey) {
|
|
return this.subscriptionStatus.get(templateKey) === 'accept';
|
|
}
|
|
|
|
// 获取所有订阅状态
|
|
getAllSubscriptionStatus() {
|
|
const status = {};
|
|
for (const key in this.templates) {
|
|
status[key] = {
|
|
template: this.templates[key],
|
|
subscribed: this.isSubscribed(key),
|
|
lastRequest: this.getLastRequestTime(key),
|
|
requestCount: this.getRequestCount(key)
|
|
};
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// 更新订阅状态
|
|
async updateSubscriptionStatus(templateKeys, result) {
|
|
if (!result.success || !result.result) return;
|
|
|
|
for (const key of templateKeys) {
|
|
const templateId = this.templates[key].id;
|
|
const status = result.result[templateId];
|
|
if (status) {
|
|
this.subscriptionStatus.set(key, status);
|
|
}
|
|
}
|
|
|
|
await this.saveSubscriptionStatus();
|
|
}
|
|
|
|
// 记录请求历史
|
|
recordSubscriptionRequest(templateKeys) {
|
|
const now = Date.now();
|
|
const requestHistory = this.getRequestHistory();
|
|
|
|
for (const key of templateKeys) {
|
|
if (!requestHistory[key]) {
|
|
requestHistory[key] = { times: [], count: 0 };
|
|
}
|
|
requestHistory[key].times.push(now);
|
|
requestHistory[key].count++;
|
|
}
|
|
|
|
this.saveRequestHistory(requestHistory);
|
|
}
|
|
|
|
// 获取请求历史
|
|
getRequestHistory() {
|
|
try {
|
|
return wx.getStorageSync('subscriptionRequestHistory') || {};
|
|
} catch (error) {
|
|
console.error('❌ 获取请求历史失败:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// 保存请求历史
|
|
saveRequestHistory(history) {
|
|
try {
|
|
wx.setStorageSync('subscriptionRequestHistory', history);
|
|
} catch (error) {
|
|
console.error('❌ 保存请求历史失败:', error);
|
|
}
|
|
}
|
|
|
|
// 获取最后请求时间
|
|
getLastRequestTime(templateKey) {
|
|
const history = this.getRequestHistory();
|
|
const keyHistory = history[templateKey];
|
|
if (keyHistory && keyHistory.times.length > 0) {
|
|
return Math.max(...keyHistory.times);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 获取请求次数
|
|
getRequestCount(templateKey) {
|
|
const history = this.getRequestHistory();
|
|
return history[templateKey]?.count || 0;
|
|
}
|
|
|
|
// 加载订阅状态
|
|
async loadSubscriptionStatus() {
|
|
try {
|
|
const status = wx.getStorageSync('subscriptionStatus') || {};
|
|
this.subscriptionStatus = new Map(Object.entries(status));
|
|
} catch (error) {
|
|
console.error('❌ 加载订阅状态失败:', error);
|
|
}
|
|
}
|
|
|
|
// 保存订阅状态
|
|
async saveSubscriptionStatus() {
|
|
try {
|
|
const status = Object.fromEntries(this.subscriptionStatus);
|
|
wx.setStorageSync('subscriptionStatus', status);
|
|
} catch (error) {
|
|
console.error('❌ 保存订阅状态失败:', error);
|
|
}
|
|
}
|
|
|
|
// 获取管理器状态
|
|
getStatus() {
|
|
return {
|
|
isInitialized: this.isInitialized,
|
|
templates: this.templates,
|
|
subscriptionStatus: Object.fromEntries(this.subscriptionStatus),
|
|
strategy: this.subscriptionStrategy
|
|
};
|
|
}
|
|
|
|
// 重置订阅状态
|
|
reset() {
|
|
this.subscriptionStatus.clear();
|
|
this.saveSubscriptionStatus();
|
|
|
|
// 清除请求历史
|
|
try {
|
|
wx.removeStorageSync('subscriptionRequestHistory');
|
|
} catch (error) {
|
|
console.error('❌ 清除请求历史失败:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建全局实例
|
|
const subscribeMessageManager = new SubscribeMessageManager();
|
|
|
|
module.exports = subscribeMessageManager;
|