peixue-dev/peidu/docs/fixes/2026-01-24-P0功能完善/📋消息推送功能实施方案.md

18 KiB
Raw Permalink Blame History

消息推送功能实施方案

📋 功能清单

1. 订单状态变更通知

1.1 通知场景

场景1: 订单支付成功
- 标题: "支付成功"
- 内容: "您的订单已支付成功,订单号:{orderNo}"
- 类型: payment
- 关联: orderId

场景2: 订单派单成功
- 标题: "订单已派单"
- 内容: "您的订单已分配给陪伴员{teacherName}"
- 类型: order
- 关联: orderId

场景3: 陪伴员接单
- 标题: "陪伴员已接单"
- 内容: "陪伴员{teacherName}已接受您的订单"
- 类型: order
- 关联: orderId

场景4: 服务开始(签到)
- 标题: "服务已开始"
- 内容: "陪伴员{teacherName}已签到,服务开始"
- 类型: service
- 关联: orderId

场景5: 服务结束(签退)
- 标题: "服务已结束"
- 内容: "陪伴员{teacherName}已签退,服务时长{duration}分钟"
- 类型: service
- 关联: orderId

场景6: 订单完成
- 标题: "订单已完成"
- 内容: "您的订单已完成,感谢使用"
- 类型: order
- 关联: orderId

场景7: 订单取消
- 标题: "订单已取消"
- 内容: "您的订单已取消,原因:{reason}"
- 类型: order
- 关联: orderId

场景8: 订单退款
- 标题: "退款成功"
- 内容: "您的订单退款已成功,金额¥{amount}"
- 类型: refund
- 关联: orderId

2. 服务反馈通知

2.1 通知场景

场景1: 每日反馈提交
- 标题: "收到新的服务反馈"
- 内容: "陪伴员{teacherName}提交了{date}的服务反馈"
- 类型: feedback
- 关联: feedbackId

场景2: 周反馈提交
- 标题: "收到本周服务总结"
- 内容: "陪伴员{teacherName}提交了本周服务总结"
- 类型: feedback
- 关联: feedbackId

场景3: 月反馈提交
- 标题: "收到本月服务报告"
- 内容: "陪伴员{teacherName}提交了本月服务报告"
- 类型: feedback
- 关联: feedbackId

场景4: 管理师回复反馈
- 标题: "管理师回复了您的反馈"
- 内容: "管理师对您的反馈进行了回复"
- 类型: feedback
- 关联: feedbackId

3. 优惠券通知

3.1 通知场景

场景1: 优惠券领取成功
- 标题: "优惠券领取成功"
- 内容: "您已成功领取{couponName},有效期至{expireDate}"
- 类型: coupon
- 关联: couponId

场景2: 优惠券即将到期
- 标题: "优惠券即将到期"
- 内容: "{couponName}将在3天后到期请尽快使用"
- 类型: coupon
- 关联: couponId

场景3: 优惠券已过期
- 标题: "优惠券已过期"
- 内容: "{couponName}已过期"
- 类型: coupon
- 关联: couponId

场景4: 新优惠券上架
- 标题: "新优惠券上架"
- 内容: "{couponName}已上架,快来领取吧"
- 类型: coupon
- 关联: couponId

4. 系统通知

4.1 通知场景

场景1: 系统公告
- 标题: 公告标题
- 内容: 公告内容
- 类型: system
- 关联: announcementId

场景2: 账户余额变动
- 标题: "账户余额变动"
- 内容: "您的账户余额{增加/减少}¥{amount},当前余额¥{balance}"
- 类型: wallet
- 关联: transactionId

场景3: 积分变动
- 标题: "积分变动"
- 内容: "您的积分{增加/减少}{points},当前积分{totalPoints}"
- 类型: points
- 关联: pointsRecordId

场景4: 提现审核结果
- 标题: "提现审核{通过/拒绝}"
- 内容: "您的提现申请已{通过/拒绝}{备注}"
- 类型: withdraw
- 关联: withdrawId

🔧 后端实现

1. 消息推送服务增强

/**
 * 消息推送服务
 */
@Service
public class NotificationServiceImpl {
    
    /**
     * 订单支付成功通知
     */
    public void sendOrderPaymentNotification(Long userId, Long orderId, String orderNo) {
        sendNotification(
            userId,
            "payment",
            "支付成功",
            "您的订单已支付成功,订单号:" + orderNo,
            orderId
        );
    }
    
    /**
     * 订单派单通知
     */
    public void sendOrderAssignNotification(Long userId, Long orderId, String teacherName) {
        sendNotification(
            userId,
            "order",
            "订单已派单",
            "您的订单已分配给陪伴员" + teacherName,
            orderId
        );
    }
    
    /**
     * 陪伴员接单通知
     */
    public void sendOrderAcceptNotification(Long userId, Long orderId, String teacherName) {
        sendNotification(
            userId,
            "order",
            "陪伴员已接单",
            "陪伴员" + teacherName + "已接受您的订单",
            orderId
        );
    }
    
    /**
     * 服务开始通知(签到)
     */
    public void sendServiceStartNotification(Long userId, Long orderId, String teacherName) {
        sendNotification(
            userId,
            "service",
            "服务已开始",
            "陪伴员" + teacherName + "已签到,服务开始",
            orderId
        );
    }
    
    /**
     * 服务结束通知(签退)
     */
    public void sendServiceEndNotification(Long userId, Long orderId, String teacherName, Integer duration) {
        sendNotification(
            userId,
            "service",
            "服务已结束",
            "陪伴员" + teacherName + "已签退,服务时长" + duration + "分钟",
            orderId
        );
    }
    
    /**
     * 服务反馈通知
     */
    public void sendFeedbackNotification(Long userId, Long feedbackId, String teacherName, String date) {
        sendNotification(
            userId,
            "feedback",
            "收到新的服务反馈",
            "陪伴员" + teacherName + "提交了" + date + "的服务反馈",
            feedbackId
        );
    }
    
    /**
     * 优惠券领取通知
     */
    public void sendCouponReceiveNotification(Long userId, Long couponId, String couponName, String expireDate) {
        sendNotification(
            userId,
            "coupon",
            "优惠券领取成功",
            "您已成功领取" + couponName + ",有效期至" + expireDate,
            couponId
        );
    }
    
    /**
     * 优惠券即将到期通知
     */
    public void sendCouponExpiringSoonNotification(Long userId, Long couponId, String couponName) {
        sendNotification(
            userId,
            "coupon",
            "优惠券即将到期",
            couponName + "将在3天后到期请尽快使用",
            couponId
        );
    }
    
    /**
     * 钱包余额变动通知
     */
    public void sendWalletBalanceChangeNotification(Long userId, Long transactionId, 
            String changeType, BigDecimal amount, BigDecimal balance) {
        String action = "增加".equals(changeType) ? "增加" : "减少";
        sendNotification(
            userId,
            "wallet",
            "账户余额变动",
            "您的账户余额" + action + "¥" + amount + ",当前余额¥" + balance,
            transactionId
        );
    }
    
    /**
     * 提现审核结果通知
     */
    public void sendWithdrawAuditNotification(Long userId, Long withdrawId, 
            boolean approved, String remark) {
        String status = approved ? "通过" : "拒绝";
        String content = "您的提现申请已" + status;
        if (remark != null && !remark.isEmpty()) {
            content += "" + remark;
        }
        sendNotification(
            userId,
            "withdraw",
            "提现审核" + status,
            content,
            withdrawId
        );
    }
}

2. 在业务流程中集成通知

/**
 * 订单服务 - 集成通知
 */
@Service
public class OrderServiceImpl {
    
    @Autowired
    private NotificationServiceImpl notificationService;
    
    /**
     * 支付成功后发送通知
     */
    @Transactional
    public void handlePaymentSuccess(Long orderId) {
        Order order = getById(orderId);
        
        // 更新订单状态
        order.setPaymentStatus(1);
        order.setStatus(2); // 待派单
        updateById(order);
        
        // 发送通知
        notificationService.sendOrderPaymentNotification(
            order.getUserId(),
            orderId,
            order.getOrderNo()
        );
    }
    
    /**
     * 派单成功后发送通知
     */
    @Transactional
    public void assignOrder(Long orderId, Long teacherId) {
        Order order = getById(orderId);
        Teacher teacher = teacherService.getById(teacherId);
        
        // 更新订单
        order.setTeacherId(teacherId);
        order.setStatus(3); // 待接单
        updateById(order);
        
        // 发送通知给家长
        notificationService.sendOrderAssignNotification(
            order.getUserId(),
            orderId,
            teacher.getName()
        );
        
        // 发送通知给陪伴员
        notificationService.sendNotification(
            teacherId,
            "order",
            "收到新订单",
            "您收到一个新订单,请及时处理",
            orderId
        );
    }
    
    /**
     * 签到后发送通知
     */
    public void handleCheckIn(Long orderId) {
        Order order = getById(orderId);
        Teacher teacher = teacherService.getById(order.getTeacherId());
        
        // 发送通知
        notificationService.sendServiceStartNotification(
            order.getUserId(),
            orderId,
            teacher.getName()
        );
    }
    
    /**
     * 签退后发送通知
     */
    public void handleCheckOut(Long orderId, Integer duration) {
        Order order = getById(orderId);
        Teacher teacher = teacherService.getById(order.getTeacherId());
        
        // 发送通知
        notificationService.sendServiceEndNotification(
            order.getUserId(),
            orderId,
            teacher.getName(),
            duration
        );
    }
}

/**
 * 成长记录服务 - 集成通知
 */
@Service
public class GrowthRecordServiceImpl {
    
    @Autowired
    private NotificationServiceImpl notificationService;
    
    /**
     * 提交反馈后发送通知
     */
    public void submitFeedback(GrowthRecord record) {
        // 保存记录
        save(record);
        
        // 获取订单信息
        Order order = orderService.getById(record.getOrderId());
        Teacher teacher = teacherService.getById(record.getTeacherId());
        
        // 发送通知给家长
        notificationService.sendFeedbackNotification(
            order.getUserId(),
            record.getId(),
            teacher.getName(),
            record.getRecordDate().toString()
        );
    }
}

🎨 前端实现

1. 消息中心页面增强

<!-- message/center.vue -->
<template>
  <view class="message-center">
    <!-- 消息分类Tab -->
    <view class="message-tabs">
      <view 
        v-for="tab in tabs" 
        :key="tab.value"
        class="tab-item"
        :class="{ active: currentTab === tab.value }"
        @click="switchTab(tab.value)"
      >
        <text>{{ tab.label }}</text>
        <view v-if="tab.unreadCount > 0" class="badge">{{ tab.unreadCount }}</view>
      </view>
    </view>
    
    <!-- 消息列表 -->
    <scroll-view class="message-list" scroll-y @scrolltolower="loadMore">
      <view 
        v-for="item in messageList" 
        :key="item.id"
        class="message-item"
        :class="{ unread: item.isRead === 0 }"
        @click="viewMessage(item)"
      >
        <view class="message-icon">
          <text>{{ getMessageIcon(item.type) }}</text>
        </view>
        <view class="message-content">
          <view class="message-title">{{ item.title }}</view>
          <view class="message-text">{{ item.content }}</view>
          <view class="message-time">{{ formatTime(item.createTime) }}</view>
        </view>
        <view v-if="item.isRead === 0" class="unread-dot"></view>
      </view>
      
      <!-- 空状态 -->
      <view v-if="messageList.length === 0" class="empty-state">
        <text class="empty-icon">📭</text>
        <text class="empty-text">暂无消息</text>
      </view>
      
      <!-- 加载更多 -->
      <view v-if="loading" class="loading">加载中...</view>
    </scroll-view>
    
    <!-- 操作按钮 -->
    <view class="action-bar">
      <button class="btn-mark-all" @click="markAllRead">全部标记为已读</button>
    </view>
  </view>
</template>

<script>
import { notificationApi } from '@/api'

export default {
  data() {
    return {
      tabs: [
        { label: '全部', value: 'all', unreadCount: 0 },
        { label: '订单', value: 'order', unreadCount: 0 },
        { label: '服务', value: 'service', unreadCount: 0 },
        { label: '反馈', value: 'feedback', unreadCount: 0 },
        { label: '系统', value: 'system', unreadCount: 0 }
      ],
      currentTab: 'all',
      messageList: [],
      page: 1,
      pageSize: 20,
      hasMore: true,
      loading: false
    }
  },
  
  onLoad() {
    this.loadMessages()
    this.loadUnreadCount()
  },
  
  onPullDownRefresh() {
    this.page = 1
    this.messageList = []
    this.loadMessages().then(() => {
      uni.stopPullDownRefresh()
    })
  },
  
  methods: {
    async loadMessages() {
      if (this.loading || !this.hasMore) return
      
      this.loading = true
      try {
        const res = await notificationApi.getList({
          type: this.currentTab === 'all' ? null : this.currentTab,
          page: this.page,
          size: this.pageSize
        })
        
        if (res.code === 200) {
          const newMessages = res.data.records || []
          this.messageList = this.page === 1 ? newMessages : [...this.messageList, ...newMessages]
          this.hasMore = newMessages.length >= this.pageSize
          this.page++
        }
      } catch (e) {
        console.error('加载消息失败:', e)
      } finally {
        this.loading = false
      }
    },
    
    async loadUnreadCount() {
      try {
        const res = await notificationApi.getUnreadCount()
        if (res.code === 200) {
          // 更新各分类未读数
          this.tabs.forEach(tab => {
            tab.unreadCount = res.data[tab.value] || 0
          })
        }
      } catch (e) {
        console.error('加载未读数失败:', e)
      }
    },
    
    switchTab(tab) {
      this.currentTab = tab
      this.page = 1
      this.messageList = []
      this.hasMore = true
      this.loadMessages()
    },
    
    async viewMessage(item) {
      // 标记为已读
      if (item.isRead === 0) {
        await notificationApi.markRead(item.id)
        item.isRead = 1
        this.loadUnreadCount()
      }
      
      // 跳转到相关页面
      if (item.relatedId) {
        this.navigateToRelated(item.type, item.relatedId)
      }
    },
    
    navigateToRelated(type, relatedId) {
      const routes = {
        'order': `/order-package/pages/order/detail?id=${relatedId}`,
        'service': `/order-package/pages/order/detail?id=${relatedId}`,
        'feedback': `/user-package/pages/feedback/list`,
        'coupon': `/user-package/pages/coupon/center`,
        'wallet': `/user-package/pages/wallet/transaction`
      }
      
      const url = routes[type]
      if (url) {
        uni.navigateTo({ url })
      }
    },
    
    async markAllRead() {
      try {
        const res = await notificationApi.markAllRead()
        if (res.code === 200) {
          this.messageList.forEach(item => {
            item.isRead = 1
          })
          this.loadUnreadCount()
          uni.showToast({ title: '已全部标记为已读', icon: 'success' })
        }
      } catch (e) {
        console.error('标记失败:', e)
      }
    },
    
    getMessageIcon(type) {
      const icons = {
        'order': '📦',
        'service': '🎯',
        'feedback': '📝',
        'payment': '💰',
        'refund': '💸',
        'coupon': '🎫',
        'wallet': '👛',
        'system': '📢'
      }
      return icons[type] || '📬'
    },
    
    formatTime(time) {
      // 格式化时间显示
      const now = new Date()
      const msgTime = new Date(time)
      const diff = now - msgTime
      
      if (diff < 60000) return '刚刚'
      if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'
      if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前'
      return msgTime.toLocaleDateString()
    },
    
    loadMore() {
      this.loadMessages()
    }
  }
}
</script>

2. 消息轮询机制

// utils/messagePolling.js
class MessagePolling {
  constructor() {
    this.timer = null
    this.interval = 30000 // 30秒轮询一次
    this.isPolling = false
  }
  
  start() {
    if (this.isPolling) return
    
    this.isPolling = true
    this.poll()
    this.timer = setInterval(() => {
      this.poll()
    }, this.interval)
  }
  
  stop() {
    if (this.timer) {
      clearInterval(this.timer)
      this.timer = null
    }
    this.isPolling = false
  }
  
  async poll() {
    try {
      const res = await notificationApi.getUnreadCount()
      if (res.code === 200 && res.data.total > 0) {
        // 更新TabBar角标
        uni.setTabBarBadge({
          index: 3, // 消息中心Tab索引
          text: res.data.total.toString()
        })
      } else {
        uni.removeTabBarBadge({ index: 3 })
      }
    } catch (e) {
      console.error('轮询消息失败:', e)
    }
  }
}

export default new MessagePolling()

📊 数据流转图

业务事件 -> 触发通知 -> 保存到数据库 -> 前端轮询/推送
  -> 用户查看 -> 标记已读 -> 跳转相关页面

⚠️ 注意事项

  1. 消息去重

    • 同一事件不重复发送
    • 使用事件ID作为幂等键
  2. 消息优先级

    • 重要消息(支付、退款)立即推送
    • 普通消息可延迟推送
  3. 性能优化

    • 批量插入消息
    • 定期清理已读消息
    • 消息列表分页加载
  4. 用户体验

    • 消息分类清晰
    • 未读消息突出显示
    • 支持一键已读
    • 消息可跳转到相关页面