# 消息通知系统 ## 通知系统架构 ### 通知类型 - **站内信**: 系统内部消息通知 - **邮件通知**: 电子邮件发送 - **短信通知**: 手机短信发送 - **微信通知**: 微信公众号/小程序推送 - **系统公告**: 全站公告通知 ### 核心模块 - **通知模板**: 消息模板管理 - **通知渠道**: 发送渠道配置 - **通知日志**: 发送记录和状态 - **通知队列**: 异步消息处理 ## 站内信系统 ### 站内信表结构 ```sql -- 站内信模板表 CREATE TABLE system_notify_template ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', name VARCHAR(63) NOT NULL COMMENT '模板名称', code VARCHAR(64) NOT NULL COMMENT '模板编码', nickname VARCHAR(255) NOT NULL COMMENT '发送人名称', content TEXT NOT NULL COMMENT '模板内容', type TINYINT NOT NULL COMMENT '类型', params VARCHAR(255) DEFAULT NULL COMMENT '参数数组', status TINYINT NOT NULL COMMENT '开启状态', remark VARCHAR(255) DEFAULT NULL COMMENT '备注', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号' ); -- 站内信消息表 CREATE TABLE system_notify_message ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', user_id BIGINT NOT NULL COMMENT '用户id', user_type TINYINT NOT NULL DEFAULT 0 COMMENT '用户类型', template_id BIGINT NOT NULL COMMENT '模版编号', template_code VARCHAR(64) NOT NULL COMMENT '模板编码', template_nickname VARCHAR(63) NOT NULL COMMENT '模版发送人名称', template_content TEXT NOT NULL COMMENT '模版内容', template_type TINYINT NOT NULL COMMENT '模版类型', template_params VARCHAR(255) NOT NULL COMMENT '模版参数', read_status BIT NOT NULL COMMENT '是否已读', read_time DATETIME DEFAULT NULL COMMENT '阅读时间', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号' ); ``` ### 站内信服务 ```java @Service public class NotifyMessageServiceImpl implements NotifyMessageService { @Resource private NotifyMessageMapper notifyMessageMapper; @Resource private NotifyTemplateService notifyTemplateService; @Override public Long createNotifyMessage(Long userId, Integer userType, NotifyTemplateReqDTO templateReq) { // 获取消息模板 NotifyTemplateDO template = notifyTemplateService.getNotifyTemplate(templateReq.getTemplateCode()); // 渲染模板内容 String content = renderTemplateContent(template, templateReq.getTemplateParams()); // 创建消息 NotifyMessageDO message = new NotifyMessageDO(); message.setUserId(userId); message.setUserType(userType); message.setTemplateId(template.getId()); message.setTemplateCode(template.getCode()); message.setTemplateNickname(template.getNickname()); message.setTemplateContent(content); message.setTemplateType(template.getType()); message.setTemplateParams(JsonUtils.toJsonString(templateReq.getTemplateParams())); message.setReadStatus(false); notifyMessageMapper.insert(message); return message.getId(); } @Override public PageResult getNotifyMessagePage(Long userId, NotifyMessagePageReqVO pageReqVO) { return notifyMessageMapper.selectPage(userId, pageReqVO); } @Override public void markAsRead(Long userId, Long messageId) { NotifyMessageDO message = validateNotifyMessageExists(messageId); // 校验用户权限 if (!Objects.equals(message.getUserId(), userId)) { throw exception(NOTIFY_MESSAGE_NOT_BELONGS_TO_USER); } // 标记已读 if (!message.getReadStatus()) { NotifyMessageDO updateObj = new NotifyMessageDO(); updateObj.setId(messageId); updateObj.setReadStatus(true); updateObj.setReadTime(new Date()); notifyMessageMapper.updateById(updateObj); } } @Override public void markAllAsRead(Long userId) { notifyMessageMapper.updateReadStatusByUserId(userId); } @Override public Long getUnreadCount(Long userId) { return notifyMessageMapper.selectUnreadCountByUserId(userId); } /** * 渲染模板内容 */ private String renderTemplateContent(NotifyTemplateDO template, Map params) { String content = template.getContent(); // 使用占位符替换参数 for (Map.Entry entry : params.entrySet()) { String placeholder = "{" + entry.getKey() + "}"; String value = String.valueOf(entry.getValue()); content = content.replace(placeholder, value); } return content; } } ``` ### 站内信模板管理 ```java @Service public class NotifyTemplateServiceImpl implements NotifyTemplateService { @Override public Long createNotifyTemplate(NotifyTemplateCreateReqVO reqVO) { // 校验模板编码唯一性 validateTemplateCodeDuplicate(null, reqVO.getCode()); // 创建模板 NotifyTemplateDO template = NotifyTemplateConvert.INSTANCE.convert(reqVO); template.setParams(buildTemplateParams(reqVO.getContent())); notifyTemplateMapper.insert(template); return template.getId(); } @Override public void updateNotifyTemplate(NotifyTemplateUpdateReqVO reqVO) { // 校验模板存在 validateNotifyTemplateExists(reqVO.getId()); // 校验编码唯一性 validateTemplateCodeDuplicate(reqVO.getId(), reqVO.getCode()); // 更新模板 NotifyTemplateDO updateObj = NotifyTemplateConvert.INSTANCE.convert(reqVO); updateObj.setParams(buildTemplateParams(reqVO.getContent())); notifyTemplateMapper.updateById(updateObj); } /** * 解析模板参数 */ private String buildTemplateParams(String content) { Set params = new HashSet<>(); // 提取 {参数名} 格式的参数 Pattern pattern = Pattern.compile("\\{([^}]+)\\}"); Matcher matcher = pattern.matcher(content); while (matcher.find()) { params.add(matcher.group(1)); } return JsonUtils.toJsonString(new ArrayList<>(params)); } } ``` ## 邮件通知系统 ### 邮件配置 ```yaml spring: mail: host: smtp.qq.com port: 587 username: your-email@qq.com password: your-password properties: mail: smtp: auth: true starttls: enable: true required: true ``` ### 邮件模板管理 ```sql -- 邮件模板表 CREATE TABLE system_mail_template ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', name VARCHAR(63) NOT NULL COMMENT '模板名称', code VARCHAR(63) NOT NULL COMMENT '模板编码', account_id BIGINT NOT NULL COMMENT '发送的邮箱账号编号', nickname VARCHAR(255) DEFAULT NULL COMMENT '发送人名称', title VARCHAR(255) NOT NULL COMMENT '邮件标题', content TEXT NOT NULL COMMENT '邮件内容', params VARCHAR(255) NOT NULL COMMENT '参数数组', status TINYINT NOT NULL COMMENT '开启状态', remark VARCHAR(255) DEFAULT NULL COMMENT '备注', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除' ); ``` ### 邮件发送服务 ```java @Service public class MailSendServiceImpl implements MailSendService { @Resource private JavaMailSender mailSender; @Resource private MailTemplateService mailTemplateService; @Resource private MailLogService mailLogService; @Override @Async public Long sendSingleMail(String mail, Long userId, Integer userType, String templateCode, Map templateParams) { // 检查邮箱格式 if (!Validator.isEmail(mail)) { throw exception(MAIL_SEND_MAIL_NOT_VALID, mail); } // 获取邮件模板 MailTemplateDO template = mailTemplateService.getMailTemplateByCodeFromCache(templateCode); // 渲染邮件内容 String title = renderTemplateContent(template.getTitle(), templateParams); String content = renderTemplateContent(template.getContent(), templateParams); // 创建发送日志 Long sendLogId = mailLogService.createMailLog(userId, userType, mail, template, templateParams); try { // 发送邮件 doSendMail(template, mail, title, content); // 更新发送状态 mailLogService.updateMailSendResult(sendLogId, true, null); } catch (Exception e) { // 更新发送失败状态 mailLogService.updateMailSendResult(sendLogId, false, e.getMessage()); log.error("[sendSingleMail][发送邮件失败, mail:{}, template:{}]", mail, templateCode, e); throw exception(MAIL_SEND_ERROR); } return sendLogId; } @Override @Async public void sendBatchMail(List mails, String templateCode, Map templateParams) { for (String mail : mails) { try { sendSingleMail(mail, null, null, templateCode, templateParams); } catch (Exception e) { log.error("[sendBatchMail][发送邮件失败, mail:{}, template:{}]", mail, templateCode, e); } } } /** * 执行邮件发送 */ private void doSendMail(MailTemplateDO template, String toMail, String title, String content) throws Exception { // 获取邮箱账号配置 MailAccountDO account = mailAccountService.getMailAccount(template.getAccountId()); // 创建邮件消息 MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); // 设置发件人 helper.setFrom(account.getMail(), template.getNickname()); // 设置收件人 helper.setTo(toMail); // 设置邮件主题和内容 helper.setSubject(title); helper.setText(content, true); // true 表示支持 HTML // 发送邮件 mailSender.send(message); } } ``` ## 短信通知系统 ### 短信渠道配置 ```sql -- 短信渠道表 CREATE TABLE system_sms_channel ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', signature VARCHAR(12) NOT NULL COMMENT '短信签名', code VARCHAR(63) NOT NULL COMMENT '渠道编码', status TINYINT NOT NULL COMMENT '开启状态', remark VARCHAR(255) DEFAULT NULL COMMENT '备注', api_key VARCHAR(128) NOT NULL COMMENT '短信 API 的账号', api_secret VARCHAR(128) DEFAULT NULL COMMENT '短信 API 的密钥', callback_url VARCHAR(255) DEFAULT NULL COMMENT '短信发送回调 URL', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除' ); -- 短信模板表 CREATE TABLE system_sms_template ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', type TINYINT NOT NULL COMMENT '短信签名', status TINYINT NOT NULL COMMENT '开启状态', code VARCHAR(63) NOT NULL COMMENT '模板编码', name VARCHAR(63) NOT NULL COMMENT '模板名称', content VARCHAR(255) NOT NULL COMMENT '模板内容', params VARCHAR(255) NOT NULL COMMENT '参数数组', remark VARCHAR(255) DEFAULT NULL COMMENT '备注', api_template_id VARCHAR(63) NOT NULL COMMENT '短信 API 的模板编号', channel_id BIGINT NOT NULL COMMENT '短信渠道编号', channel_code VARCHAR(63) NOT NULL COMMENT '短信渠道编码', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除' ); ``` ### 短信发送服务 ```java @Service public class SmsSendServiceImpl implements SmsSendService { @Resource private SmsTemplateService smsTemplateService; @Resource private SmsLogService smsLogService; @Resource private SmsClientFactory smsClientFactory; @Override @Async public Long sendSingleSms(String mobile, Long userId, Integer userType, String templateCode, Map templateParams) { // 校验手机号码格式 if (!Validator.isMobile(mobile)) { throw exception(SMS_SEND_MOBILE_NOT_VALID, mobile); } // 获取短信模板 SmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode); // 创建发送日志 Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, template, templateParams); try { // 执行发送 SmsClient smsClient = smsClientFactory.getSmsClient(template.getChannelId()); SmsSendResult sendResult = smsClient.sendSms(mobile, template.getApiTemplateId(), templateParams); // 更新发送结果 smsLogService.updateSmsSendResult(sendLogId, sendResult); } catch (Exception e) { log.error("[sendSingleSms][发送短信失败, mobile:{}, template:{}]", mobile, templateCode, e); smsLogService.updateSmsSendResult(sendLogId, false, e.getMessage()); throw exception(SMS_SEND_ERROR); } return sendLogId; } @Override @Async public void sendBatchSms(List mobiles, String templateCode, Map templateParams) { for (String mobile : mobiles) { try { sendSingleSms(mobile, null, null, templateCode, templateParams); } catch (Exception e) { log.error("[sendBatchSms][发送短信失败, mobile:{}, template:{}]", mobile, templateCode, e); } } } @Override public void receiveSmsStatus(String channelCode, String text, HttpServletRequest request) throws Throwable { // 获取短信客户端 SmsClient smsClient = smsClientFactory.getSmsClient(channelCode); // 解析回调数据 List receiveResults = smsClient.parseSmsReceiveStatus(text, request); // 更新发送状态 for (SmsReceiveRespDTO result : receiveResults) { smsLogService.updateSmsReceiveResult(result.getLogId(), result); } } } ``` ### 短信客户端抽象 ```java public interface SmsClient { /** * 获得渠道编号 */ String getChannelCode(); /** * 发送短信 */ SmsSendRespDTO sendSms(String mobile, String templateId, Map templateParams) throws Throwable; /** * 解析接收短信的结果 */ List parseSmsReceiveStatus(String text, HttpServletRequest request) throws Throwable; } @Component public abstract class AbstractSmsClient implements SmsClient { protected final SmsChannelProperties properties; public AbstractSmsClient(SmsChannelProperties properties) { this.properties = properties; } /** * 初始化 */ @PostConstruct public final void init() { doInit(); log.info("[init][配置({}) 初始化完成]", properties); } /** * 自定义初始化 */ protected abstract void doInit(); } ``` ## 微信通知系统 ### 微信模板消息 ```java @Service public class WechatNotifyService { @Resource private WxMpService wxMpService; /** * 发送模板消息 */ public void sendTemplateMessage(String openId, String templateId, Map data, String url) { try { WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder() .toUser(openId) .templateId(templateId) .url(url) .build(); // 设置模板数据 for (Map.Entry entry : data.entrySet()) { templateMessage.addData(new WxMpTemplateData( entry.getKey(), String.valueOf(entry.getValue()), "#173177" )); } // 发送消息 wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage); } catch (Exception e) { log.error("[sendTemplateMessage][发送微信模板消息失败, openId:{}, templateId:{}]", openId, templateId, e); } } /** * 发送小程序订阅消息 */ public void sendSubscribeMessage(String openId, String templateId, Map data, String page) { try { WxMaSubscribeMessage subscribeMessage = WxMaSubscribeMessage.builder() .toUser(openId) .templateId(templateId) .page(page) .build(); // 设置数据 for (Map.Entry entry : data.entrySet()) { subscribeMessage.addData(new WxMaSubscribeData( entry.getKey(), String.valueOf(entry.getValue()) )); } // 发送消息 wxMaService.getMsgService().sendSubscribeMsg(subscribeMessage); } catch (Exception e) { log.error("[sendSubscribeMessage][发送小程序订阅消息失败, openId:{}, templateId:{}]", openId, templateId, e); } } } ``` ## 系统公告 ### 公告管理 ```sql -- 通知公告表 CREATE TABLE system_notice ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '公告ID', title VARCHAR(50) NOT NULL COMMENT '公告标题', content TEXT NOT NULL COMMENT '公告内容', type TINYINT NOT NULL COMMENT '公告类型', status TINYINT NOT NULL DEFAULT 0 COMMENT '公告状态', creator VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updater VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号' ); ``` ### 公告服务 ```java @Service public class NoticeServiceImpl implements NoticeService { @Override public Long createNotice(NoticeCreateReqVO reqVO) { // 创建通知公告 NoticeDO notice = NoticeConvert.INSTANCE.convert(reqVO); noticeMapper.insert(notice); // 推送给所有在线用户(可选) if (Objects.equals(reqVO.getType(), NoticeTypeEnum.NOTICE.getType())) { pushNoticeToAllUsers(notice); } return notice.getId(); } @Override public void updateNotice(NoticeUpdateReqVO reqVO) { // 校验公告存在 validateNoticeExists(reqVO.getId()); // 更新公告 NoticeDO updateObj = NoticeConvert.INSTANCE.convert(reqVO); noticeMapper.updateById(updateObj); } /** * 推送公告给所有用户 */ private void pushNoticeToAllUsers(NoticeDO notice) { // 获取所有在线用户 Set onlineUserIds = getOnlineUserIds(); // 创建站内信 Map templateParams = new HashMap<>(); templateParams.put("title", notice.getTitle()); templateParams.put("content", notice.getContent()); for (Long userId : onlineUserIds) { try { notifyMessageService.createNotifyMessage(userId, UserTypeEnum.ADMIN.getValue(), new NotifyTemplateReqDTO("SYSTEM_NOTICE", templateParams)); } catch (Exception e) { log.error("[pushNoticeToAllUsers][推送公告失败, userId:{}, noticeId:{}]", userId, notice.getId(), e); } } } } ``` ## 消息队列集成 ### 异步消息处理 ```java @Component public class NotificationProducer { @Resource private RedisTemplate redisTemplate; /** * 发送站内信消息 */ public void sendNotifyMessage(NotifyMessageSendDTO sendDTO) { redisTemplate.convertAndSend("notify.message.send", sendDTO); } /** * 发送邮件消息 */ public void sendMailMessage(MailSendDTO sendDTO) { redisTemplate.convertAndSend("mail.send", sendDTO); } /** * 发送短信消息 */ public void sendSmsMessage(SmsSendDTO sendDTO) { redisTemplate.convertAndSend("sms.send", sendDTO); } } @Component public class NotificationConsumer { @Resource private NotifyMessageService notifyMessageService; @Resource private MailSendService mailSendService; @Resource private SmsSendService smsSendService; @EventListener public void onNotifyMessage(NotifyMessageSendDTO sendDTO) { try { notifyMessageService.createNotifyMessage( sendDTO.getUserId(), sendDTO.getUserType(), sendDTO.getTemplateReq() ); } catch (Exception e) { log.error("[onNotifyMessage][处理站内信消息失败]", e); } } @EventListener public void onMailMessage(MailSendDTO sendDTO) { try { mailSendService.sendSingleMail( sendDTO.getMail(), sendDTO.getUserId(), sendDTO.getUserType(), sendDTO.getTemplateCode(), sendDTO.getTemplateParams() ); } catch (Exception e) { log.error("[onMailMessage][处理邮件消息失败]", e); } } @EventListener public void onSmsMessage(SmsSendDTO sendDTO) { try { smsSendService.sendSingleSms( sendDTO.getMobile(), sendDTO.getUserId(), sendDTO.getUserType(), sendDTO.getTemplateCode(), sendDTO.getTemplateParams() ); } catch (Exception e) { log.error("[onSmsMessage][处理短信消息失败]", e); } } } ``` ## 通知统一发送 ### 通知发送门面 ```java @Service public class NotificationFacadeService { @Resource private NotifyMessageService notifyMessageService; @Resource private MailSendService mailSendService; @Resource private SmsSendService smsSendService; @Resource private WechatNotifyService wechatNotifyService; @Resource private NotificationProducer notificationProducer; /** * 发送通知(多渠道) */ public void sendNotification(NotificationSendDTO sendDTO) { // 站内信 if (sendDTO.getChannels().contains(NotifyChannelEnum.SITE_MESSAGE)) { sendSiteMessage(sendDTO); } // 邮件 if (sendDTO.getChannels().contains(NotifyChannelEnum.EMAIL)) { sendEmail(sendDTO); } // 短信 if (sendDTO.getChannels().contains(NotifyChannelEnum.SMS)) { sendSms(sendDTO); } // 微信 if (sendDTO.getChannels().contains(NotifyChannelEnum.WECHAT)) { sendWechat(sendDTO); } } /** * 批量发送通知 */ @Async public void sendBatchNotification(List sendDTOs) { for (NotificationSendDTO sendDTO : sendDTOs) { try { sendNotification(sendDTO); } catch (Exception e) { log.error("[sendBatchNotification][批量发送通知失败]", e); } } } private void sendSiteMessage(NotificationSendDTO sendDTO) { NotifyMessageSendDTO messageDTO = new NotifyMessageSendDTO(); messageDTO.setUserId(sendDTO.getUserId()); messageDTO.setUserType(sendDTO.getUserType()); messageDTO.setTemplateReq(new NotifyTemplateReqDTO( sendDTO.getTemplateCode(), sendDTO.getTemplateParams() )); notificationProducer.sendNotifyMessage(messageDTO); } private void sendEmail(NotificationSendDTO sendDTO) { if (StrUtil.isBlank(sendDTO.getEmail())) { return; } MailSendDTO mailDTO = new MailSendDTO(); mailDTO.setMail(sendDTO.getEmail()); mailDTO.setUserId(sendDTO.getUserId()); mailDTO.setUserType(sendDTO.getUserType()); mailDTO.setTemplateCode(sendDTO.getTemplateCode()); mailDTO.setTemplateParams(sendDTO.getTemplateParams()); notificationProducer.sendMailMessage(mailDTO); } private void sendSms(NotificationSendDTO sendDTO) { if (StrUtil.isBlank(sendDTO.getMobile())) { return; } SmsSendDTO smsDTO = new SmsSendDTO(); smsDTO.setMobile(sendDTO.getMobile()); smsDTO.setUserId(sendDTO.getUserId()); smsDTO.setUserType(sendDTO.getUserType()); smsDTO.setTemplateCode(sendDTO.getTemplateCode()); smsDTO.setTemplateParams(sendDTO.getTemplateParams()); notificationProducer.sendSmsMessage(smsDTO); } } ``` ## 最佳实践 ### 性能优化 1. **异步发送**: 使用消息队列异步处理通知 2. **批量发送**: 支持批量发送减少系统开销 3. **失败重试**: 实现发送失败的重试机制 4. **流量控制**: 设置发送频率限制防止滥用 ### 用户体验 1. **消息分类**: 不同类型消息使用不同模板 2. **个性化**: 支持用户自定义通知偏好设置 3. **及时性**: 重要消息实时推送 4. **历史记录**: 保留通知历史方便用户查看 ### 监控告警 1. **发送成功率**: 监控各渠道发送成功率 2. **延迟监控**: 监控消息发送延迟 3. **失败分析**: 分析发送失败原因 4. **用量统计**: 统计各类型消息发送量