SpringBoot整合Vertx-Mqtt多租户(优化版)

整个工具的代码都在Gitee或者Github地址内

gitee:solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb

github:GitHub - ZeroNing/solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb

需要引入的JAR包

复制代码
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-mqtt</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
        </dependency>

需要引入的JAR包(版本根据自身要求使用,本教程用的版本均为最新)

1.新增MessageListener注解

java 复制代码
@Target(value = { ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Component
@Conditional(MqttCondition.class)
public @interface MessageListener {

  /**
   * 主题
   */
  String[] topics();

  /**
   * 消息质量
   */
  int qos() default 0;

  /**
   * 允许订阅的租户范围
   */
  String[] tenantRange() default StrUtil.EMPTY;
}

2.异常编码

java 复制代码
public interface MqttErrorCode extends BaseExceptionCode {

    String CLIENT_IS_NULL="CLIENT_IS_NULL";
}

3.Mqtt开关控制

java 复制代码
public class MqttCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String enabled = ValidateUtils.getOrDefault(context.getEnvironment().getProperty("mqtt.enabled"),"true");
        return BooleanUtil.toBoolean(enabled);
    }
}

4.Mqtt配置

java 复制代码
@Configuration
@EnableConfigurationProperties(value = {TenantMqttProfile.class})
@Import(value = {MqttUtils.class})
@ConditionalOnProperty(name = "mqtt.enabled", havingValue = "true", matchIfMissing = true)
public class MqttConfig extends AbstractMessageLineRunner<MessageListener> {

    private final TenantMqttProfile profile;

    private final MqttUtils mqttUtils;

    public MqttConfig(TenantMqttProfile profile, ApplicationContext applicationContext, MqttUtils mqttUtils) {
        this.profile = profile;
        this.mqttUtils = mqttUtils;
        SpringUtil.setContext(applicationContext);
    }

    @Override
    public void init(List<Object> clazzList) throws Exception {
        // 检查 MQTT 是否启用
        if (!profile.getEnabled()) {
            logger.error("mqtt 不启用,不初始化队列以及消费者");
            return;
        }
        // 获取所有租户的配置 Map<租户编码,MQTT 配置>
        Map<String, MqttProfile> tenantProfileMap = profile.getTenant();
        if (ValidateUtils.isEmpty(tenantProfileMap)) {
            logger.error("AbstractMessageLineRunner:没有 MQTT 配置");
            return;
        }
        // 获取 MqttInitService Bean,如果不存在则使用默认实现
        Map<String, MqttInitService> abstractMQMap = SpringUtil.getBeansOfType(MqttInitService.class);
        MqttInitService mqttInitService = ValidateUtils.isNotEmpty(abstractMQMap) 
                ? abstractMQMap.values().stream().findFirst().get() 
                : new DefaultMqttInitService(mqttUtils);
        // 遍历所有租户配置,为每个租户初始化 MQTT Client
        for (Map.Entry<String, MqttProfile> entry : tenantProfileMap.entrySet()) {
            // entry.getKey()=租户编码,entry.getValue()=该租户的 MQTT 配置
            mqttInitService.initMqttClient(entry.getKey(), entry.getValue(), clazzList);
        }
    }
}
java 复制代码
public class MqttProfile {

  /**
   * 用户名
   */
  private String userName;

  /**
   * 密码
   */
  private String password;

  /**
   * 连接
   */
  private String url;

  /**
   * 客户端的标识(不可重复,为空时侯用uuid)
   */
  private String clientId;

  /**
   * 连接超时(毫秒)
   */
  private int completionTimeout = 30000;

  /**
   * 是否自动重连
   */
  private boolean automaticReconnect = true;

  /**
   * 客户端掉线后,是否自动清除session
   */
  private boolean cleanSession = false;

  /**
   * 心跳时间
   */
  private int keepAliveInterval = MqttClientOptions.DEFAULT_KEEP_ALIVE_INTERVAL;
  /**
   * 遗嘱消息
   */
  private MqttWill will;
  /**
   * 最大未确认消息数量
   */
  private int maxInflight = MqttClientOptions.DEFAULT_MAX_INFLIGHT_QUEUE;

  /**
   * 重连次数(-1 无限重连 0 不重连)
   */
  private int reconnectAttempts = -1;

  /**
   * 重连间隔(毫秒)- 注意:Vert.x 5.x 不支持自动重连
   */
  private long reconnectInterval = 1000;


  /**
   * ssl连接是否验证证书
   */
  private boolean verifyCertificate = false;

  private VertxConfig vertx;

  public static class MqttWill implements Serializable {

    /**
     * 遗嘱主题
     */
    private String topic;
    /**
     * 遗嘱消息
     */
    private String message;
    /**
     * 遗嘱消息质量
     */
    private int qos;

    /**
     * 是否保留消息
     */
    private boolean retained;

    public boolean getRetained() {
      return retained;
    }

    public void setRetained(boolean retained) {
      this.retained = retained;
    }

    public String getTopic() {
      return topic;
    }

    public void setTopic(String topic) {
      this.topic = topic;
    }

    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }

    public int getQos() {
      return qos;
    }

    public void setQos(int qos) {
      this.qos = qos;
    }
  }

  public static class VertxConfig implements Serializable {

    // ==================== 基本配置 ====================

    /**
     * 事件循环线程池大小。默认值:2 * CPU 核心数。
     */
    private int eventLoopPoolSize = VertxOptions.DEFAULT_EVENT_LOOP_POOL_SIZE;

    /**
     * Worker 线程池大小。默认值:20。
     */
    private int workerPoolSize = VertxOptions.DEFAULT_WORKER_POOL_SIZE;

    /**
     * 内部阻塞线程池大小。默认值:20。
     */
    private int internalBlockingPoolSize = VertxOptions.DEFAULT_INTERNAL_BLOCKING_POOL_SIZE;

    /**
     * 阻塞线程检查间隔(数值)。默认值:1(秒)。
     */
    private long blockedThreadCheckInterval = 1L;
    /**
     * 阻塞线程检查间隔的时间单位。默认值:SECONDS。
     */
    private TimeUnit blockedThreadCheckIntervalUnit = TimeUnit.SECONDS;

    /**
     * 事件循环线程最大执行时间(数值)。默认值:2(秒)。
     */
    private long maxEventLoopExecuteTime = 2L;
    /**
     * 事件循环线程最大执行时间单位。默认值:SECONDS。
     */
    private TimeUnit maxEventLoopExecuteTimeUnit = TimeUnit.SECONDS;

    /**
     * Worker 线程最大执行时间(数值)。默认值:60(秒)。
     */
    private long maxWorkerExecuteTime = 60L;
    /**
     * Worker 线程最大执行时间单位。默认值:SECONDS。
     */
    private TimeUnit maxWorkerExecuteTimeUnit = TimeUnit.SECONDS;

    /**
     * 警告异常时间(数值)。默认值:5(秒)。
     */
    private long warningExceptionTime = 5L;
    /**
     * 警告异常时间单位。默认值:SECONDS。
     */
    private TimeUnit warningExceptionTimeUnit = TimeUnit.SECONDS;

    // ==================== 高可用配置 ====================

    /**
     * 是否启用高可用模式。默认值:false。
     */
    private boolean haEnabled = false;

    /**
     * 法定节点数。默认值:1。
     */
    private int quorumSize = 1;

    /**
     * 高可用组名。默认值:__DEFAULT__。
     */
    private String haGroup = "__DEFAULT__";

    // ==================== 高级配置 ====================

    /**
     * 是否优先使用原生传输(如 epoll、kqueue)。默认值:false。
     */
    private boolean preferNativeTransport = false;

    /**
     * 是否禁用线程上下文类加载器(TCCL)。默认值:false。
     */
    private boolean disableTCCL = false;

    /**
     * 是否使用守护线程。默认值:false。
     */
    private Boolean useDaemonThread = false;

    public int getEventLoopPoolSize() {
      return eventLoopPoolSize;
    }

    public void setEventLoopPoolSize(int eventLoopPoolSize) {
      this.eventLoopPoolSize = eventLoopPoolSize;
    }

    public int getWorkerPoolSize() {
      return workerPoolSize;
    }

    public void setWorkerPoolSize(int workerPoolSize) {
      this.workerPoolSize = workerPoolSize;
    }

    public int getInternalBlockingPoolSize() {
      return internalBlockingPoolSize;
    }

    public void setInternalBlockingPoolSize(int internalBlockingPoolSize) {
      this.internalBlockingPoolSize = internalBlockingPoolSize;
    }

    public long getBlockedThreadCheckInterval() {
      return blockedThreadCheckInterval;
    }

    public void setBlockedThreadCheckInterval(long blockedThreadCheckInterval) {
      this.blockedThreadCheckInterval = blockedThreadCheckInterval;
    }

    public TimeUnit getBlockedThreadCheckIntervalUnit() {
      return blockedThreadCheckIntervalUnit;
    }

    public void setBlockedThreadCheckIntervalUnit(TimeUnit blockedThreadCheckIntervalUnit) {
      this.blockedThreadCheckIntervalUnit = blockedThreadCheckIntervalUnit;
    }

    public long getMaxEventLoopExecuteTime() {
      return maxEventLoopExecuteTime;
    }

    public void setMaxEventLoopExecuteTime(long maxEventLoopExecuteTime) {
      this.maxEventLoopExecuteTime = maxEventLoopExecuteTime;
    }

    public TimeUnit getMaxEventLoopExecuteTimeUnit() {
      return maxEventLoopExecuteTimeUnit;
    }

    public void setMaxEventLoopExecuteTimeUnit(TimeUnit maxEventLoopExecuteTimeUnit) {
      this.maxEventLoopExecuteTimeUnit = maxEventLoopExecuteTimeUnit;
    }

    public long getMaxWorkerExecuteTime() {
      return maxWorkerExecuteTime;
    }

    public void setMaxWorkerExecuteTime(long maxWorkerExecuteTime) {
      this.maxWorkerExecuteTime = maxWorkerExecuteTime;
    }

    public TimeUnit getMaxWorkerExecuteTimeUnit() {
      return maxWorkerExecuteTimeUnit;
    }

    public void setMaxWorkerExecuteTimeUnit(TimeUnit maxWorkerExecuteTimeUnit) {
      this.maxWorkerExecuteTimeUnit = maxWorkerExecuteTimeUnit;
    }

    public long getWarningExceptionTime() {
      return warningExceptionTime;
    }

    public void setWarningExceptionTime(long warningExceptionTime) {
      this.warningExceptionTime = warningExceptionTime;
    }

    public TimeUnit getWarningExceptionTimeUnit() {
      return warningExceptionTimeUnit;
    }

    public void setWarningExceptionTimeUnit(TimeUnit warningExceptionTimeUnit) {
      this.warningExceptionTimeUnit = warningExceptionTimeUnit;
    }

    public boolean isHaEnabled() {
      return haEnabled;
    }

    public void setHaEnabled(boolean haEnabled) {
      this.haEnabled = haEnabled;
    }

    public int getQuorumSize() {
      return quorumSize;
    }

    public void setQuorumSize(int quorumSize) {
      this.quorumSize = quorumSize;
    }

    public String getHaGroup() {
      return haGroup;
    }

    public void setHaGroup(String haGroup) {
      this.haGroup = haGroup;
    }

    public boolean isPreferNativeTransport() {
      return preferNativeTransport;
    }

    public void setPreferNativeTransport(boolean preferNativeTransport) {
      this.preferNativeTransport = preferNativeTransport;
    }

    public boolean isDisableTCCL() {
      return disableTCCL;
    }

    public void setDisableTCCL(boolean disableTCCL) {
      this.disableTCCL = disableTCCL;
    }

    public Boolean getUseDaemonThread() {
      return useDaemonThread;
    }

    public void setUseDaemonThread(Boolean useDaemonThread) {
      this.useDaemonThread = useDaemonThread;
    }
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  public String getClientId() {
    return clientId;
  }

  public void setClientId(String clientId) {
    this.clientId = clientId;
  }

  public int getCompletionTimeout() {
    return completionTimeout;
  }

  public void setCompletionTimeout(int completionTimeout) {
    this.completionTimeout = completionTimeout;
  }

  public boolean isAutomaticReconnect() {
    return automaticReconnect;
  }

  public void setAutomaticReconnect(boolean automaticReconnect) {
    this.automaticReconnect = automaticReconnect;
  }

  public boolean isCleanSession() {
    return cleanSession;
  }

  public void setCleanSession(boolean cleanSession) {
    this.cleanSession = cleanSession;
  }

  public int getKeepAliveInterval() {
    return keepAliveInterval;
  }

  public void setKeepAliveInterval(int keepAliveInterval) {
    this.keepAliveInterval = keepAliveInterval;
  }

  public MqttWill getWill() {
    return will;
  }

  public void setWill(MqttWill will) {
    this.will = will;
  }

  public int getMaxInflight() {
    return maxInflight;
  }

  public void setMaxInflight(int maxInflight) {
    this.maxInflight = maxInflight;
  }

  public int getReconnectAttempts() {
    return reconnectAttempts;
  }

  public void setReconnectAttempts(int reconnectAttempts) {
    this.reconnectAttempts = reconnectAttempts;
  }

  public long getReconnectInterval() {
    return reconnectInterval;
  }

  public void setReconnectInterval(long reconnectInterval) {
    this.reconnectInterval = reconnectInterval;
  }

  public boolean isVerifyCertificate() {
    return verifyCertificate;
  }

  public void setVerifyCertificate(boolean verifyCertificate) {
    this.verifyCertificate = verifyCertificate;
  }

  public VertxConfig getVertx() {
    return vertx;
  }

  public void setVertx(VertxConfig vertx) {
    this.vertx = vertx;
  }
}
java 复制代码
@ConfigurationProperties("mqtt")
public class TenantMqttProfile {

  public Map<String,MqttProfile> tenant;

  //是否启用
  private boolean enabled = true;

  public boolean getEnabled() {
    return enabled;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }

  public Map<String, MqttProfile> getTenant() {
    return tenant;
  }

  public void setTenant(Map<String, MqttProfile> tenant) {
    this.tenant = tenant;
  }
}

5.抽象消费者类

java 复制代码
public abstract class AbstractConsumer<T, R> implements CommonMqttMessageListener<T, R, MqttModel<T>> {

    protected final Logger logger = LoggerUtils.logger(getClass());

    protected String tenantCode;

    public void messageArrived(String topic, MqttPublishMessage message) throws Exception {
        String json = message.payload().toString(StandardCharsets.UTF_8);
        Throwable throwable = null;
        R result = null;
        MqttModel<T> model = null;
        try {
            model = conversion(json);
            tenantCode = model.getTenantCode();
            logger.info("线程名:{},租户编码为:{},消息ID:{},topic主题:{},{}:消费者消息: {}", Thread.currentThread().getName(), tenantCode, message.messageId(), topic,getClass().getName(), json);
            // 判断是否重复消费
            if (checkMessageKey(model)) {
                throw new BaseException(MqErrorCode.MESSAGE_REPEAT_CONSUMPTION);
            }
            if (ValidateUtils.isNotEmpty(tenantCode)) {
                RequestHeaderHolder.setTenantCode(tenantCode);
            }
            // 消费消息
            result = this.handleMessage(model.getBody());
        } catch (Throwable e) {
            logger.error("AbstractConsumer:消费报错,消息为:{}, 异常为:", json, e);
            throwable = e;
        } finally {
            deleteCheckMessageKey(model);
            // 保存消费成功/失败消息
            saveLog(result, throwable, model);
        }
    }
}

6.Mqtt接收发送实体类

java 复制代码
public class MqttModel<T> extends BaseMq<T> {

    private String topic;

    private boolean retained;

    private int qos;

    public int getQos() {
        return qos;
    }

    public void setQos(int qos) {
        this.qos = qos;
    }

    public boolean getRetained() {
        return retained;
    }

    public void setRetained(boolean retained) {
        this.retained = retained;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public MqttModel(String tenantCode) {
        super();
        setTenantCode(tenantCode);
    }

    public MqttModel(String tenantCode, String topic, T body) {
        super(body);
        this.topic = topic;
        setTenantCode(tenantCode);
    }
}

7.初始化多租户连接接口

java 复制代码
public interface MqttInitService {

    void initMqttClient(String tenantCode, MqttProfile mqttProfile, List<Object> clazzList) throws Exception;

    void initMqttClient(String tenantCode, MqttProfile mqttProfile) throws Exception;
}
java 复制代码
public class DefaultMqttInitService implements MqttInitService {

    private final Logger logger = LoggerUtils.logger(DefaultMqttInitService.class);

    private final MqttUtils utils;

    public DefaultMqttInitService(MqttUtils utils) {
        this.utils = utils;
    }

    @Override
    public void initMqttClient(String tenantCode, MqttProfile mqttProfile, List<Object> clazzList) throws Exception {
        // 初始化 Vert.x
        Vertx vertx = utils.initVertx(mqttProfile.getVertx());
        utils.putVertx(tenantCode, vertx);

        // 从配置 URL 中获取第一个地址 (支持集群配置,逗号分隔)
        String url = mqttProfile.getUrl().split(",")[0];
        // 解析 host 和 port
        String[] parts = url.replace("tcp://", "").replace("ssl://", "").split(":");
        String host = parts[0];
        int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 1883;

        // 初始化 MQTT 连接配置
        MqttClientOptions options = utils.initMqttConnectOptions(mqttProfile);
        // 设置 clientId
        String clientId = ValidateUtils.getOrDefault(mqttProfile.getClientId(), UUID.randomUUID().toString());
        options.setClientId(clientId);

        // 创建 MQTT Client
        MqttClient mqttClient = MqttClient.create(vertx, options);
        // 保存配置到 Map 中,key=租户编码
        utils.putOptionsMap(tenantCode, options);

        // 连接 MQTT Broker
        mqttClient.connect(port, host).onComplete(ar -> {
            if (ar.succeeded()) {
                logger.info("租户:{} MQTT 连接成功,clientId:{}", tenantCode, clientId);
                // 连接成功后订阅主题
                try {
                    utils.subscribe(mqttClient, clazzList, tenantCode);
                } catch (Exception e) {
                    logger.error("租户:{} 订阅主题失败", tenantCode, e);
                }
            } else {
                logger.error("租户:{} MQTT 连接失败", tenantCode, ar.cause());
            }
        });
        // 配置断开连接回调 - Vert.x 5.x 没有 reconnect 方法,需要手动重连
        mqttClient.closeHandler(v -> {
            logger.info("租户:{} MQTT 连接断开", tenantCode);
            mqttClient.connect(port, host).onComplete(ar -> {
                if (ar.succeeded()) {
                    logger.info("租户:{} MQTT 连接成功,clientId:{}", tenantCode, clientId);
                    // 连接成功后订阅主题
                    try {
                        utils.subscribe(mqttClient, clazzList, tenantCode);
                    } catch (Exception e) {
                        logger.error("租户:{} 订阅主题失败", tenantCode, e);
                    }
                } else {
                    logger.error("租户:{} MQTT 连接失败", tenantCode, ar.cause());
                }
            });
        });

        // 保存 client 到 Map 中,key=租户编码
        utils.putClient(tenantCode, mqttClient);
    }

    @Override
    public void initMqttClient(String tenantCode, MqttProfile mqttProfile) throws Exception {
        this.initMqttClient(tenantCode, mqttProfile, new ArrayList<>(SpringUtil.getBeansWithAnnotation(MessageListener.class).values()));
    }
}

8.Mqtt工具类

java 复制代码
public class MqttTopicFilterMatcher {

    private MqttTopicFilterMatcher() {}

    // ====================== 核心API ======================
    /**
     * 查找匹配主题的第一个通配符过滤器
     *
     * @param topic 主题 (e.g. "sensor/room1/temperature")
     * @param filters 通配符主题过滤器集合 (e.g. ["sensor/+/temperature", "alarm/#"])
     * @return 匹配成功的过滤器,若无匹配返回null
     */
    public static String findFirstMatchingFilter(String topic, List<String> filters) {
        for (String filter : filters) {
            if (matches(filter, topic)) {
                return filter;
            }
        }
        return null;
    }

    /**
     * 查找所有匹配主题的通配符过滤器
     *
     * @param topic 主题
     * @param filters 通配符主题过滤器集合
     * @return 所有匹配成功的过滤器列表(保持原顺序)
     */
    public static List<String> findAllMatchingFilters(String topic, List<String> filters) {
        if (topic == null || filters == null) {
            return Collections.emptyList();
        }

        List<String> matches = new ArrayList<>();
        for (String filter : filters) {
            if (matches(filter, topic)) {
                matches.add(filter);
            }
        }
        return matches;
    }

    // ====================== 底层匹配逻辑 ======================
    /**
     * 判断主题是否匹配单个通配符过滤器
     *
     * @param filter 通配符主题过滤器 (e.g. "sensor/+/temperature")
     * @param topic  实际主题 (e.g. "sensor/room1/temperature")
     * @return true 如果匹配成功
     */
    public static boolean matches(String filter, String topic) {
        // 空检查(MQTT规范禁止空主题)
        if (filter == null || topic == null ||
                filter.isEmpty() || topic.isEmpty()) {
            return false;
        }

        // 分割层级(保留末尾空字符串以检测非法结尾'/')
        String[] filterLevels = filter.split("/", -1);
        String[] topicLevels = topic.split("/", -1);

        // 检查过滤器合法性(纯文本主题也需验证)
        if (!isValidFilter(filterLevels)) {
            return false;
        }

        // 逐层匹配
        int i = 0;
        while (i < filterLevels.length && i < topicLevels.length) {
            String f = filterLevels[i];
            String t = topicLevels[i];

            // 处理非法空层级(规范禁止)
            if (t.isEmpty()) {
                return false;
            }

            if (f.equals("+")) {
                // '+' 匹配任意单层非空主题
                i++;
            } else if (f.equals("#")) {
                // '#' 必须位于末尾(已在isValidFilter中验证)
                return true;
            } else {
                // 严格字符串匹配(兼容纯文本主题)
                if (!f.equals(t)) {
                    return false;
                }
                i++;
            }
        }

        // 检查层级数是否一致
        return i == filterLevels.length && i == topicLevels.length;
    }

    /**
     * 验证主题过滤器是否符合MQTT规范
     */
    private static boolean isValidFilter(String[] levels) {
        boolean hasMultiLevelWildcard = false;

        for (int i = 0; i < levels.length; i++) {
            String level = levels[i];

            // 检查非法空层级(末尾空字符串允许表示以'/'结尾)
            if (level.isEmpty() && i < levels.length - 1) {
                return false;
            }

            // 检查多级通配符 #
            if (level.equals("#")) {
                if (hasMultiLevelWildcard || i != levels.length - 1) {
                    return false; // '#' 必须是最后一个层级
                }
                hasMultiLevelWildcard = true;
            }
            // 单级通配符+无需额外检查
        }
        return true;
    }

    // ====================== 使用示例 ======================
    public static void main(String[] args) {
        // 测试数据
        String topic = "sensor/room1/temperature";
        List<String> filters = List.of(
                "sensor/+/temperature",  // 通配符匹配
                "sensor/room1/temperature", // 纯文本匹配
                "alarm/#",
                "sensor/room1/+"
        );

        // 场景1:获取第一个匹配的过滤器
        String firstMatch = findFirstMatchingFilter(topic, filters);
        System.out.println("第一个匹配: " + firstMatch);
        // 输出: sensor/+/temperature

        // 场景2:获取所有匹配的过滤器
        List<String> allMatches = findAllMatchingFilters(topic, filters);
        System.out.println("所有匹配: " + allMatches);
        // 输出: [sensor/+/temperature, sensor/room1/temperature, sensor/room1/+]

        // 验证纯文本主题匹配
        assert matches("sensor/room1/temperature", "sensor/room1/temperature"); // true
        assert !matches("sensor/room1/temp", "sensor/room1/temperature");     // false
    }
}
java 复制代码
@Configuration
public class MqttUtils implements SendService<MqttModel<?>> {

    private final Logger logger = LoggerUtils.logger(MqttUtils.class);

    private final Map<String, MqttClient> clientMap = new HashMap<>();

    private final Map<String, MqttClientOptions> optionsMap = new HashMap<>();

    private final Map<String, Vertx> vertxMap = new HashMap<>();

    public Map<String, MqttClientOptions> getOptionsMap() {
        return optionsMap;
    }

    public static Map<String,AbstractConsumer<?,?>> consumerMap = new HashMap<>();

    public void putOptionsMap(String tenantCode, MqttClientOptions options) {
        this.optionsMap.put(tenantCode, options);
    }

    public Map<String, MqttClient> getClientMap() {
        return clientMap;
    }

    public void putClient(String tenantCode, MqttClient client) {
        this.clientMap.put(tenantCode, client);
    }

    public Map<String, Vertx> getVertxMap() {
        return vertxMap;
    }

    public void putVertx(String tenantCode, Vertx vertx) {
        this.vertxMap.put(tenantCode, vertx);
    }

    /**
     * 发送消息
     *
     * @param data 消息内容
     */
    @Override
    public void send(MqttModel<?> data) throws Exception {
        try {
            MqttClient client = getClient(data.getTenantCode());
            String json = JSONUtil.toJsonStr(data);
            client.publish(data.getTopic(), 
                    Buffer.buffer(json.getBytes(StandardCharsets.UTF_8)),
                    MqttQoS.valueOf(data.getQos()),
                    data.getRetained(),
                    false);
        } catch (Exception e) {
            logger.error(String.format("MQTT: 主题[%s]发送消息失败", data.getTopic()), e);
            throw e;
        }
    }

    @Override
    public void sendDelay(MqttModel<?> data, long delay) throws Exception {
        send(data);
    }

    @Override
    public void sendExpiration(MqttModel<?> data, long expiration) throws Exception {
        send(data);
    }

    /**
     * 订阅消息
     *
     * @param tenantCode 租户编码
     * @param topic      主题
     * @param qos        消息质量
     * @param consumer   消费者
     */
    public void subscribe(String tenantCode, String topic, int qos, AbstractConsumer<?, ?> consumer) throws BaseException {
        if (ValidateUtils.isEmpty(topic)) {
            return;
        }
        MqttClient client = getClient(tenantCode);
        client.subscribe(topic, qos);
        // Vert.x MQTT 通过 publishHandler 接收消息
        client.publishHandler(message -> {
            String msgTopic = message.topicName();
            if (msgTopic.equals(topic)) {
                try {
                    consumer.messageArrived(msgTopic, message);
                } catch (Exception e) {
                    logger.error("消费消息异常:", e);
                }
            }
        });
    }

    /**
     * 订阅消息
     *
     * @param client     mqtt连接
     * @param tenantCode 租户编码
     */
    public void subscribe(MqttClient client, String tenantCode) throws Exception {
        List<Object> clazzList = new ArrayList<>(SpringUtil.getBeansWithAnnotation(MessageListener.class).values());
        this.subscribe(client, clazzList, tenantCode);
    }

    /**
     * 订阅消息
     *
     * @param client     mqtt连接
     * @param clazzList  消费者列表
     * @param tenantCode 租户编码
     */
    public void subscribe(MqttClient client, List<Object> clazzList, String tenantCode) throws Exception {
        if (ValidateUtils.isNotEmpty(clazzList)) {
            for (Object abstractConsumer : clazzList) {
                MessageListener messageListener = AnnotationUtil.getAnnotation(abstractConsumer.getClass(), MessageListener.class);
                if (ValidateUtils.isEmpty(messageListener) || ValidateUtils.isEmpty(messageListener.topics())) {
                    continue;
                }
                List<String> rangeList = Lambda.toList(Arrays.asList(messageListener.tenantRange()), ValidateUtils::isNotEmpty, key -> key);
                if (ValidateUtils.isEmpty(rangeList) || rangeList.contains(tenantCode)) {
                    for (String topic : messageListener.topics()) {
                        topic = SpringUtil.getElValue(topic);
                        AbstractConsumer<?, ?> consumer = (AbstractConsumer<?, ?>) BeanUtil.copyProperties(abstractConsumer, abstractConsumer.getClass(), (String) null);
                        // 订阅主题
                        client.subscribe(topic, messageListener.qos());
                        consumerMap.put(topic, consumer);
                    }
                    client.publishHandler(message -> {
                        try {
                            String topic = MqttTopicFilterMatcher.findFirstMatchingFilter(message.topicName(), new ArrayList<>(consumerMap.keySet()));
                            AbstractConsumer<?, ?> consumer = consumerMap.get(topic);
                            if(ObjectUtil.isNull(consumer)){
                                logger.error("主题:{},不存在消费者,消费内容为:{}",message.topicName(),String.valueOf(message.payload()));
                                return;
                            }
                            consumer.messageArrived(message.topicName(), message);
                        } catch (Exception e) {
                            logger.error("消费消息异常,topic:{}", message.topicName(), e);
                        }
                    });
                } else {
                    logger.info("{}租户,{}只支持{}范围", tenantCode, abstractConsumer.getClass().getSimpleName(), rangeList.toArray());
                }
            }
        }
    }

    /**
     * 取消订阅
     *
     * @param topic 主题
     */
    public void unsubscribe(String tenantCode, String[] topic) throws Exception {
        if (ValidateUtils.isEmpty(topic)) {
            return;
        }
        MqttClient client = getClient(tenantCode);
        for (String t : topic) {
            client.unsubscribe(t);
        }
    }

    /**
     * 关闭连接
     */
    public void disconnect(String tenantCode) throws Exception {
        MqttClient client = getClient(tenantCode);
        client.disconnect();
        Vertx vertx = vertxMap.get(tenantCode);
        if (vertx != null) {
            vertx.close();
        }
    }

    /**
     * 重新连接 - Vert.x 5.x 没有内置 reconnect 方法,需要重新创建连接
     */
    public void reconnect(String tenantCode) throws Exception {
        // Vert.x 5.x 没有 reconnect 方法,需要重新创建连接
        logger.warn("Vert.x 5.x 不支持 reconnect 方法,请重新初始化客户端");
    }

    public void reconnect(String tenantCode, MqttProfile mqttProfile) throws Exception {
        reconnect(tenantCode);
    }

    private MqttClient getClient(String tenantCode) throws BaseException {
        MqttClient client = getClientMap().get(tenantCode);
        if (ValidateUtils.isEmpty(client)) {
            throw new BaseException(MqttErrorCode.CLIENT_IS_NULL, tenantCode);
        }
        return client;
    }

    public MqttClientOptions initMqttConnectOptions(MqttProfile mqttProfile) {
        MqttClientOptions options = new MqttClientOptions();
        
        // 基本认证配置
        options.setUsername(mqttProfile.getUserName());
        options.setPassword(mqttProfile.getPassword());
        
        // 会话配置
        options.setCleanSession(mqttProfile.isCleanSession());
        options.setAutoGeneratedClientId(false);
        
        // 心跳配置
        options.setKeepAliveInterval(mqttProfile.getKeepAliveInterval());
        options.setAutoKeepAlive(true);
        
        // 消息流控配置
        options.setMaxInflightQueue(mqttProfile.getMaxInflight());
        options.setAckTimeout(mqttProfile.getCompletionTimeout() / 1000); // 毫秒转秒
        
        // 自动确认配置
        options.setAutoAck(true);

        options.setReconnectAttempts(mqttProfile.getReconnectAttempts());
        options.setReconnectInterval(mqttProfile.getReconnectInterval());

        // 设置遗嘱消息
        MqttWill will = mqttProfile.getWill();
        if (ValidateUtils.isNotEmpty(will)) {
            options.setWillFlag(true);
            options.setWillTopic(will.getTopic());
            // Vert.x 5.x 使用 setWillMessageBytes 而不是 setWillMessage
            if (ValidateUtils.isNotEmpty(will.getMessage())) {
                options.setWillMessageBytes(Buffer.buffer(will.getMessage().getBytes(StandardCharsets.UTF_8)));
            }
            options.setWillQoS(will.getQos());
            options.setWillRetain(will.getRetained());
        }

        // SSL/TLS 配置
        if (mqttProfile.getUrl() != null && mqttProfile.getUrl().startsWith("ssl://")) {
            options.setSsl(true);
        }
        if (!mqttProfile.isVerifyCertificate()) {
            options.setTrustAll(true);
        }

        return options;
    }

    public Vertx initVertx(MqttProfile.VertxConfig vertxConfig) {
        if (vertxConfig == null) {
            return Vertx.vertx();
        }
        VertxOptions options = new VertxOptions()
                .setEventLoopPoolSize(vertxConfig.getEventLoopPoolSize())
                .setWorkerPoolSize(vertxConfig.getWorkerPoolSize())
                .setInternalBlockingPoolSize(vertxConfig.getInternalBlockingPoolSize())
                .setBlockedThreadCheckIntervalUnit(vertxConfig.getBlockedThreadCheckIntervalUnit())
                .setMaxEventLoopExecuteTimeUnit(vertxConfig.getMaxEventLoopExecuteTimeUnit())
                .setMaxWorkerExecuteTimeUnit(vertxConfig.getMaxWorkerExecuteTimeUnit())
                .setWarningExceptionTimeUnit(vertxConfig.getWarningExceptionTimeUnit())
                .setHAEnabled(vertxConfig.isHaEnabled())
                .setQuorumSize(vertxConfig.getQuorumSize())
                .setHAGroup(vertxConfig.getHaGroup())
                .setPreferNativeTransport(vertxConfig.isPreferNativeTransport())
                .setDisableTCCL(vertxConfig.isDisableTCCL());

        if (vertxConfig.getUseDaemonThread() != null) {
            options.setUseDaemonThread(vertxConfig.getUseDaemonThread());
        }

        return Vertx.vertx(options);
    }
}
相关推荐
AC赳赳老秦1 小时前
政企内网落地:OpenClaw 离线环境深度适配方案,无外网场景下本地化模型对接与全功能使用
java·大数据·运维·python·自动化·deepseek·openclaw
weixin_449173651 小时前
在 Java 中,‌线程安全的 List‌ 主要有以下几种实现方式,它们的效率取决于具体的使用场景(尤其是读写比例):
java·线程安全的list
砚底藏山河2 小时前
股票数据API接口:如何获取股票历历史分时KDJ数据
java·python·maven
Csvn2 小时前
Python 性能优化与 Profiling 工具
后端·python
不减20斤不改头像2 小时前
手机一句话开发贪吃蛇!TRAE SOLO 移动端 AI 编程实测
前端·后端
明月_清风3 小时前
K8s 从入门到上手:核心概念+常用工具全解析
后端·kubernetes
随风,奔跑3 小时前
Nginx
服务器·后端·nginx·web
MegaDataFlowers3 小时前
运行若依项目
java
lulu12165440783 小时前
JetBrains IDE 终极AI编程方案:CC GUI插件让Claude Code和Codex丝滑运行
java·ide·人工智能·python·ai编程