CC 咖啡猫的工作空间 Coding Space

实时消息推送实践

实时消息推送是系统与用户实时通信的重要渠道,包括内部系统通知和外部第三方推送。


1. 推送类型

类型 说明 典型场景
站内通知 用户登录后可在站内查看 订单状态变更、系统公告
短信/邮件 运营商通道或邮件服务 验证码、重要通知
App推送 极光/华为/小米通道 营销推送、实时提醒
WebSocket 实时双向通信 客服消息、实时数据大盘
钉钉/企微 企业内部机器人 告警通知、工单提醒

2. 设计要点

2.1 消息模型

// 消息模板
public class PushMessage {
    private Long userId;           // 接收用户
    private String templateCode;   // 模板编码
    private Map<String, String> params;  // 模板参数
    private PushChannel channel;   // 推送渠道
    private LocalDateTime expiredTime;  // 过期时间
}

// 推送渠道
public enum PushChannel {
   站内, 短信, 邮件, APP, WEB_HOOK
}

2.2 异步发送

@Async
public void pushAsync(PushMessage message) {
    // 发送前先查询用户是否订阅该类消息
    if (!userSubscribed(message.getUserId(), message.getTemplateCode())) {
        return;
    }
    // 根据渠道发送
    switch (message.getChannel()) {
        case 站内 -> send站内(message);
        case 短信 -> send短信(message);
        case APP -> sendApp(message);
        case WEB_HOOK -> sendWebhook(message);
    }
}

3. 实现方案

3.1 站内消息表

CREATE TABLE sys_message (
    id          BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id     BIGINT NOT NULL COMMENT '用户ID',
    title       VARCHAR(200) NOT NULL COMMENT '标题',
    content     TEXT COMMENT '内容',
    type        TINYINT DEFAULT 1 COMMENT '1-系统通知 2-订单 3-活动',
    read_status TINYINT DEFAULT 0 COMMENT '0-未读 1-已读',
    created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
    read_at     DATETIME COMMENT '阅读时间'
) COMMENT '站内消息表';

-- 消息模板表
CREATE TABLE msg_template (
    id          BIGINT PRIMARY KEY AUTO_INCREMENT,
    code        VARCHAR(50) UNIQUE NOT NULL COMMENT '模板编码',
    name        VARCHAR(100) NOT NULL COMMENT '模板名称',
    content     TEXT NOT NULL COMMENT '模板内容,支持占位符 {param}',
    channels    VARCHAR(100) COMMENT '可用渠道,逗号分隔',
    status      TINYINT DEFAULT 1 COMMENT '1-启用 0-禁用'
) COMMENT '消息模板表';

3.2 消息发送流程

业务触发 → 消息入库 → MQ异步投递 → 各渠道发送 → 发送记录 → 状态回执

3.3 WebHook 推送(钉钉/企微)

public void sendDingTalk(String webhookUrl, String content) {
    Map<String, Object> body = Map.of(
        "msgtype", "text",
        "text", Map.of("content", content)
    );

    HttpClientUtil.post(webhookUrl, JSON.toJSONString(body),
        Map.of("Content-Type", "application/json"));
}

// 企微机器人
public void sendWeCom(String webhookUrl, String content) {
    Map<String, Object> body = Map.of(
        "msgtype", "text",
        "text", Map.of("content", content)
    );
    HttpClientUtil.post(webhookUrl, JSON.toJSONString(body));
}

4. 注意事项

  1. 频率限制:同一用户同一消息类型,5分钟内不重复推送
  2. 退订管理:支持用户关闭特定类型推送
  3. 敏感信息:手机号、银行卡号等在推送内容中脱敏
  4. 失败重试:MQ消费失败时,3次重试 + 死信队列告警
  5. 发送日志:记录发送结果,便于排查和统计送达率

5. 常见问题

问题 解决方案
推送延迟高 使用独立发送线程池,避免业务线程阻塞
重复推送 消息入库前查重(基于业务ID+用户ID+模板)
渠道挂掉 降级策略:短信失败切邮件,保留站内消息
用户设备不在线 APP推送走厂商通道(华为/小米),支持推送到达回执

最后更新:2026/05/11