前端

uni-app通知功能实现:权限设置与推送集成指南

TRAE AI 编程助手

本文深入解析uni-app通知功能的完整实现流程,涵盖权限管理、推送集成、最佳实践等核心技术要点,帮助开发者快速构建稳定可靠的消息推送系统。

通知功能核心概念

通知类型与架构

uni-app通知系统主要包含三种类型:

  • 本地通知:应用本地触发的即时通知
  • 推送通知:服务端推送的远程消息
  • 系统通知:系统级别的状态栏通知
graph TD A[uni-app通知系统] --> B[本地通知] A --> C[推送通知] A --> D[系统通知] B --> B1[定时提醒] B --> B2[事件触发] C --> C1[UniPush] C --> C2[个推] C --> C3[极光推送] D --> D1[状态栏消息] D --> D2[横幅通知]

权限体系解析

不同平台的通知权限管理存在差异:

平台权限申请时机用户可控性系统限制
iOS首次启动时严格
Android运行时申请版本差异
微信小程序订阅机制平台限制

权限设置实现详解

1. 基础权限配置

manifest.json中配置推送权限:

{
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "android.permission.RECEIVE_BOOT_COMPLETED",
          "android.permission.VIBRATE",
          "android.permission.WAKE_LOCK",
          "android.permission.ACCESS_NETWORK_STATE",
          "android.permission.INTERNET"
        ]
      }
    }
  }
}

2. 运行时权限申请

// 权限管理工具类
class NotificationPermission {
  static async requestPermission() {
    // #ifdef APP-PLUS
    const platform = uni.getSystemInfoSync().platform;
    
    if (platform === 'android') {
      return await this.requestAndroidPermission();
    } else if (platform === 'ios') {
      return await this.requestIOSPermission();
    }
    // #endif
    
    // #ifdef MP-WEIXIN
    return await this.requestWechatPermission();
    // #endif
  }
  
  static async requestAndroidPermission() {
    const permissions = [
      'android.permission.POST_NOTIFICATIONS',
      'android.permission.ACCESS_NETWORK_STATE'
    ];
    
    for (const permission of permissions) {
      const result = await new Promise((resolve) => {
        plus.android.requestPermissions([permission], (e) => {
          resolve(e.granted);
        });
      });
      
      if (!result) {
        console.warn(`权限申请失败: ${permission}`);
        return false;
      }
    }
    
    return true;
  }
  
  static async requestIOSPermission() {
    return new Promise((resolve) => {
      plus.ios.import('UIUserNotificationSettings');
      const settings = plus.ios.newObject('UIUserNotificationSettings');
      
      plus.push.getClientInfo((info) => {
        resolve(info && info.token);
      });
    });
  }
}

3. 权限状态检测

// 权限状态监控
async function checkNotificationStatus() {
  // #ifdef APP-PLUS
  const platform = uni.getSystemInfoSync().platform;
  
  if (platform === 'android') {
    const main = plus.android.runtimeMainActivity();
    const Context = plus.android.importClass('android.content.Context');
    const NotificationManager = plus.android.importClass('android.app.NotificationManager');
    
    const nm = main.getSystemService(Context.NOTIFICATION_SERVICE);
    const areEnabled = nm.areNotificationsEnabled();
    
    return areEnabled;
  }
  // #endif
  
  return true; // 默认允许
}

推送集成深度实践

1. UniPush集成方案

步骤一:服务端配置

// 服务端推送接口实现
const uniPush = require('unipush-nodejs');
 
class PushService {
  constructor() {
    this.client = new uniPush({
      appId: process.env.UNI_APP_ID,
      appKey: process.env.UNI_APP_KEY,
      appSecret: process.env.UNI_APP_SECRET,
      masterSecret: process.env.UNI_MASTER_SECRET
    });
  }
  
  async sendNotification(options) {
    const payload = {
      platform: 'all',
      audience: {
        registration_id: [options.clientId]
      },
      notification: {
        title: options.title,
        body: options.content,
        android: {
          extras: { ...options.data },
          channel_id: 'default_channel'
        },
        ios: {
          extras: { ...options.data },
          sound: 'default'
        }
      },
      options: {
        time_to_live: 86400, // 24小时
        apns_production: process.env.NODE_ENV === 'production'
      }
    };
    
    try {
      const result = await this.client.push(payload);
      return { success: true, messageId: result.msg_id };
    } catch (error) {
      console.error('推送失败:', error);
      return { success: false, error: error.message };
    }
  }
}

步骤二:客户端集成

// 推送服务管理器
class PushNotificationManager {
  constructor() {
    this.clientId = null;
    this.isInitialized = false;
  }
  
  async initialize() {
    if (this.isInitialized) return;
    
    // #ifdef APP-PLUS
    await this.setupAppPush();
    // #endif
    
    // #ifdef MP-WEIXIN
    await this.setupWechatPush();
    // #endif
    
    this.isInitialized = true;
  }
  
  async setupAppPush() {
    // 获取客户端推送标识
    const clientInfo = await new Promise((resolve) => {
      plus.push.getClientInfo((info) => {
        resolve(info);
      });
    });
    
    this.clientId = clientInfo ? clientInfo.clientid : null;
    
    // 监听推送消息
    plus.push.addEventListener('receive', (msg) => {
      this.handlePushMessage(msg);
    });
    
    // 监听点击事件
    plus.push.addEventListener('click', (msg) => {
      this.handlePushClick(msg);
    });
    
    console.log('推送服务初始化完成,客户端ID:', this.clientId);
  }
  
  handlePushMessage(message) {
    const payload = this.parseMessage(message);
    
    // 本地通知展示
    uni.createPushMessage({
      title: payload.title,
      content: payload.content,
      payload: payload.data,
      success: () => {
        console.log('本地通知创建成功');
      },
      fail: (error) => {
        console.error('本地通知创建失败:', error);
      }
    });
  }
  
  parseMessage(message) {
    let payload;
    
    try {
      // Android平台
      if (message.payload) {
        payload = JSON.parse(message.payload);
      } else {
        // iOS平台
        payload = message.aps ? message.aps.alert : message;
      }
    } catch (e) {
      payload = {
        title: '新消息',
        content: message.content || '您有一条新消息'
      };
    }
    
    return payload;
  }
  
  handlePushClick(message) {
    const payload = this.parseMessage(message);
    
    // 路由跳转逻辑
    if (payload.data && payload.data.page) {
      uni.navigateTo({
        url: payload.data.page,
        success: () => {
          console.log('页面跳转成功:', payload.data.page);
        }
      });
    }
  }
}
 
// 使用示例
const pushManager = new PushNotificationManager();
export default pushManager;

2. 第三方推送集成

个推集成示例:

// 个推推送配置
class GeTuiPushService {
  constructor() {
    this.config = {
      appId: process.env.GETUI_APP_ID,
      appKey: process.env.GETUI_APP_KEY,
      masterSecret: process.env.GETUI_MASTER_SECRET,
      host: 'https://restapi.getui.com'
    };
  }
  
  async pushToSingle(clientId, notification) {
    const authToken = await this.getAuthToken();
    
    const payload = {
      request_id: this.generateRequestId(),
      audience: {
        cid: [clientId]
      },
      push_message: {
        notification: {
          title: notification.title,
          body: notification.content,
          click_type: 'intent',
          intent: 'intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10000000;component=com.example.app/.MainActivity;end',
          notify_id: Date.now()
        }
      }
    };
    
    const response = await fetch(`${this.config.host}/v2/${this.config.appId}/push_single`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'token': authToken
      },
      body: JSON.stringify(payload)
    });
    
    return await response.json();
  }
  
  async getAuthToken() {
    const payload = {
      sign: this.generateSign(),
      timestamp: Date.now().toString(),
      appkey: this.config.appKey
    };
    
    const response = await fetch(`${this.config.host}/v1/${this.config.appId}/auth`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
    
    const result = await response.json();
    return result.data.token;
  }
  
  generateSign() {
    const timestamp = Date.now().toString();
    return crypto.createHash('sha256')
      .update(this.config.appKey + timestamp + this.config.masterSecret)
      .digest('hex');
  }
}

高级功能实现

1. 通知分类与渠道管理

// Android通知渠道管理
class NotificationChannelManager {
  static createChannel(channelId, channelName, importance = 'high') {
    // #ifdef APP-PLUS && ANDROID
    const main = plus.android.runtimeMainActivity();
    const Context = plus.android.importClass('android.content.Context');
    const NotificationManager = plus.android.importClass('android.app.NotificationManager');
    const NotificationChannel = plus.android.importClass('android.app.NotificationChannel');
    
    const nm = main.getSystemService(Context.NOTIFICATION_SERVICE);
    const channel = new NotificationChannel(
      channelId,
      channelName,
      NotificationManager.IMPORTANCE_HIGH
    );
    
    channel.setDescription(`${channelName}通知渠道`);
    channel.enableLights(true);
    channel.setLightColor(0xFF0000);
    channel.enableVibration(true);
    
    nm.createNotificationChannel(channel);
    // #endif
  }
  
  static createDefaultChannels() {
    const channels = [
      { id: 'chat', name: '聊天消息', importance: 'high' },
      { id: 'system', name: '系统通知', importance: 'default' },
      { id: 'marketing', name: '营销消息', importance: 'low' }
    ];
    
    channels.forEach(channel => {
      this.createChannel(channel.id, channel.name, channel.importance);
    });
  }
}

2. 通知统计与分析

// 通知数据统计
class NotificationAnalytics {
  constructor() {
    this.db = uni.getStorageSync('notification_stats') || {};
  }
  
  recordEvent(eventType, notificationId, extra = {}) {
    const timestamp = Date.now();
    const date = new Date(timestamp).toISOString().split('T')[0];
    
    if (!this.db[date]) {
      this.db[date] = {
        sent: 0,
        received: 0,
        clicked: 0,
        dismissed: 0
      };
    }
    
    this.db[date][eventType]++;
    
    // 记录详细事件
    if (!this.db.events) this.db.events = [];
    this.db.events.push({
      type: eventType,
      notificationId,
      timestamp,
      ...extra
    });
    
    // 清理过期数据
    this.cleanupOldData();
    
    // 持久化存储
    uni.setStorageSync('notification_stats', this.db);
  }
  
  getStats(days = 7) {
    const stats = [];
    const today = new Date();
    
    for (let i = 0; i < days; i++) {
      const date = new Date(today);
      date.setDate(date.getDate() - i);
      const dateStr = date.toISOString().split('T')[0];
      
      const dayStats = this.db[dateStr] || {
        sent: 0,
        received: 0,
        clicked: 0,
        dismissed: 0
      };
      
      stats.push({
        date: dateStr,
        ...dayStats,
        clickRate: dayStats.sent > 0 ? 
          (dayStats.clicked / dayStats.sent * 100).toFixed(2) : 0
      });
    }
    
    return stats.reverse();
  }
  
  cleanupOldData() {
    const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
    
    if (this.db.events) {
      this.db.events = this.db.events.filter(
        event => event.timestamp > thirtyDaysAgo
      );
    }
    
    // 清理旧日期数据
    Object.keys(this.db).forEach(date => {
      if (date !== 'events') {
        const dateTimestamp = new Date(date).getTime();
        if (dateTimestamp < thirtyDaysAgo) {
          delete this.db[date];
        }
      }
    });
  }
}

最佳实践与性能优化

1. 错误处理与重试机制

// 推送重试策略
class PushRetryStrategy {
  constructor(maxRetries = 3, baseDelay = 1000) {
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
  }
  
  async executeWithRetry(operation, context) {
    let lastError;
    
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const result = await operation();
        
        // 记录成功指标
        this.recordSuccess(context, attempt);
        return result;
        
      } catch (error) {
        lastError = error;
        
        // 判断是否需要重试
        if (!this.shouldRetry(error) || attempt === this.maxRetries) {
          throw error;
        }
        
        // 计算延迟时间(指数退避)
        const delay = this.baseDelay * Math.pow(2, attempt - 1);
        
        console.warn(`推送失败,${delay}ms后重试 (第${attempt}次):`, error.message);
        
        await this.sleep(delay);
      }
    }
    
    throw lastError;
  }
  
  shouldRetry(error) {
    // 网络错误、超时等可重试
    const retryableErrors = [
      'NETWORK_ERROR',
      'TIMEOUT',
      'SERVER_ERROR',
      'RATE_LIMIT'
    ];
    
    return retryableErrors.some(type => error.message.includes(type));
  }
  
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  
  recordSuccess(context, attempts) {
    console.log(`推送成功: ${context}, 尝试次数: ${attempts}`);
  }
}

2. 电量与性能优化

// 智能推送策略
class SmartPushStrategy {
  constructor() {
    this.batteryLevel = 100;
    this.networkType = 'wifi';
    this.lastPushTime = 0;
  }
  
  async initialize() {
    // 监听电量变化
    // #ifdef APP-PLUS
    plus.device.getInfo({
      success: (info) => {
        this.batteryLevel = info.battery || 100;
      }
    });
    // #endif
    
    // 监听网络状态
    uni.onNetworkStatusChange((res) => {
      this.networkType = res.networkType;
    });
  }
  
  shouldSendPush(notification) {
    // 低电量时减少推送频率
    if (this.batteryLevel < 20 && !notification.priority) {
      return false;
    }
    
    // 移动网络下限制大内容推送
    if (this.networkType === 'cellular' && this.isLargeContent(notification)) {
      return false;
    }
    
    // 防打扰时段检查
    if (this.isQuietHours()) {
      return notification.priority === 'high';
    }
    
    return true;
  }
  
  isLargeContent(notification) {
    const contentSize = JSON.stringify(notification).length;
    return contentSize > 1024; // 1KB
  }
  
  isQuietHours() {
    const hour = new Date().getHours();
    return hour >= 23 || hour <= 7;
  }
  
  getOptimalPushTime() {
    const now = Date.now();
    const timeSinceLastPush = now - this.lastPushTime;
    
    // 最小推送间隔
    const minInterval = this.batteryLevel < 30 ? 300000 : 60000; // 5分钟或1分钟
    
    if (timeSinceLastPush < minInterval) {
      return this.lastPushTime + minInterval;
    }
    
    return now;
  }
}

TRAE IDE 开发效率提升

在使用 TRAE IDE 开发uni-app通知功能时,可以显著提升开发效率:

智能代码补全

TRAE IDE 的智能代码补全功能可以快速生成推送相关的模板代码:

// 输入 "pushnotification" 触发智能补全
// TRAE IDE 会自动生成完整的推送处理模板
class PushNotificationHandler {
  constructor() {
    this.initializePushService();
    this.setupEventListeners();
  }
  
  // AI 智能生成的初始化方法
  async initializePushService() {
    // 权限检查
    const hasPermission = await this.checkPermission();
    if (!hasPermission) {
      await this.requestPermission();
    }
    
    // 服务注册
    await this.registerPushService();
  }
}

实时错误检测

TRAE IDE 的实时错误检测可以在编码阶段发现潜在的推送配置问题:

// 错误示例:TRAE IDE 会立即提示
plus.push.getClientInfo((info) => {
  // TRAE IDE 提示:未处理 info 为 null 的情况
  console.log(info.token); // ⚠️ 潜在的空指针异常
});
 
// 修正后的代码
plus.push.getClientInfo((info) => {
  if (info && info.token) {
    console.log('客户端token:', info.token);
  } else {
    console.warn('获取客户端信息失败');
  }
});

调试工具集成

TRAE IDE 内置的调试工具可以直接查看推送消息的详细内容:

// 在TRAE IDE中,可以直接在调试控制台查看推送数据
handlePushMessage(message) {
  // 使用 TRAE IDE 的调试工具查看 message 结构
  console.log('推送消息详情:', JSON.stringify(message, null, 2));
  
  // TRAE IDE 支持断点调试,可以逐步分析消息处理流程
  debugger;
  
  const payload = this.parseMessage(message);
  this.displayNotification(payload);
}

完整项目集成示例

1. 项目结构规划

src/
├── services/
│   ├── push/
│   │   ├── index.js          # 推送管理器
│   │   ├── permission.js     # 权限管理
│   │   ├── channels.js       # 通知渠道
│   │   └── analytics.js      # 统计分析
│   └── api/
│       └── push.js           # 推送API
├── utils/
│   ├── platform.js           # 平台工具
│   └── constants.js          # 常量定义
└── pages/
    └── settings/
        └── notification.vue  # 通知设置页面

2. 主入口集成

// main.js
import PushManager from '@/services/push/index.js';
 
// 应用启动时初始化推送服务
async function initializeApp() {
  // 初始化推送管理器
  const pushManager = new PushManager();
  
  try {
    await pushManager.initialize();
    console.log('推送服务初始化成功');
  } catch (error) {
    console.error('推送服务初始化失败:', error);
  }
  
  // 其他初始化逻辑...
}
 
// 使用 TRAE IDE 的智能提示快速完成初始化
initializeApp();

3. 用户设置界面

<!-- pages/settings/notification.vue -->
<template>
  <view class="notification-settings">
    <view class="section">
      <text class="title">通知设置</text>
      
      <view class="setting-item">
        <text>接收推送通知</text>
        <switch 
          :checked="settings.enabled"
          @change="toggleNotification"
        />
      </view>
      
      <view class="setting-item" v-if="settings.enabled">
        <text>声音提醒</text>
        <switch 
          :checked="settings.sound"
          @change="updateSetting('sound', $event.detail.value)"
        />
      </view>
      
      <view class="setting-item" v-if="settings.enabled">
        <text>振动提醒</text>
        <switch 
          :checked="settings.vibration"
          @change="updateSetting('vibration', $event.detail.value)"
        />
      </view>
    </view>
    
    <view class="section" v-if="channelSupported">
      <text class="title">通知类别</text>
      
      <view 
        class="channel-item" 
        v-for="channel in channels" 
        :key="channel.id"
      >
        <view class="channel-info">
          <text class="channel-name">{{ channel.name }}</text>
          <text class="channel-desc">{{ channel.description }}</text>
        </view>
        <switch 
          :checked="channel.enabled"
          @change="toggleChannel(channel.id, $event.detail.value)"
        />
      </view>
    </view>
  </view>
</template>
 
<script>
import { mapState, mapActions } from 'vuex';
import NotificationService from '@/services/push/permission.js';
 
export default {
  data() {
    return {
      settings: {
        enabled: true,
        sound: true,
        vibration: true
      },
      channels: [],
      channelSupported: false
    };
  },
  
  async onLoad() {
    await this.loadSettings();
    
    // 检查是否支持通知渠道(Android 8.0+)
    this.channelSupported = await this.checkChannelSupport();
    if (this.channelSupported) {
      await this.loadChannels();
    }
  },
  
  methods: {
    async loadSettings() {
      try {
        const settings = await NotificationService.getSettings();
        this.settings = { ...this.settings, ...settings };
      } catch (error) {
        console.error('加载设置失败:', error);
      }
    },
    
    async toggleNotification(e) {
      const enabled = e.detail.value;
      
      try {
        if (enabled) {
          // 申请权限
          const granted = await NotificationService.requestPermission();
          if (!granted) {
            uni.showToast({
              title: '需要通知权限才能开启',
              icon: 'none'
            });
            this.settings.enabled = false;
            return;
          }
        }
        
        await NotificationService.updateSettings({ enabled });
        this.settings.enabled = enabled;
        
        uni.showToast({
          title: enabled ? '通知已开启' : '通知已关闭',
          icon: 'success'
        });
        
      } catch (error) {
        console.error('更新通知设置失败:', error);
        uni.showToast({
          title: '设置失败,请重试',
          icon: 'none'
        });
      }
    },
    
    async updateSetting(key, value) {
      try {
        await NotificationService.updateSettings({ [key]: value });
        this.settings[key] = value;
      } catch (error) {
        console.error('更新设置失败:', error);
      }
    }
  }
};
</script>

测试与验证

1. 单元测试

// tests/push/permission.test.js
import { describe, it, expect, jest } from '@jest/globals';
import NotificationPermission from '@/services/push/permission.js';
 
describe('NotificationPermission', () => {
  it('应该正确申请Android权限', async () => {
    // 模拟plus.android API
    global.plus = {
      android: {
        requestPermissions: jest.fn((permissions, callback) => {
          callback({ granted: true });
        })
      }
    };
    
    const result = await NotificationPermission.requestAndroidPermission();
    expect(result).toBe(true);
  });
  
  it('应该处理权限被拒绝的情况', async () => {
    global.plus = {
      android: {
        requestPermissions: jest.fn((permissions, callback) => {
          callback({ granted: false });
        })
      }
    };
    
    const result = await NotificationPermission.requestAndroidPermission();
    expect(result).toBe(false);
  });
});

2. 集成测试

// tests/push/integration.test.js
describe('推送集成测试', () => {
  it('应该完成完整的推送流程', async () => {
    const pushManager = new PushManager();
    
    // 初始化
    await pushManager.initialize();
    
    // 模拟推送消息
    const mockMessage = {
      title: '测试消息',
      content: '这是一条测试推送',
      payload: { test: true }
    };
    
    // 处理推送
    const result = await pushManager.handlePushMessage(mockMessage);
    
    expect(result.success).toBe(true);
  });
});

常见问题与解决方案

1. 权限申请失败

问题:Android 13+ 设备上通知权限申请失败

解决方案

// Android 13+ 特殊处理
async function handleAndroid13Permission() {
  const sdkVersion = plus.android.getSystemInfo().sdkVersion;
  
  if (sdkVersion >= 33) {
    // Android 13+ 需要特殊处理
    const result = await new Promise((resolve) => {
      plus.android.requestPermissions([
        'android.permission.POST_NOTIFICATIONS'
      ], (e) => {
        if (e.deniedAlways) {
          // 用户选择了"不再询问"
          uni.showModal({
            title: '需要通知权限',
            content: '请在设置中手动开启通知权限',
            success: (res) => {
              if (res.confirm) {
                // 跳转到应用设置页面
                plus.android.runtimeMainActivity().startActivity(
                  plus.android.newObject('android.content.Intent', 
                    plus.android.importClass('android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS'),
                    plus.android.newObject('android.net.Uri', `package:${plus.runtime.appid}`)
                  )
                );
              }
            }
          });
        }
        resolve(e.granted);
      });
    });
    
    return result;
  }
  
  return true;
}

2. 推送到达率低

优化策略

  1. 厂商通道集成:集成华为、小米、OPPO等厂商推送
  2. 智能心跳机制:保持长连接活跃
  3. 消息去重:避免重复推送相同内容
  4. 分群推送:精准定位目标用户
// 厂商通道适配器
class VendorPushAdapter {
  static getInstance() {
    const brand = uni.getSystemInfoSync().brand;
    
    switch (brand.toLowerCase()) {
      case 'huawei':
        return new HuaweiPushAdapter();
      case 'xiaomi':
        return new XiaomiPushAdapter();
      case 'oppo':
        return new OppoPushAdapter();
      default:
        return new DefaultPushAdapter();
    }
  }
}

3. 通知点击无响应

问题排查

// 调试通知点击事件
plus.push.addEventListener('click', (msg) => {
  console.log('通知点击事件触发:', JSON.stringify(msg));
  
  try {
    // 检查payload格式
    const payload = JSON.parse(msg.payload);
    console.log('解析后的payload:', payload);
    
    // 验证页面路由
    if (payload.page) {
      console.log('目标页面:', payload.page);
      
      // 尝试跳转
      uni.navigateTo({
        url: payload.page,
        success: () => {
          console.log('页面跳转成功');
        },
        fail: (error) => {
          console.error('页面跳转失败:', error);
          
          // 降级处理
          uni.switchTab({
            url: '/pages/index/index'
          });
        }
      });
    }
  } catch (error) {
    console.error('通知处理失败:', error);
  }
});

06|实战案例:电商应用推送系统

6.1 业务场景分析

假设我们正在开发一个电商应用,需要实现以下推送场景:

  • 订单状态更新通知(高优先级)
  • 营销活动推送(中优先级,需用户订阅)
  • 物流信息提醒(高优先级)
  • 优惠券到期提醒(中优先级)
  • 系统公告(低优先级)

6.2 完整实现

// ecommercePushService.js - 电商推送服务完整实现
class EcommercePushService {
  constructor() {
    // 用户偏好设置 - 支持细粒度的推送控制
    this.userPreferences = this.loadUserPreferences();
    
    // 推送配置
    this.config = {
      baseUrl: process.env.BASE_URL || 'https://api.example.com',
      timeout: 10000,
      retryAttempts: 3
    };
    
    // 初始化推送服务
    this.initialize();
  }
  
  /**
   * 初始化推送服务
   */
  async initialize() {
    try {
      // 初始化基础推送服务
      await pushService.initialize();
      
      // 检查并申请通知权限
      const hasPermission = await PermissionStrategy.smartRequestPermission('order');
      
      if (!hasPermission) {
        console.warn('⚠️ 用户未授予通知权限,部分功能可能受限');
      }
      
      console.log('✅ 电商推送服务初始化完成');
      
    } catch (error) {
      console.error('❌ 电商推送服务初始化失败:', error);
    }
  }
  
  /**
   * 订单状态推送 - 高优先级业务通知
   */
  async sendOrderUpdate(userId, orderInfo) {
    // 检查用户偏好
    if (!this.userPreferences.order) {
      console.log('用户关闭了订单推送');
      return { success: false, reason: 'user_disabled' };
    }
    
    // 构建订单状态消息
    const message = {
      type: 'order',
      priority: 'high',
      title: '订单状态更新',
      content: `您的订单${orderInfo.orderNo}已${this.getOrderStatusText(orderInfo.status)}`,
      payload: {
        action: 'order_detail',
        orderId: orderInfo.orderId,
        orderNo: orderInfo.orderNo,
        status: orderInfo.status,
        statusText: this.getOrderStatusText(orderInfo.status),
        timestamp: Date.now()
      },
      timestamp: Date.now(),
      badge: 1 // 角标数量
    };
    
    // 根据不同状态设置不同的声音和震动
    if (['shipped', 'delivered'].includes(orderInfo.status)) {
      message.sound = 'order_update.wav';
      message.vibrate = true;
    }
    
    return this.sendPush(userId, message);
  }
  
  /**
   * 营销活动推送 - 需用户订阅的营销通知
   */
  async sendMarketingCampaign(userId, campaign) {
    // 检查用户偏好和订阅状态
    if (!this.userPreferences.marketing) {
      console.log('用户关闭了营销推送');
      return { success: false, reason: 'marketing_disabled' };
    }
    
    // 检查活动有效性
    if (!this.isCampaignValid(campaign)) {
      console.log('营销活动无效或已过期');
      return { success: false, reason: 'invalid_campaign' };
    }
    
    const message = {
      type: 'marketing',
      priority: 'medium',
      title: campaign.title,
      content: campaign.content,
      payload: {
        action: 'campaign_detail',
        campaignId: campaign.id,
        campaignType: campaign.type,
        url: campaign.url,
        image: campaign.image,
        startTime: campaign.startTime,
        endTime: campaign.endTime,
        timestamp: Date.now()
      },
      timestamp: Date.now(),
      // 营销消息使用较温和的提醒方式
      sound: 'marketing.wav',
      vibrate: false
    };
    
    return this.sendPush(userId, message);
  }
  
  /**
   * 物流信息提醒 - 高优先级物流通知
   */
  async sendLogisticsUpdate(userId, logisticsInfo) {
    if (!this.userPreferences.logistics) {
      console.log('用户关闭了物流推送');
      return { success: false, reason: 'logistics_disabled' };
    }
    
    const message = {
      type: 'logistics',
      priority: 'high',
      title: '物流信息更新',
      content: `您的包裹${logisticsInfo.trackingNo}已${logisticsInfo.status}`,
      payload: {
        action: 'logistics_detail',
        orderId: logisticsInfo.orderId,
        trackingNo: logisticsInfo.trackingNo,
        company: logisticsInfo.company,
        status: logisticsInfo.status,
        location: logisticsInfo.location,
        timestamp: Date.now()
      },
      timestamp: Date.now(),
      // 物流更新使用明显的提醒
      sound: 'logistics_update.wav',
      vibrate: true,
      badge: 1
    };
    
    return this.sendPush(userId, message);
  }
  
  /**
   * 优惠券到期提醒 - 提前提醒用户
   */
  async sendCouponExpiryReminder(userId, couponInfo) {
    if (!this.userPreferences.coupon) {
      console.log('用户关闭了优惠券推送');
      return { success: false, reason: 'coupon_disabled' };
    }
    
    // 检查是否需要提醒(提前3天)
    const daysUntilExpiry = this.getDaysUntilExpiry(couponInfo.expiryDate);
    if (daysUntilExpiry > 3) {
      console.log('优惠券到期时间还早,暂不提醒');
      return { success: false, reason: 'too_early' };
    }
    
    const message = {
      type: 'coupon',
      priority: 'medium',
      title: '优惠券即将到期',
      content: `您的${couponInfo.title}优惠券将在${daysUntilExpiry}天后过期,快来使用吧!`,
      payload: {
        action: 'coupon_list',
        couponId: couponInfo.id,
        title: couponInfo.title,
        discount: couponInfo.discount,
        minAmount: couponInfo.minAmount,
        expiryDate: couponInfo.expiryDate,
        daysUntilExpiry: daysUntilExpiry,
        timestamp: Date.now()
      },
      timestamp: Date.now(),
      sound: 'coupon_reminder.wav',
      vibrate: false
    };
    
    return this.sendPush(userId, message);
  }
  
  /**
   * 系统公告推送 - 低优先级系统通知
   */
  async sendSystemAnnouncement(userId, announcement) {
    const message = {
      type: 'system',
      priority: 'low',
      title: announcement.title,
      content: announcement.content,
      payload: {
        action: 'announcement_detail',
        announcementId: announcement.id,
        type: announcement.type,
        url: announcement.url,
        timestamp: Date.now()
      },
      timestamp: Date.now(),
      // 系统公告使用温和的提醒
      sound: 'system.wav',
      vibrate: false
    };
    
    return this.sendPush(userId, message);
  }
  
  /**
   * 统一推送发送方法 - 处理通用逻辑
   */
  async sendPush(userId, message) {
    try {
      // 检查推送服务状态
      const pushStatus = pushService.getPushStatus();
      if (!pushStatus.initialized) {
        throw new Error('推送服务未初始化');
      }
      
      // 构建推送请求数据
      const pushData = {
        userId,
        message: {
          ...message,
          clientId: pushStatus.clientId,
          platform: uni.getSystemInfoSync().platform
        }
      };
      
      // 调用后端API发送推送
      const result = await uni.request({
        url: `${this.config.baseUrl}/api/push/send`,
        method: 'POST',
        timeout: this.config.timeout,
        data: pushData,
        header: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${uni.getStorageSync('token')}`
        }
      });
      
      if (result.statusCode === 200 && result.data.success) {
        console.log(`✅ 推送发送成功 [${message.type}]: ${userId}`, message.title);
        
        // 记录推送历史
        this.recordPushHistory(userId, message, true);
        
        return { success: true, data: result.data };
      } else {
        throw new Error(result.data.message || '推送发送失败');
      }
      
    } catch (error) {
      console.error(`❌ 推送发送失败 [${message.type}]: ${userId}`, error);
      
      // 记录推送历史
      this.recordPushHistory(userId, message, false, error.message);
      
      // 如果是网络错误,加入重试队列
      if (this.isNetworkError(error)) {
        await pushNetworkOptimizer.addToRetryQueue(message, error);
      }
      
      return { success: false, error: error.message };
    }
  }
  
  /**
   * 获取订单状态文本映射
   */
  getOrderStatusText(status) {
    const statusMap = {
      'pending': '待付款',
      'paid': '已付款',
      'confirmed': '已确认',
      'preparing': '备货中',
      'shipped': '已发货',
      'in_transit': '运输中',
      'delivered': '已送达',
      'received': '已签收',
      'completed': '已完成',
      'cancelled': '已取消',
      'refunding': '退款中',
      'refunded': '已退款'
    };
    
    return statusMap[status] || '状态更新';
  }
  
  /**
   * 检查营销活动有效性
   */
  isCampaignValid(campaign) {
    const now = Date.now();
    
    // 检查开始时间
    if (campaign.startTime && now < new Date(campaign.startTime).getTime()) {
      return false;
    }
    
    // 检查结束时间
    if (campaign.endTime && now > new Date(campaign.endTime).getTime()) {
      return false;
    }
    
    // 检查活动状态
    if (campaign.status !== 'active') {
      return false;
    }
    
    return true;
  }
  
  /**
   * 计算距离到期天数
   */
  getDaysUntilExpiry(expiryDate) {
    const now = new Date();
    const expiry = new Date(expiryDate);
    const diffTime = expiry - now;
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    return Math.max(0, diffDays);
  }
  
  /**
   * 检查是否为网络错误
   */
  isNetworkError(error) {
    const networkErrorMessages = [
      'network',
      'timeout',
      'offline',
      'connection',
      'ENOTFOUND',
      'ECONNREFUSED'
    ];
    
    const errorMessage = error.message || error.toString();
    return networkErrorMessages.some(keyword => 
      errorMessage.toLowerCase().includes(keyword.toLowerCase())
    );
  }
  
  /**
   * 记录推送历史 - 用于统计和分析
   */
  recordPushHistory(userId, message, success, error = null) {
    try {
      const history = uni.getStorageSync('push_history') || [];
      
      const record = {
        userId,
        messageId: message.payload.messageId || this.generateMessageId(message),
        type: message.type,
        title: message.title,
        priority: message.priority,
        success,
        timestamp: Date.now(),
        ...(error && { error })
      };
      
      history.push(record);
      
      // 只保留最近1000条记录
      if (history.length > 1000) {
        history.splice(0, history.length - 1000);
      }
      
      uni.setStorageSync('push_history', history);
      
    } catch (error) {
      console.error('记录推送历史失败:', error);
    }
  }
  
  /**
   * 生成消息ID
   */
  generateMessageId(message) {
    const content = JSON.stringify({
      type: message.type,
      title: message.title,
      content: message.content,
      timestamp: Math.floor(message.timestamp / 1000)
    });
    
    return this.simpleHash(content);
  }
  
  /**
   * 简单的哈希函数
   */
  simpleHash(str) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return hash.toString();
  }
  
  /**
   * 加载用户偏好设置
   */
  loadUserPreferences() {
    const defaults = {
      marketing: true,    // 营销推送
      order: true,        // 订单推送
      logistics: true,    // 物流推送
      coupon: true,       // 优惠券推送
      social: true,       // 社交推送
      system: true        // 系统推送
    };
    
    const saved = uni.getStorageSync('push_preferences') || {};
    
    return {
      ...defaults,
      ...saved
    };
  }
  
  /**
   * 更新用户偏好设置
   */
  updateUserPreferences(preferences) {
    this.userPreferences = {
      ...this.userPreferences,
      ...preferences
    };
    
    uni.setStorageSync('push_preferences', this.userPreferences);
    
    console.log('✅ 用户偏好设置已更新:', this.userPreferences);
  }
  
  /**
   * 获取推送统计信息
   */
  getPushStats() {
    try {
      const history = uni.getStorageSync('push_history') || [];
      
      const stats = {
        total: history.length,
        success: history.filter(h => h.success).length,
        failed: history.filter(h => !h.success).length,
        byType: {},
        lastPush: history.length > 0 ? history[history.length - 1].timestamp : null
      };
      
      // 按类型统计
      history.forEach(record => {
        if (!stats.byType[record.type]) {
          stats.byType[record.type] = { total: 0, success: 0, failed: 0 };
        }
        stats.byType[record.type].total++;
        if (record.success) {
          stats.byType[record.type].success++;
        } else {
          stats.byType[record.type].failed++;
        }
      });
      
      return stats;
      
    } catch (error) {
      console.error('获取推送统计失败:', error);
      return { total: 0, success: 0, failed: 0, byType: {} };
    }
  }
}
 
// 创建并导出单例实例
const ecommercePushService = new EcommercePushService();
 
export default ecommercePushService;

6.3 使用示例

// 在业务逻辑中使用电商推送服务
 
// 1. 订单状态更新
await ecommercePushService.sendOrderUpdate(userId, {
  orderId: '123456',
  orderNo: 'ORD202312010001',
  status: 'shipped'
});
 
// 2. 营销活动推送
await ecommercePushService.sendMarketingCampaign(userId, {
  id: 'campaign_001',
  title: '双12大促',
  content: '全场5折起,限时抢购!',
  type: 'discount',
  startTime: '2023-12-01 00:00:00',
  endTime: '2023-12-12 23:59:59',
  url: '/pages/campaign/detail?id=campaign_001'
});
 
// 3. 物流信息更新
await ecommercePushService.sendLogisticsUpdate(userId, {
  orderId: '123456',
  trackingNo: 'SF1234567890',
  company: '顺丰速运',
  status: '派送中',
  location: '北京市朝阳区'
});
 
// 4. 优惠券到期提醒
await ecommercePushService.sendCouponExpiryReminder(userId, {
  id: 'coupon_001',
  title: '满100减20',
  discount: '20元',
  minAmount: '100元',
  expiryDate: '2023-12-15 23:59:59'
});
 
// 5. 获取推送统计
const stats = ecommercePushService.getPushStats();
console.log('推送统计:', stats);

总结与展望

本文深入探讨了uni-app通知功能的完整实现方案,从权限管理到推送集成,从基础功能到高级优化,为开发者提供了全面的技术指导。通过合理的架构设计和最佳实践,可以构建出稳定可靠的通知系统。

关键要点回顾

  1. 权限管理是基础:正确处理各平台的权限差异,使用智能申请策略提升用户授权率
  2. 推送集成需全面:支持多种推送服务,通过多通道策略确保消息可达
  3. 用户体验要优化:智能推送策略,避免过度打扰用户,提供个性化推送体验
  4. 性能监控不能少:实时监控推送效果,通过内存优化和网络优化提升系统稳定性
  5. 错误处理要完善:完善的错误处理和重试机制,确保消息不丢失

TRAE IDE在推送开发中的价值

通过本文的实践可以看出,TRAE IDE在推送功能开发中发挥了重要作用:

  • 智能代码补全:快速生成平台相关的条件编译代码
  • 实时错误检测:及时发现潜在的权限和平台兼容性问题
  • 调试工具集成:简化推送测试流程,提升开发效率
  • 性能分析:帮助识别和优化性能瓶颈

未来发展方向

  • AI智能推送:基于用户行为的个性化推送,利用机器学习优化推送时机和内容
  • 富媒体通知:支持图片、视频、音频等丰富内容格式,提升用户参与度
  • 跨平台统一:更完善的跨平台推送解决方案,统一各平台的推送接口
  • 隐私保护加强:符合GDPR、CCPA等隐私法规要求,提供更透明的隐私控制
  • 边缘计算集成:利用边缘计算降低推送延迟,提升实时性
  • 5G网络优化:针对5G网络特性优化推送策略,提供更丰富的推送体验

借助 TRAE IDE 的强大功能,开发者可以更高效地实现这些高级特性,提升开发效率和应用质量。TRAE IDE的智能代码补全、实时错误检测和调试工具,让整个开发过程更加顺畅,帮助开发者专注于业务逻辑的实现。

(此内容由 AI 辅助生成,仅供参考)