Files
rui-docs/backend/specs/2026-06-04-mq-unified-push-design.md
vifo 19de7e24ec docs: 迁移 spring-ai 通用文档到 rui-docs
从 docs-local 迁移以下文档:

- backend/guides/: AI开发环境配置、Nacos配置、GitNexus指南、OpenCode工作流等

- backend/templates/: Superpowers设计模板、计划模板、审查清单

- backend/config-templates/: 应用配置模板、Nacos配置

- backend/design/: 数据库表结构规划

- backend/specs/: 项目文档治理、MQ统一推送设计

- backend/: 代码分析报告、Feign分析报告、文档治理报告

- frontend/design/: Admin-UI分模块打包设计
2026-06-04 09:34:03 +08:00

8.7 KiB
Raw Permalink Blame History

MQ 统一推送入口设计文档

设计日期: 2026-06-04 版本: v1.0 状态: 已批准 目标: 创建统一消息队列推送入口 MqClient,封装多 Provider 路由、Action 注入、异常兜底,对标 spring-rui MqDefaultClient


一、背景与目标

1.1 现状分析

当前项目已有基础 MQ 能力:

  • MqService 接口:提供 send() 方法,面向业务开发者直接使用
  • Message<T> 模型:含 id, topic, payload, headers, timestamp, retryCount
  • RedisMqService / RabbitMqService:分别基于 Redis Pub/Sub 和 RabbitMQ 的实现
  • MqTopic 注解:用于方法级消息订阅

存在的问题

  1. 缺乏统一推送门面:业务代码需要直接注入 MqService 并选择实现,无法自动按 Provider 路由
  2. 无 Provider 抽象:无法在多环境(开发用 Redis、生产用 RabbitMQ)间平滑切换
  3. 无 Action 语义:消息缺乏"添加/删除/更新"等业务动作标识
  4. 异常处理分散:各实现自行处理异常,缺乏统一兜底

1.2 目标定义

  1. 创建 MqClient 门面类,作为业务层唯一推送入口
  2. 引入 MqPublisher Provider 接口,实现多 MQ 后端自动路由
  3. 扩展 Message 模型,支持 action、provider、exchange、delay 等高级字段
  4. 保持现有 MqService 接口不变,确保向后兼容
  5. 所有推送操作统一异常捕获和日志记录

二、详细设计

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                      业务层 (Business)                        │
│                    MqClient.publish(...)                     │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                   MqClient (门面/统一入口)                    │
│  · 自动从 Spring 容器获取所有 MqPublisher 实现                 │
│  · 按 support(MqProvider) 过滤匹配的 Publisher               │
│  · 自动注入 action 到 payload                                │
│  · 统一 try-catch + 日志                                     │
└──────────────────────────┬──────────────────────────────────┘
                           │
           ┌───────────────┼───────────────┐
           ▼               ▼               ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  RedisMqPublisher │ │ RabbitMqPublisher│ │  FutureProvider  │
│  (Redis实现)      │ │  (RabbitMQ实现)   │ │  (可扩展)        │
└─────────────────┘ └─────────────────┘ └─────────────────┘

2.2 核心组件

组件 职责 位置
MqClient 统一推送门面,业务层唯一入口 rui-common-mq
MqPublisher Provider 能力接口,定义 support + publish rui-common-mq
MqProvider Provider 枚举(RABBITMQ, REDIS rui-common-mq
MqProperties 配置属性(默认 provider、topic prefix rui-common-mq
MqAction 消息动作枚举(ADDED, DELETED 等) rui-common-mq
Message<T> 扩展后的消息模型 rui-common-mq

2.3 数据流

启动阶段 (一次)
  │
  ▼
Spring 注入 List<MqPublisher> → 遍历注册到 EnumMap
  │
  ▼
publisherMap = { RABBITMQ: RabbitMqPublisher, REDIS: RedisMqPublisher }

运行时 (每次 publish)
  │
  ▼
MqClient.publish(String topic, MqAction action, T payload)
  │
  ├─ 1. 构造 Message<T>
  ├─ 2. 若 action != NONE,将 action 序列化后注入 payload
  ├─ 3. 若 message.provider == null,使用 MqProperties 默认值
  ├─ 4. publisherMap.get(provider)  →  O(1) 直接取到 Publisher
  ├─ 5. 调用 publisher.publish(message)
  └─ 6. catch Exception → log.error,不抛异常

2.4 接口设计

MqClient(门面类)

采用构造器注入 + 启动预构建 EnumMap,避免运行时遍历:

@Component
@Slf4j
public class MqClient {
    private final MqProperties properties;
    private final EnumMap<MqProvider, MqPublisher> publisherMap = new EnumMap<>(MqProvider.class);

    public MqClient(MqProperties properties, List<MqPublisher> publishers) {
        this.properties = properties;
        for (MqPublisher p : publishers) {
            publisherMap.put(p.getProvider(), p);  // O(1) 注册
        }
    }

    /** 使用默认 Provider 推送 */
    public <T> void publish(String topic, T payload);

    /** 使用默认 Provider 推送,带 Action */
    public <T> void publish(String topic, MqAction action, T payload);

    /** 指定 Provider 推送 */
    public <T> void publish(MqProvider provider, String topic, T payload);

    /** 指定 Provider 推送,带 Action */
    public <T> void publish(MqProvider provider, String topic, MqAction action, T payload);

    /** 完整 Message 推送 */
    public <T> void publish(Message<T> message);

    private MqPublisher resolve(MqProvider provider) {
        return publisherMap.get(provider);  // O(1) 查找
    }
}

MqPublisherProvider 接口)

public interface MqPublisher {
    /** 返回该 Publisher 支持的 Provider 类型 */
    MqProvider getProvider();

    /** 基础推送 */
    <T> void publish(String topic, T payload);

    /** 完整消息推送 */
    <T> void publish(Message<T> message);
}

MqProvider(枚举)

public enum MqProvider {
    RABBITMQ,
    REDIS
}

MqProperties(配置)

@ConfigurationProperties(prefix = "mq")
@Data
public class MqProperties {
    private MqProvider provider = MqProvider.RABBITMQ;
    private String prefix = "rui";

    public String enTopic(String topic) { ... }
    public String deTopic(String topic) { ... }
}

MqAction(动作枚举,精简版)

public enum MqAction {
    NONE, ADDED, DELETED, UPDATED, CREATED, 
    CANCEL, ENABLED, SUCCESSFUL, FAILURE
}

2.5 扩展现有实现

RedisMqService 调整:

  • 实现 MqPublisher 接口
  • support(MqProvider.REDIS) 返回 true
  • publish() 复用现有 send() 逻辑

RabbitMqService 调整:

  • 实现 MqPublisher 接口
  • support(MqProvider.RABBITMQ) 返回 true
  • publish() 复用现有 send() 逻辑

2.6 错误处理

  • 异常策略MqClient 所有 publish 方法统一 try-catch,记录 error 日志,不抛异常给业务层
  • Provider 不匹配:若找不到支持该 Provider 的 Publisher,记录 warn 日志
  • Payload 为空:自动创建空 JSON 对象 {}

三、验收标准

  • MqClient 可正常注入并调用 publish() 发送消息
  • 通过配置 mq.provider=redis 可自动切换到 Redis 实现
  • publish(topic, MqAction.ADDED, payload) 发送的消息包含 action 字段
  • 发送异常时不抛异常,仅记录日志
  • 现有 MqService.send() 调用不受影响,向后兼容
  • 项目可正常编译通过

四、风险与依赖

风险 影响 缓解措施
现有 MqService 被多处使用 不修改 MqService,新建 MqPublisher 接口
Message<T> 扩展后序列化兼容性 新增字段均为可空,不影响现有序列化
SpringUtil 在静态方法中可能未初始化 MqClient 通过构造器注入,非静态调用

五、对标分析

维度 spring-rui (MqDefaultClient) 本设计 (MqClient)
门面类名 MqDefaultClient MqClient(更简洁)
Provider 接口 MqService(含 support/publish MqPublisher(不冲突现有 MqService
配置类名 MqAutoConfiguration MqProperties(更语义化)
JSON 框架 fastjson2 Jackson(适配本项目)
Provider 支持 MQTT, RABBITMQ, REDIS RABBITMQ, REDISMQTT 暂不需要)
Action 枚举 Actions80+ 项) MqAction(精简 9 项)
包名 org.rui.common.mq com.rui.common.mq