18 KiB
18 KiB
消息推送功能实施方案
📋 功能清单
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()
📊 数据流转图
业务事件 -> 触发通知 -> 保存到数据库 -> 前端轮询/推送
-> 用户查看 -> 标记已读 -> 跳转相关页面
⚠️ 注意事项
-
消息去重
- 同一事件不重复发送
- 使用事件ID作为幂等键
-
消息优先级
- 重要消息(支付、退款)立即推送
- 普通消息可延迟推送
-
性能优化
- 批量插入消息
- 定期清理已读消息
- 消息列表分页加载
-
用户体验
- 消息分类清晰
- 未读消息突出显示
- 支持一键已读
- 消息可跳转到相关页面