Browse Source

feat:项目git仓库迁移

yangliu 4 months ago
commit
b9a9c77f17
100 changed files with 8281 additions and 0 deletions
  1. 38 0
      .gitignore
  2. 31 0
      README.md
  3. 63 0
      hfln-framework-catchlog-starter/pom.xml
  4. 58 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ApplicationContextHelper.java
  5. 12 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchAndLog.java
  6. 94 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchLogAspect.java
  7. 20 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchLogAutoConfiguration.java
  8. 40 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/DefaultResponseHandler.java
  9. 11 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ResponseHandlerFactory.java
  10. 5 0
      hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ResponseHandlerI.java
  11. 1 0
      hfln-framework-catchlog-starter/src/main/resources/META-INF/spring.factories
  12. 99 0
      hfln-framework-common/pom.xml
  13. 71 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/CacheConstants.java
  14. 28 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/GlobalConstants.java
  15. 14 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/PackageConstants.java
  16. 35 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/SecurityConstants.java
  17. 24 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/TokenConstants.java
  18. 38 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/YesOrNoEnum.java
  19. 45 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/enums/SysTypeEnum.java
  20. 23 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/id/IdWorker.java
  21. 82 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/id/SnowFlake.java
  22. 65 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/jackson/JacksonConfiguration.java
  23. 1 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/package-info.java
  24. 44 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/FastJson2JsonRedisSerializer.java
  25. 54 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/RedisConfig.java
  26. 10 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/RedisKey.java
  27. 309 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/service/RedisService.java
  28. 47 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/util/AliSmsUtil.java
  29. 33 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/util/Base62Encoder.java
  30. 128 0
      hfln-framework-common/src/main/java/cn/hfln/framework/common/util/WeChatUtil.java
  31. 3 0
      hfln-framework-common/src/main/resources/META-INF/spring.factories
  32. 213 0
      hfln-framework-design-starter/emqx-spring-boot-starter/README.md
  33. 70 0
      hfln-framework-design-starter/emqx-spring-boot-starter/pom.xml
  34. 83 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/annotation/MqttListener.java
  35. 84 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/annotation/MqttListenerAnnotationProcessor.java
  36. 45 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/autoconfigure/EmqxAutoConfiguration.java
  37. 50 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/autoconfigure/MessageHandlerAutoConfiguration.java
  38. 314 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/config/EmqxConfig.java
  39. 41 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/ConnectionState.java
  40. 448 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/EmqxConnector.java
  41. 67 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/ReconnectStrategy.java
  42. 90 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/constant/EmqxConstants.java
  43. 138 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/context/MessageContext.java
  44. 114 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttClientManager.java
  45. 36 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttMessageHandler.java
  46. 43 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttMessageHandlerRegistry.java
  47. 66 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/exception/EmqxException.java
  48. 118 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/AbstractMessageHandler.java
  49. 79 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/EmqxMessageHandler.java
  50. 63 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandler.java
  51. 200 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerAdapter.java
  52. 99 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerChain.java
  53. 182 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerRegistry.java
  54. 132 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/model/MqttMessages.java
  55. 172 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/properties/EmqxProperties.java
  56. 276 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/publisher/MqttPublisher.java
  57. 210 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/subscriber/MqttSubscriber.java
  58. 211 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/util/EmqxUtils.java
  59. 43 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/util/ExecutorUtils.java
  60. 120 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/META-INF/spring-configuration-metadata.json
  61. 3 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/META-INF/spring.factories
  62. 51 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/application.yml
  63. 11 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/TestApplication.java
  64. 26 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/annotation/MqttListenerTest.java
  65. 211 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/annotation/MqttPublisherListenerTest.java
  66. 15 0
      hfln-framework-design-starter/emqx-spring-boot-starter/src/test/resources/application.yml
  67. 10 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/README.md
  68. 43 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/pom.xml
  69. 85 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/base/BaseSwaggerConfig.java
  70. 47 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/base/SwaggerProperties.java
  71. 91 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/configure/DocAutoConfigure.java
  72. 91 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/properties/DocProperties.java
  73. 3 0
      hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/resources/META-INF/spring.factories
  74. 13 0
      hfln-framework-design-starter/minio-spring-boot-starter/README.md
  75. 47 0
      hfln-framework-design-starter/minio-spring-boot-starter/pom.xml
  76. 83 0
      hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/configure/MinioAutoConfig.java
  77. 52 0
      hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/properties/MinioProperties.java
  78. 173 0
      hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/service/MinioBucketService.java
  79. 663 0
      hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/service/MinioObjectService.java
  80. 2 0
      hfln-framework-design-starter/minio-spring-boot-starter/src/main/resources/META-INF/spring.factories
  81. 234 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/README.md
  82. 105 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/pom.xml
  83. 18 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/annotation/MqttSubscribe.java
  84. 23 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttAutoConfiguration.java
  85. 51 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttClientConfig.java
  86. 26 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttProperties.java
  87. 38 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/converter/JsonMessageConverter.java
  88. 25 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/converter/MessageConverter.java
  89. 83 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/handler/MqttMessageHandler.java
  90. 81 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/handler/MqttSubscribeProcessor.java
  91. 31 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/interceptor/DefaultMqttMessageInterceptor.java
  92. 42 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/interceptor/MqttMessageInterceptor.java
  93. 23 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/DefaultMqttConnectionListener.java
  94. 15 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/DefaultMqttMessageListener.java
  95. 25 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/MqttConnectionListener.java
  96. 16 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/MqttMessageListener.java
  97. 210 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/template/MqttTemplate.java
  98. 2 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/resources/META-INF/spring.factories
  99. 167 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/MqttTemplateTest.java
  100. 13 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestApplication.java

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+*.jar
+mvnw
+mvnw.cmd
+.mvn
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+/.idea/

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# hfln-framework-parent
+
+当前版本: 1.0.0-SNAPSHOT
+
+### 介绍
+> hfln-framework-parent是基于[COLA组件](https://github.com/alibaba/COLA)构建的依赖组件库,用来简化项目,便于维护。
+
+### 组件及相关组件的兼容版本
+- JDK 1.8
+- Spring Boot: 2.3.12.RELEASE
+- Spring Cloud: Hoxton.SR12
+- Spring Cloud Alibaba: 2.2.9.RELEASE
+- Sentinel: 1.8.5
+- Nacos: 2.1.0
+- RocketMQ: 4.9.4
+- MyBatis-Plus: 3.5.3.2
+
+
+### 项目结构说明
+
+``` 
+├── hfln-framework-parent                    -- 父模块
+    ├── hfln-framework-catchlog-starter      -- 异常处理和日志组件
+    ├── hfln-framework-common                -- 通用工具类
+    ├── hfln-framework-design-starter        -- 自定义拓展工具类
+    ├── hfln-framework-domain-starter        -- Spring托管的领域实体组件
+    ├── hfln-framework-dto                   -- DTO格式定义
+    ├── hfln-framework-exception             -- 异常定义
+    ├── hfln-framework-extension-starter     -- 扩展点组件
+    ├── hfln-framework-bom                   -- 以上组件依赖聚合pom
+```

+ 63 - 0
hfln-framework-catchlog-starter/pom.xml

@@ -0,0 +1,63 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.hfln.framework</groupId>
+        <artifactId>hfln-framework-parent</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>hfln-framework-catchlog-starter</artifactId>
+    <name>hfln-framework-catchlog-starter</name>
+    <description>异常处理和日志组件</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-core</artifactId>
+        </dependency>
+        <!-- 工具包 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+
+        <!-- 日志包 -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <!-- 组件依赖 -->
+        <dependency>
+            <groupId>cn.hfln.framework</groupId>
+            <artifactId>hfln-framework-dto</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hfln.framework</groupId>
+            <artifactId>hfln-framework-exception</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <!-- 测试包 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 58 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ApplicationContextHelper.java

@@ -0,0 +1,58 @@
+package cn.hfln.framework.catchlog;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+
+@Component("colaCatchLogApplicationContextHelper")
+@Slf4j
+public class ApplicationContextHelper implements ApplicationContextAware {
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        ApplicationContextHelper.applicationContext = applicationContext;
+    }
+
+    public static <T> T getBean(Class<T> targetClz) {
+        T beanInstance = null;
+        //优先按type查
+        try {
+            beanInstance = (T)applicationContext.getBean(targetClz);
+        } catch (Exception e) {
+        }
+
+        //按name查
+        try {
+            if (beanInstance == null) {
+                String simpleName = targetClz.getSimpleName();
+                //首字母小写
+                simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
+                beanInstance = (T) applicationContext.getBean(simpleName);
+            }
+        }
+        catch (Exception e) {
+            log.warn("No bean found for " + targetClz.getCanonicalName());
+        }
+        return beanInstance;
+    }
+
+    public static Object getBean(String claz) {
+        return ApplicationContextHelper.applicationContext.getBean(claz);
+    }
+
+    public static <T> T getBean(String name, Class<T> requiredType) {
+        return ApplicationContextHelper.applicationContext.getBean(name, requiredType);
+    }
+
+    public static <T> T getBean(Class<T> requiredType, Object... params) {
+        return ApplicationContextHelper.applicationContext.getBean(requiredType, params);
+    }
+
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+}

+ 12 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchAndLog.java

@@ -0,0 +1,12 @@
+package cn.hfln.framework.catchlog;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CatchAndLog {
+
+}

+ 94 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchLogAspect.java

@@ -0,0 +1,94 @@
+package cn.hfln.framework.catchlog;
+
+import cn.hfln.framework.dto.ApiResult;
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.InputStreamSource;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+
+@Aspect
+@Slf4j
+@Order(1)
+public class CatchLogAspect {
+    @Pointcut("@within(cn.hfln.framework.catchlog.CatchAndLog) && execution(public * *(..))")
+    public void pointcut() {
+    }
+
+    @Around("pointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        String traceId = UUID.randomUUID().toString().replace("-", "");
+
+        Object response = null;
+        try {
+            response = joinPoint.proceed();
+            response = wrapApiResult(response);
+        } finally {
+            if (response instanceof ApiResult) {
+                ((ApiResult<?>) response).setTraceId(traceId);
+                ((ApiResult<?>) response).setDateTime(LocalDateTime.now());
+            }
+        }
+        return response;
+    }
+
+    /**
+     * 包装响应为ApiResult格式
+     */
+    private Object wrapApiResult(Object response) {
+        return (response instanceof ApiResult) ? response : ApiResult.success(response);
+    }
+
+    /**
+     * 记录请求参数(优化:移除危险操作,简化逻辑)
+     */
+    private void logRequest(ProceedingJoinPoint joinPoint, String traceId) {
+        try {
+            Signature signature = joinPoint.getSignature();
+            if (!(signature instanceof MethodSignature)) {
+                return;
+            }
+            MethodSignature methodSignature = (MethodSignature) signature;
+            Object[] args = joinPoint.getArgs();
+            String[] argNames = methodSignature.getParameterNames();
+
+            Map<String, Object> paramMap = new HashMap<>();
+            for (int i = 0; i < args.length; i++) {
+                if (args[i] instanceof InputStreamSource || "file".equals(argNames[i])) {
+                    continue;
+                }
+                paramMap.put(argNames[i], args[i]);
+            }
+
+            if (!paramMap.isEmpty()) {
+                log.debug("[traceId:{}] REQUEST PARAMS: {}", traceId, JSON.toJSONString(paramMap));
+            }
+        } catch (Exception e) {
+            log.error("[traceId:{}] 请求日志记录失败: {}", traceId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 记录响应结果(优化:增加空值检查)
+     */
+    private void logResponse(Object response, String traceId) {
+        try {
+            if (response != null) {
+                log.debug("[traceId:{}] RESPONSE: {}", traceId, JSON.toJSONString(response));
+            }
+        } catch (Exception e) {
+            log.error("[traceId:{}] 响应日志记录失败: {}", traceId, e.getMessage(), e);
+        }
+    }
+}

+ 20 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/CatchLogAutoConfiguration.java

@@ -0,0 +1,20 @@
+package cn.hfln.framework.catchlog;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+
+@Configuration
+@EnableAspectJAutoProxy
+public class CatchLogAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(CatchLogAspect.class)
+    public CatchLogAspect catchLogAspect() {
+        return new CatchLogAspect();
+    }
+
+
+}

+ 40 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/DefaultResponseHandler.java

@@ -0,0 +1,40 @@
+package cn.hfln.framework.catchlog;
+
+import cn.hfln.framework.dto.Response;
+import cn.hfln.framework.extension.BaseException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class DefaultResponseHandler implements ResponseHandlerI{
+
+    @Override
+    public  Object handle(Class returnType, String errCode, String errMsg){
+        if (isColaResponse(returnType)){
+            return handleColaResponse(returnType, errCode, errMsg);
+        }
+        return null;
+    }
+
+    public  Object handle(Class returnType, BaseException e){
+        return handle(returnType, e.getErrCode(), e.getMessage());
+    }
+
+
+    private static Object handleColaResponse(Class returnType, String errCode, String errMsg) {
+        try {
+            Response response = (Response)returnType.newInstance();
+            response.setSuccess(false);
+            response.setErrCode(errCode);
+            response.setErrMessage(errMsg);
+            return response;
+        }
+        catch (Exception ex){
+            log.error(ex.getMessage(), ex);
+            return  null;
+        }
+    }
+
+    private static boolean isColaResponse(Class returnType) {
+        return  returnType == Response.class || returnType.getGenericSuperclass() == Response.class;
+    }
+}

+ 11 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ResponseHandlerFactory.java

@@ -0,0 +1,11 @@
+package cn.hfln.framework.catchlog;
+
+public class ResponseHandlerFactory {
+
+    public static ResponseHandlerI get(){
+        if(ApplicationContextHelper.getBean(ResponseHandlerI.class) != null){
+            return ApplicationContextHelper.getBean(ResponseHandlerI.class);
+        }
+        return new DefaultResponseHandler();
+    }
+}

+ 5 - 0
hfln-framework-catchlog-starter/src/main/java/cn/hfln/framework/catchlog/ResponseHandlerI.java

@@ -0,0 +1,5 @@
+package cn.hfln.framework.catchlog;
+
+public interface ResponseHandlerI {
+    public Object handle(Class returnType, String errCode, String errMsg);
+}

+ 1 - 0
hfln-framework-catchlog-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration = cn.hfln.framework.catchlog.CatchLogAutoConfiguration

+ 99 - 0
hfln-framework-common/pom.xml

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.hfln.framework</groupId>
+		<artifactId>hfln-framework-parent</artifactId>
+		<version>1.0.0-SNAPSHOT</version>
+	</parent>
+
+	<artifactId>hfln-framework-common</artifactId>
+	<name>hfln-framework-common</name>
+	<description>通用工具</description>
+
+	<dependencies>
+
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-pool2</artifactId>
+			<scope>compile</scope>
+		</dependency>
+		<!-- SpringBoot Boot Redis -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+		</dependency>
+		<!-- Alibaba Fastjson -->
+		<dependency>
+			<groupId>com.alibaba.fastjson2</groupId>
+			<artifactId>fastjson2</artifactId>
+		</dependency>
+		<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.csp</groupId>
+			<artifactId>sentinel-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.csp</groupId>
+			<artifactId>sentinel-spring-webmvc-adapter</artifactId>
+		</dependency>
+		<!-- Spring Web -->
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webmvc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+
+		<!-- Apache Lang3 -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+		<!-- Transmittable ThreadLocal -->
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>transmittable-thread-local</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-crypto</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-json</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>cn.hfln.framework</groupId>
+			<artifactId>hfln-framework-dto</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>cn.hfln.framework</groupId>
+			<artifactId>hfln-framework-exception</artifactId>
+		</dependency>
+	</dependencies>
+
+</project>

+ 71 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/CacheConstants.java

@@ -0,0 +1,71 @@
+package cn.hfln.framework.common.constant;
+
+/**
+ * 缓存常量信息
+ *
+ * @author cw
+ */
+public class CacheConstants {
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final long CAPTCHA_EXPIRATION = 2;
+    /**
+     * 缓存有效期,默认720(分钟)
+     */
+    public final static long EXPIRATION = 720;
+
+    /**
+     * 缓存刷新时间,默认120(分钟)
+     */
+    public final static long REFRESH_TIME = 120;
+
+    /**
+     * 密码最大错误次数
+     */
+    public final static int PASSWORD_MAX_RETRY_COUNT = 5;
+
+    /**
+     * 密码锁定时间,默认10(分钟)
+     */
+    public final static long PASSWORD_LOCK_TIME = 10;
+
+    /**
+     * 权限缓存前缀
+     */
+    public final static String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 手机登录验证码 redis key
+     */
+    public static final String PHONE_LOGIN_CODE_KEY = "phone_login_codes:";
+    /**
+     * 手机验证码缓存有效期,5分钟
+     */
+    public final static long PHONE_CODE_EXPIRATION = 5;
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
+    /**
+     * 登录IP黑名单 cache key
+     */
+    public static final String SYS_LOGIN_BLACKIPLIST = SYS_CONFIG_KEY + "sys.login.blackIPList";
+}

+ 28 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/GlobalConstants.java

@@ -0,0 +1,28 @@
+package cn.hfln.framework.common.constant;
+
+/**
+ * 全局常量
+ * @author huolifu
+ * @date 2023/10/19 18:29
+ **/
+public class GlobalConstants {
+
+    /**
+     * UTF-8
+     */
+    public final static String CHARSET_UTF8 = "UTF-8";
+
+    /**
+     * 重复用户统计
+     */
+    public final static String REPEAT_USER_COUNT = "REPEAT_USER_COUNT";
+    /**
+     * 标准日期时间格式
+     */
+    public final static String STANDARD_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 默认时区
+     */
+    public final static String DEFAULT_TIMEZONE = "GMT+8";
+
+}

+ 14 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/PackageConstants.java

@@ -0,0 +1,14 @@
+package cn.hfln.framework.common.constant;
+
+/**
+ * PackageConstants
+ *
+ * @author cw
+ * @date 2023/10/17 9:52
+ */
+public class PackageConstants {
+
+    public static String REDIS_CONFIG = "cn.wideth.framework.common.redis";
+
+    public static String SECURITY_CONFIG = "cn.wideth.framework.common.security";
+}

+ 35 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/SecurityConstants.java

@@ -0,0 +1,35 @@
+package cn.hfln.framework.common.constant;
+
+/**
+ * 权限相关通用常量
+ *
+ * @author cw
+ */
+public class SecurityConstants {
+    /**
+     * 用户ID字段
+     */
+    public static final String DETAILS_USER_ID = "user_id";
+
+    /**
+     * 用户名字段
+     */
+    public static final String DETAILS_USERNAME = "username";
+    public static final String REAL_NAME = "realName";
+    public static final String USER_TYPE = "userType";
+
+
+    /**
+     * 用户标识
+     */
+    public static final String USER_KEY = "user_key";
+
+    /**
+     * 登录用户
+     */
+    public static final String LOGIN_USER = "login_user";
+    public static final String SYS_TYPE = "sys_type";
+    public static final String TENANT_ID = "tenant_id";
+
+    public static final long EXPIRATION_TIME = 12 * 60 * 60 * 1000;
+}

+ 24 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/TokenConstants.java

@@ -0,0 +1,24 @@
+package cn.hfln.framework.common.constant;
+
+/**
+ * Token的Key常量
+ *
+ * @author cw
+ */
+public class TokenConstants {
+    /**
+     * 令牌自定义标识
+     */
+    public static final String AUTHENTICATION = "Authorization";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String PREFIX = "Bearer ";
+
+    /**
+     * 令牌秘钥
+     */
+    public final static String SECRET = "wideth";
+
+}

+ 38 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/constant/YesOrNoEnum.java

@@ -0,0 +1,38 @@
+package cn.hfln.framework.common.constant;
+
+import lombok.Getter;
+
+/**
+ * 通用是否
+ *
+ * @author huolifu
+ * @date 2023/10/30 18:37
+ **/
+@Getter
+public enum YesOrNoEnum {
+
+    YES("1", "是"),
+    NO("0", "否");
+
+    private final String code;
+
+    private final String name;
+
+    private YesOrNoEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    public static YesOrNoEnum of(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (YesOrNoEnum userStatus : YesOrNoEnum.values()) {
+            if (code.equals(userStatus.code)) {
+                return userStatus;
+            }
+        }
+        return null;
+    }
+}

+ 45 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/enums/SysTypeEnum.java

@@ -0,0 +1,45 @@
+package cn.hfln.framework.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum SysTypeEnum {
+    DISPATCHER("2", "非-调度中心"),
+    ADMIN_MANAGE("1", "非-后台管理系统"),
+    SUI_XING("3", "非-随行人员"),
+    C_USER("4", "非-用户端"),
+    EMER_DISPATCHER("5", "急-调度中心"),
+    EMER_ADMIN_MANAGE("6", "急-后台管理系统"),
+    EMER_SUB_DISPATCHER("7", "急-调度分站"),
+    EMER_LEADER("8", "急-领导端"),
+    EMER_NOTICE("9", "急-院内告知"),
+
+    ;
+
+    /**
+     * 状态码
+     */
+    private final String code;
+    /**
+     * 状态名称
+     */
+    private final String name;
+
+    SysTypeEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    public static SysTypeEnum of(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (SysTypeEnum payType : SysTypeEnum.values()) {
+            if (code.equals(payType.code)) {
+                return payType;
+            }
+        }
+        return null;
+    }
+}

+ 23 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/id/IdWorker.java

@@ -0,0 +1,23 @@
+package cn.hfln.framework.common.id;
+
+/**
+ * 雪花算法ID生成工具
+ * @author huolifu
+ * @date 2023/10/27 8:50
+ **/
+public class IdWorker {
+    private static SnowFlake instance;
+    private static SnowFlake init(){
+        if(instance==null){
+            synchronized (IdWorker.class){
+                if(instance==null){
+                    instance = new SnowFlake(2,3);
+                }
+            }
+        }
+        return instance;
+    }
+    public static long getId(){
+        return init().nextId();
+    }
+}

+ 82 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/id/SnowFlake.java

@@ -0,0 +1,82 @@
+package cn.hfln.framework.common.id;
+
+/**
+ * 雪花算法
+ * @author huolifu
+ * @date 2023/10/27 8:48
+ **/
+public class SnowFlake {
+    /**
+     * 起始的时间戳
+     */
+    private final static long START_STMP = 1698367781838L;
+    /**
+     * 每一部分占用的位数
+     */
+    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
+    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
+    private final static long DATACENTER_BIT = 5;//数据中心占用的位数
+    /**
+     * 每一部分的最大值
+     */
+    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
+    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
+    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
+    /**
+     * 每一部分向左的位移
+     */
+    private final static long MACHINE_LEFT = SEQUENCE_BIT;
+    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
+    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+    private long datacenterId;  //数据中心
+    private long machineId;     //机器标识
+    private long sequence = 0L; //序列号
+    private long lastStmp = -1L;//上一次时间戳
+    public SnowFlake(long datacenterId, long machineId) {
+        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
+            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
+        }
+        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
+            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
+        }
+        this.datacenterId = datacenterId;
+        this.machineId = machineId;
+    }
+    /**
+     * 产生下一个ID
+     *
+     * @return
+     */
+    public synchronized long nextId() {
+        long currStmp = getNewstmp();
+        if (currStmp < lastStmp) {
+            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
+        }
+        if (currStmp == lastStmp) {
+            //相同毫秒内,序列号自增
+            sequence = (sequence + 1) & MAX_SEQUENCE;
+            //同一毫秒的序列数已经达到最大
+            if (sequence == 0L) {
+                currStmp = getNextMill();
+            }
+        } else {
+            //不同毫秒内,序列号置为0
+            sequence = 0L;
+        }
+        lastStmp = currStmp;
+        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
+                | datacenterId << DATACENTER_LEFT       //数据中心部分
+                | machineId << MACHINE_LEFT             //机器标识部分
+                | sequence;                             //序列号部分
+    }
+    private long getNextMill() {
+        long mill = getNewstmp();
+        while (mill <= lastStmp) {
+            mill = getNewstmp();
+        }
+        return mill;
+    }
+    private long getNewstmp() {
+        return System.currentTimeMillis();
+    }
+}

+ 65 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/jackson/JacksonConfiguration.java

@@ -0,0 +1,65 @@
+package cn.hfln.framework.common.jackson;
+
+import cn.hutool.core.date.DatePattern;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * JacksonConfiguration
+ *
+ * @author cw
+ * @date 2023/12/15 14:57
+ */
+@Configuration
+@AutoConfigureBefore(JacksonAutoConfiguration.class)
+public class JacksonConfiguration {
+
+
+
+
+    /**
+     * Jackson对 LocalDate及LocalDateTime格式化的支持
+     */
+    @Bean
+    public ObjectMapper objectMapper() {
+        return new ObjectMapper()
+                .setLocale(Locale.CHINA)
+                .setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
+                .registerModule(javaTimeModule())
+                .setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN))
+                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+                .findAndRegisterModules();
+    }
+
+    private Module javaTimeModule() {
+        JavaTimeModule module = new JavaTimeModule();
+        module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+        module.addSerializer(new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+        module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
+        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
+        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+        return module;
+    }
+}

+ 1 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/package-info.java

@@ -0,0 +1 @@
+package cn.hfln.framework.common;

+ 44 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,44 @@
+package cn.hfln.framework.common.redis;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ *
+ * @author cw
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    private Class<T> clazz;
+
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
+    }
+}

+ 54 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/RedisConfig.java

@@ -0,0 +1,54 @@
+package cn.hfln.framework.common.redis;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ *
+ * @author cw
+ */
+@Configuration
+@EnableCaching
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedisConfig extends CachingConfigurerSupport {
+    @Bean
+    @SuppressWarnings(value = {"unchecked", "rawtypes"})
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) {
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+
+        container.setConnectionFactory(factory);
+
+        //  container.setTaskExecutor(null);            // 设置用于执行监听器方法的 Executor
+        //  container.setErrorHandler(null);            // 设置监听器方法执行过程中出现异常的处理器
+        //  container.addMessageListener(null, null);   // 手动设置监听器 & 监听的 topic 表达式
+        return container;
+    }
+}

+ 10 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/RedisKey.java

@@ -0,0 +1,10 @@
+package cn.hfln.framework.common.redis;
+
+/**
+ * RedisKey
+ *
+ * @author cw
+ */
+public class RedisKey {
+
+}

+ 309 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/redis/service/RedisService.java

@@ -0,0 +1,309 @@
+package cn.hfln.framework.common.redis.service;
+
+import cn.hutool.core.util.RandomUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * spring redis 工具类
+ *
+ * @author cw
+ **/
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+@Component
+public class RedisService implements CommandLineRunner {
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key   缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 分布式锁
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public boolean setNx(final String key, String value) {
+        return redisTemplate.opsForValue().setIfAbsent(key, value);
+    }
+
+    /**
+     * 分布式锁
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public boolean setNx(final String key, String value, final Long timeout, final TimeUnit timeUnit) {
+        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key      缓存的键值
+     * @param value    缓存的值
+     * @param timeout  时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout)
+    {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @param unit 时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit)
+    {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 有效时间
+     */
+    public long getExpire(final String key)
+    {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key)
+    {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public Long increment(final String key) {
+        return redisTemplate.opsForValue().increment(key);
+    }
+
+    public Long incrementMap(String key, String filed) {
+        return redisTemplate.opsForHash().increment(key, filed, 1L);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key) {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public boolean deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection) > 0;
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key 缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList)
+    {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key)
+    {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key 缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
+    {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext())
+        {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key)
+    {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
+    {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key)
+    {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
+    {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
+    {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey) {
+        return redisTemplate.opsForHash().delete(key, hKey) > 0;
+    }
+
+    @Override
+    public void run(String... args) throws Exception {
+        redisTemplate.opsForValue().set("test", RandomUtil.randomString(2));
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+}

+ 47 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/util/AliSmsUtil.java

@@ -0,0 +1,47 @@
+//package cn.hfln.framework.common.util;
+//
+//import cn.hfln.framework.extension.BizException;
+//import com.aliyun.dysmsapi20170525.Client;
+//import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+//import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+//import com.aliyun.teaopenapi.models.Config;
+//import com.aliyun.teautil.models.RuntimeOptions;
+//import lombok.extern.slf4j.Slf4j;
+//
+///**
+// * 阿里云短信发送工具类
+// * @author huolifu
+// * @date 2023/10/26 13:51
+// **/
+//@Slf4j
+//public class AliSmsUtil {
+//
+//    public static boolean sendAliSms(String accessKeyId, String accessKeySecret, String phone, String signName, String templateCode, String templateParam) {
+//        Config config = new Config()
+//                .setAccessKeyId(accessKeyId)
+//                .setAccessKeySecret(accessKeySecret);
+//        config.endpoint = "dysmsapi.aliyuncs.com";
+//
+//        SendSmsRequest sendSmsRequest = new SendSmsRequest()
+//                .setPhoneNumbers(phone)
+//                .setSignName(signName)
+//                .setTemplateCode(templateCode)
+//                .setTemplateParam(templateParam);
+//
+//        RuntimeOptions runtime = new RuntimeOptions();
+//        SendSmsResponse response;
+//        try {
+//            Client client = new Client(config);
+//            response = client.sendSmsWithOptions(sendSmsRequest, runtime);
+//            log.info("SEND ALI SMS RESULT:{}",response.getBody().getMessage());
+//        } catch (Exception e) {
+//            log.error("SEND SMS ERROR:{}",e.getMessage());
+//            throw new BizException("短信发送异常");
+//        }
+//        if("OK".equals(response.getBody().getMessage())){
+//            return true;
+//        }
+//        throw new BizException(response.getBody().getMessage());
+//    }
+//
+//}

+ 33 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/util/Base62Encoder.java

@@ -0,0 +1,33 @@
+package cn.hfln.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 62进制编码
+ * @author huolifu
+ * @date 2023/10/27 8:47
+ **/
+public class Base62Encoder {
+    private static final String CHARS = "oNWxUYwrXdCOIj4ck6M8RbiQa3H91pSmZTAh70zquLnKvt2VyEGlBsPJgDe5Ff";
+    private static final int SCALE = 62;
+    private static final int MIN_LENGTH = 5;
+
+    /**
+     * 数字转62进制
+     *
+     * @param num num
+     * @return String
+     */
+    public static String encode62(long num) {
+        StringBuilder builder = new StringBuilder();
+        int remainder;
+        while (num > SCALE - 1) {
+            remainder = Long.valueOf(num % SCALE).intValue();
+            builder.append(CHARS.charAt(remainder));
+            num = num / SCALE;
+        }
+        builder.append(CHARS.charAt(Long.valueOf(num).intValue()));
+        String value = builder.reverse().toString();
+        return StringUtils.leftPad(value, MIN_LENGTH, '0');
+    }
+}

+ 128 - 0
hfln-framework-common/src/main/java/cn/hfln/framework/common/util/WeChatUtil.java

@@ -0,0 +1,128 @@
+package cn.hfln.framework.common.util;
+
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信工具类
+ *
+ * @author huolifu
+ * @date 2023/10/30 17:11
+ **/
+@Slf4j
+public class WeChatUtil {
+
+    /**
+     * 小程序登录
+     *
+     * @param appid  小程序 appId
+     * @param secret 小程序 appSecret
+     * @param jsCode 登录时获取的 code,可通过wx.login获取
+     * @return
+     */
+    public static String getOpenid(String appid, String secret, String jsCode) {
+        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
+        String result = HttpUtil.get(String.format(url, appid, secret, jsCode));
+        JSONObject object = JSONObject.parseObject(result);
+        if (object.getIntValue("errcode") == 0) {
+            return object.getString("openid");
+        }
+        return "";
+    }
+
+
+    public static String getAccessToken(String appid, String secret) {
+        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
+        String result = HttpUtil.get(String.format(url, appid, secret));
+        JSONObject object = JSONObject.parseObject(result);
+
+        return object.getString("access_token");
+    }
+
+
+    public static String generate_urllink(String token, String jsonBody) {
+        String url = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";
+        String result = HttpUtil.post(String.format(url, token), jsonBody);
+        JSONObject object = JSONObject.parseObject(result);
+        if (object.getIntValue("errcode") == 0) {
+            return object.getString("url_link");
+        }
+        throw new RuntimeException("调用微信异常!");
+    }
+
+    public static String getPhoneNumber(String accessToken, String jsCode) {
+        try {
+            String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken + "";
+            Map<String, Object> params = new HashMap<>();
+            params.put("code", jsCode);
+            String jsonParams = JSONObject.toJSONString(params);
+            String result = HttpUtil.post(url, jsonParams);
+            JSONObject object = JSONObject.parseObject(result);
+            if (object.getIntValue("errcode") == 0) {
+                JSONObject o = object.getJSONObject("phone_info");
+                return o.getString("phoneNumber");
+            } else {
+                throw new RuntimeException(object.getIntValue("errcode") + "--" + object.getString("errmsg"));
+            }
+        } catch (Exception e) {
+            log.error("Failed to get phone number", e);
+        }
+        return null;
+    }
+
+    public static String getQrCode(String accessToken, String scene, String page,
+                                   Integer width,Object lineColor,Boolean autoColor,Boolean isHyaline) {
+        try {
+            String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
+            // 请求参数
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("scene", scene);
+            jsonObject.put("page", page);
+            jsonObject.put("width", width);
+            jsonObject.put("auto_color", autoColor);
+            jsonObject.put("line_color", lineColor);
+            jsonObject.put("is_hyaline", isHyaline);
+            jsonObject.put("check_path", false);
+            URL obj = new URL(url);
+            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
+            con.setRequestMethod("POST");
+            con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+            con.setDoOutput(true);
+            try (OutputStream os = con.getOutputStream()) {
+                byte[] input = jsonObject.toString().getBytes("utf-8");
+                os.write(input, 0, input.length);
+            }
+            int responseCode = con.getResponseCode();
+            if (responseCode == HttpURLConnection.HTTP_OK) {
+                try (InputStream in = con.getInputStream()) {
+                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len;
+                    while ((len = in.read(buffer)) != -1) {
+                        bos.write(buffer, 0, len);
+                    }
+                    // 使用Base64进行编码
+                    String base64EncodedString = Base64.getEncoder().encodeToString(bos.toByteArray());
+                    return base64EncodedString;
+                }
+            } else {
+                throw new Exception("微信二维码生成有误:" + responseCode);
+            }
+
+        } catch (Exception e) {
+            log.error("Failed to generate WeChat QR code", e);
+        }
+        return null;
+    }
+
+}

+ 3 - 0
hfln-framework-common/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.hfln.framework.common.redis.service.RedisService

+ 213 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/README.md

@@ -0,0 +1,213 @@
+# EMQX Spring Boot Starter
+
+## 简介
+EMQX Spring Boot Starter 是一个基于 Spring Boot 的 EMQX 客户端集成工具,提供注解驱动的 MQTT 消息监听、发布以及自定义消息处理等功能,帮助开发者快速集成 EMQX 消息中间件。通过该工具,开发者可以轻松实现 MQTT 消息的订阅、发布和处理,提升开发效率。
+
+## 功能特性
+- **注解驱动的 MQTT 消息监听**:通过 `@MqttListener` 注解,快速实现消息监听。
+- **注解驱动的 MQTT 消息发布**:通过 `MqttPublisher` 类,方便地发布消息。
+- **灵活的消息处理机制**:支持自定义消息处理器,满足复杂业务需求。
+- **支持异步处理**:消息处理支持异步执行,提高系统响应能力。
+- **支持消息重试**:内置消息重试机制,确保消息可靠传递。
+- **支持消息处理链**:通过消息处理链,实现消息的链式处理。
+
+## 快速开始
+
+### 依赖配置
+在项目的 pom.xml 中添加以下依赖:
+
+```xml
+<dependency>
+    <groupId>cn.hfln.framework</groupId>
+    <artifactId>emqx-spring-boot-starter</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+</dependency>
+```
+
+### EMQX 连接配置
+在 application.yml 中配置 EMQX 连接信息:
+
+```yaml
+emqx:
+  server-uris: tcp://localhost:1883
+  client-id: your-client-id
+  username: your-username
+  password: your-password
+  connection-timeout: 30
+  keep-alive-interval: 60
+```
+
+## 使用指南
+
+### 1. 使用 @MqttListener 注解监听消息
+在需要监听 MQTT 消息的方法上添加 `@MqttListener` 注解,指定要订阅的主题。
+
+示例代码:
+```java
+import cn.hfln.emqx.annotation.MqttListener;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class MyMqttListener {
+
+    @MqttListener(topic = "my/topic")
+    public void onMessage(String message) {
+        log.info("Received message: {}", message);
+    }
+}
+```
+
+### 接收通配符消息
+使用 `@MqttListener` 注解时,可以通过指定通配符主题来接收匹配的消息。例如,使用 `+` 表示单层通配符,使用 `#` 表示多层通配符。
+
+示例代码:
+```java
+import cn.hfln.emqx.annotation.MqttListener;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class WildcardMqttListener {
+
+    @MqttListener(topic = "my/+/topic")
+    public void onSingleLevelWildcard(String message) {
+        log.info("Received message from single level wildcard: {}", message);
+    }
+
+    @MqttListener(topic = "my/#")
+    public void onMultiLevelWildcard(String message) {
+        log.info("Received message from multi level wildcard: {}", message);
+    }
+}
+```
+
+### 2. 使用 MqttPublisher 发布消息
+通过注入 `MqttPublisher` 实例,调用其方法发布消息。
+
+示例代码:
+```java
+import cn.hfln.emqx.publisher.MqttPublisher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MyMqttService {
+
+    @Autowired
+    private MqttPublisher publisher;
+
+    public void sendMessage(String topic, String message) {
+        publisher.send(topic, message);
+    }
+
+    public void sendRetainedMessage(String topic, String message) {
+        publisher.sendRetained(topic, message);
+    }
+}
+```
+
+### 3. 自定义消息处理
+通过实现 `AbstractMessageHandler` 类,可以自定义消息处理逻辑。
+
+示例代码:
+```java
+import cn.hfln.emqx.handler.AbstractMessageHandler;
+import cn.hfln.emqx.context.MessageContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyCustomHandler extends AbstractMessageHandler {
+
+    @Override
+    public boolean handle(MessageContext context) {
+        // 自定义消息处理逻辑
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "my-custom-handler";
+    }
+}
+```
+
+### 4. 使用 MessageContext 处理消息
+`MessageContext` 提供了消息处理的上下文信息,包括消息内容、主题、QoS 等。通过 `MessageContext`,可以更灵活地处理消息。
+
+示例代码:
+```java
+import cn.hfln.emqx.annotation.MqttListener;
+import cn.hfln.emqx.context.MessageContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class MessageContextListener {
+
+    @MqttListener(topic = "my/topic")
+    public void onMessage(MessageContext context) {
+        String topic = context.getTopic();
+        String message = new String(context.getMessage().getPayload());
+        int qos = context.getMessage().getQos();
+        log.info("Received message from topic: {}, message: {}, QoS: {}", topic, message, qos);
+    }
+}
+```
+
+### 通配符主题使用说明
+
+MQTT 支持两种通配符:
+- `+` (单层通配符):匹配任意一个层级
+- `#` (多层通配符):匹配任意多个层级
+
+#### 通配符使用规则
+1. `+` 必须占据一个完整的层级,不能出现在层级中间
+   - ✅ `my/+/topic` - 匹配 `my/1/topic`, `my/2/topic` 等
+   - ❌ `my/t+pic` - 不合法
+
+2. `#` 必须是主题的最后一个字符
+   - ✅ `my/#` - 匹配 `my/1`, `my/1/2`, `my/1/2/3` 等
+   - ❌ `my/#/topic` - 不合法
+
+3. 通配符不能出现在主题的开头
+   - ❌ `+/my/topic` - 不合法
+   - ❌ `#/my/topic` - 不合法
+
+#### 示例代码
+```java
+@Slf4j
+@Component
+public class WildcardMqttListener {
+
+    // 匹配 my/1/topic, my/2/topic 等
+    @MqttListener(topic = "my/+/topic")
+    public void onSingleLevelWildcard(String message) {
+        log.info("Received message from single level wildcard: {}", message);
+    }
+
+    // 匹配 my/1, my/1/2, my/1/2/3 等
+    @MqttListener(topic = "my/#")
+    public void onMultiLevelWildcard(String message) {
+        log.info("Received message from multi level wildcard: {}", message);
+    }
+
+    // 匹配 device/+/status, device/+/data 等
+    @MqttListener(topic = "device/+/status")
+    public void onDeviceStatus(String message) {
+        log.info("Received device status: {}", message);
+    }
+}
+```
+
+#### 注意事项
+1. 通配符主题的订阅会消耗更多资源,建议谨慎使用
+2. 多层通配符 `#` 会匹配所有子主题,可能导致接收到不需要的消息
+3. 建议在发布消息时使用具体的主题,避免使用通配符
+4. 在使用通配符时,建议在日志中记录完整的主题信息,方便调试
+
+## 示例代码
+更多示例代码请参考 `

+ 70 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/pom.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.hfln.framework</groupId>
+        <artifactId>hfln-framework-design-starter</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>emqx-spring-boot-starter</artifactId>
+    <name>emqx-spring-boot-starter</name>
+    <description>EMQX Spring Boot Starter for HFLN Framework</description>
+
+    <dependencies>
+        <!-- Spring Boot Starter -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- Spring Boot Configuration Processor -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Spring Boot Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- Spring Boot AOP -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- Spring Boot Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- EMQX Java Client -->
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>1.2.5</version>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project> 

+ 83 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/annotation/MqttListener.java

@@ -0,0 +1,83 @@
+package cn.hfln.emqx.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * MQTT消息监听器注解
+ * 用于标记消息处理方法,支持灵活的消息订阅配置
+ * 
+ * 支持两种使用方式:
+ * 1. 使用MessageContext(推荐):
+ *    public void handleMessage(MessageContext context)
+ * 
+ * 2. 使用原始消息类型(向后兼容):
+ *    public void handleMessage(MqttMessage message)
+ *    public void handleMessage(String message)
+ *    public void handleMessage(byte[] payload)
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MqttListener {
+    /**
+     * 订阅的主题
+     * 支持通配符:
+     * - + 表示单层通配符
+     * - # 表示多层通配符
+     * 例如:
+     * - device/+/status
+     * - sensor/#
+     */
+    String[] topics();
+
+    /**
+     * 服务质量级别
+     * 0: 最多一次
+     * 1: 至少一次
+     * 2: 确保只有一次
+     */
+    int qos() default 1;
+
+    /**
+     * 是否使用共享订阅
+     * 共享订阅可以确保消息只被一个客户端处理
+     */
+    boolean shared() default false;
+
+    /**
+     * 共享订阅组名
+     * 当shared=true时生效
+     */
+    String sharedGroup() default "";
+
+    /**
+     * 消息处理超时时间(毫秒)
+     * 超过此时间未处理完成将记录警告日志
+     */
+    long timeout() default 5000;
+
+    /**
+     * 是否异步处理消息
+     * true: 异步处理,不阻塞消息接收
+     * false: 同步处理,阻塞消息接收
+     */
+    boolean async() default true;
+
+    /**
+     * 消息处理失败时的重试次数
+     * 0表示不重试
+     */
+    int retryTimes() default 0;
+
+    /**
+     * 重试间隔(毫秒)
+     */
+    long retryInterval() default 1000;
+
+    /**
+     * 是否使用高级特性
+     * true: 使用MessageContext,支持完整的消息处理生命周期
+     * false: 使用原始消息类型,向后兼容
+     */
+    boolean advanced() default true;
+} 

+ 84 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/annotation/MqttListenerAnnotationProcessor.java

@@ -0,0 +1,84 @@
+package cn.hfln.emqx.annotation;
+
+import cn.hfln.emqx.config.EmqxProperties;
+import cn.hfln.emqx.core.MqttClientManager;
+import cn.hfln.emqx.core.MqttMessageHandler;
+import cn.hfln.emqx.core.MqttMessageHandlerRegistry;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+@Component
+public class MqttListenerAnnotationProcessor implements BeanPostProcessor {
+
+    private final MqttClientManager mqttClientManager;
+    private final MqttMessageHandlerRegistry messageHandlerRegistry;
+    private final EmqxProperties emqxProperties;
+    private final AtomicInteger clientIdCounter = new AtomicInteger(0);
+
+    public MqttListenerAnnotationProcessor(MqttClientManager mqttClientManager,
+                                         MqttMessageHandlerRegistry messageHandlerRegistry,
+                                         EmqxProperties emqxProperties) {
+        this.mqttClientManager = mqttClientManager;
+        this.messageHandlerRegistry = messageHandlerRegistry;
+        this.emqxProperties = emqxProperties;
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        log.info("开始处理Bean: {}, 类型: {}", beanName, bean.getClass().getName());
+        
+        ReflectionUtils.doWithMethods(bean.getClass(), method -> {
+            MqttListener annotation = AnnotationUtils.findAnnotation(method, MqttListener.class);
+            if (annotation != null) {
+                log.info("发现MqttListener注解 - Bean: {}, 方法: {}, 注解: {}", 
+                    beanName, method.getName(), annotation);
+                
+                String[] topics = annotation.topics();
+                log.info("订阅主题: {}", Arrays.toString(topics));
+                
+                int qos = annotation.qos();
+                log.info("QoS级别: {}", qos);
+                
+                String clientId = generateClientId(beanName, method.getName());
+                log.info("生成的客户端ID: {}", clientId);
+                
+                try {
+                    MqttMessageHandler handler = new MqttMessageHandler(bean, method);
+                    log.info("创建消息处理器 - Bean: {}, 方法: {}", beanName, method.getName());
+                    
+                    messageHandlerRegistry.registerHandler(clientId, handler);
+                    log.info("注册消息处理器 - 客户端ID: {}", clientId);
+                    
+                    mqttClientManager.createClient(clientId, topics, qos);
+                    log.info("创建MQTT客户端 - 客户端ID: {}, 主题: {}, QoS: {}", 
+                        clientId, Arrays.toString(topics), qos);
+                } catch (Exception e) {
+                    log.error("处理MqttListener注解时发生错误 - Bean: {}, 方法: {}", 
+                        beanName, method.getName(), e);
+                    throw new RuntimeException("Failed to process MqttListener annotation", e);
+                }
+            }
+        });
+        
+        log.info("完成处理Bean: {}", beanName);
+        return bean;
+    }
+
+    private String generateClientId(String beanName, String methodName) {
+        String clientId = String.format("%s-%s-%d", 
+            emqxProperties.getClientIdPrefix(), 
+            beanName + "-" + methodName,
+            clientIdCounter.incrementAndGet());
+        log.debug("生成客户端ID: {}", clientId);
+        return clientId;
+    }
+} 

+ 45 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/autoconfigure/EmqxAutoConfiguration.java

@@ -0,0 +1,45 @@
+package cn.hfln.emqx.autoconfigure;
+
+import cn.hfln.emqx.connector.EmqxConnector;
+import cn.hfln.emqx.handler.EmqxMessageHandler;
+import cn.hfln.emqx.handler.MessageHandlerRegistry;
+import cn.hfln.emqx.properties.EmqxProperties;
+import cn.hfln.emqx.publisher.MqttPublisher;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * EMQX自动配置类
+ */
+@Configuration
+@EnableConfigurationProperties(EmqxProperties.class)
+@ConditionalOnProperty(prefix = "emqx", name = "enabled", havingValue = "true", matchIfMissing = true)
+public class EmqxAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public EmqxConnector emqxConnector(EmqxProperties properties) {
+        return new EmqxConnector(properties);
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MessageHandlerRegistry messageHandlerRegistry() {
+        return new MessageHandlerRegistry();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public EmqxMessageHandler emqxMessageHandler(EmqxConnector connector, MessageHandlerRegistry registry) {
+        return new EmqxMessageHandler(connector, registry);
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MqttPublisher mqttPublisher(EmqxConnector connector) {
+        return new MqttPublisher(connector);
+    }
+} 

+ 50 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/autoconfigure/MessageHandlerAutoConfiguration.java

@@ -0,0 +1,50 @@
+package cn.hfln.emqx.autoconfigure;
+
+import cn.hfln.emqx.handler.MessageHandler;
+import cn.hfln.emqx.handler.MessageHandlerChain;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 消息处理器自动配置
+ */
+@Slf4j
+@Configuration
+public class MessageHandlerAutoConfiguration {
+    
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    /**
+     * 创建消息处理链
+     *
+     * @return 消息处理链
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public MessageHandlerChain messageHandlerChain() {
+        MessageHandlerChain chain = new MessageHandlerChain();
+        
+        // 从ApplicationContext中获取所有MessageHandler类型的Bean
+        Map<String, MessageHandler> handlerMap = applicationContext.getBeansOfType(MessageHandler.class);
+        if (!handlerMap.isEmpty()) {
+            List<MessageHandler> handlers = new ArrayList<>(handlerMap.values());
+            for (MessageHandler handler : handlers) {
+                chain.addHandler(handler);
+                log.info("Added message handler: {}", handler.getName());
+            }
+        } else {
+            log.warn("No MessageHandler beans found in application context");
+        }
+        
+        return chain;
+    }
+} 

+ 314 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/config/EmqxConfig.java

@@ -0,0 +1,314 @@
+package cn.hfln.emqx.config;
+
+import cn.hfln.emqx.constant.EmqxConstants;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * EMQX配置类
+ */
+@Data
+@Validated
+@ConfigurationProperties(prefix = EmqxConstants.CONFIG_PREFIX)
+public class EmqxConfig {
+    /**
+     * 服务器配置
+     */
+    @NotNull
+    private Server server = new Server();
+
+    /**
+     * 发布者配置
+     */
+    @NotNull
+    private Publisher publisher = new Publisher();
+
+    /**
+     * 订阅者配置
+     */
+    @NotNull
+    private Subscriber subscriber = new Subscriber();
+
+    /**
+     * 监控配置
+     */
+    @NotNull
+    private Management management = new Management();
+
+    /**
+     * 服务器配置
+     */
+    @Data
+    public static class Server {
+        /**
+         * 服务器地址列表
+         */
+        @NotBlank
+        private String urls = EmqxConstants.DEFAULT_SERVER_URL;
+
+        /**
+         * 用户名
+         */
+        private String username;
+
+        /**
+         * 密码
+         */
+        private String password;
+
+        /**
+         * 客户端ID
+         */
+        private String clientId;
+
+        /**
+         * 是否清除会话
+         */
+        private boolean cleanSession = true;
+
+        /**
+         * 连接超时时间(秒)
+         */
+        @Min(1)
+        private int connectionTimeout = EmqxConstants.DEFAULT_CONNECTION_TIMEOUT;
+
+        /**
+         * 保活间隔(秒)
+         */
+        @Min(1)
+        private int keepAliveInterval = EmqxConstants.DEFAULT_KEEP_ALIVE_INTERVAL;
+
+        /**
+         * 最大重连延迟(毫秒)
+         */
+        @Min(1)
+        private int maxReconnectDelay = EmqxConstants.DEFAULT_MAX_RECONNECT_DELAY;
+
+        /**
+         * 是否自动重连
+         */
+        private boolean automaticReconnect = true;
+
+        /**
+         * 最大飞行消息数
+         */
+        @Min(1)
+        private int maxInflight = EmqxConstants.DEFAULT_MAX_INFLIGHT;
+
+        /**
+         * 完成超时时间(毫秒)
+         */
+        @Min(1)
+        private int completionTimeout = EmqxConstants.DEFAULT_COMPLETION_TIMEOUT;
+
+        /**
+         * 线程池大小
+         */
+        @Min(1)
+        private int executorServiceSize = EmqxConstants.DEFAULT_EXECUTOR_SERVICE_SIZE;
+
+        /**
+         * SSL配置
+         */
+        private Ssl ssl = new Ssl();
+    }
+
+    /**
+     * SSL配置
+     */
+    @Data
+    public static class Ssl {
+        /**
+         * 是否启用SSL
+         */
+        private boolean enabled = false;
+
+        /**
+         * SSL协议
+         */
+        private String protocol = "TLS";
+
+        /**
+         * CA证书路径
+         */
+        private String caCert;
+
+        /**
+         * 客户端证书路径
+         */
+        private String clientCert;
+
+        /**
+         * 客户端密钥路径
+         */
+        private String clientKey;
+
+        /**
+         * 密钥密码
+         */
+        private String password;
+    }
+
+    /**
+     * 发布者配置
+     */
+    @Data
+    public static class Publisher {
+        /**
+         * 默认QoS级别
+         */
+        @Min(0)
+        private int defaultQos = EmqxConstants.DEFAULT_QOS;
+
+        /**
+         * 是否默认保留
+         */
+        private boolean defaultRetained = false;
+
+        /**
+         * 是否异步发布
+         */
+        private boolean async = true;
+
+        /**
+         * 缓冲区大小
+         */
+        @Min(1)
+        private int bufferSize = EmqxConstants.DEFAULT_BUFFER_SIZE;
+
+        /**
+         * 批处理大小
+         */
+        @Min(1)
+        private int batchSize = EmqxConstants.DEFAULT_BATCH_SIZE;
+
+        /**
+         * 批处理间隔(毫秒)
+         */
+        @Min(1)
+        private int batchInterval = EmqxConstants.DEFAULT_BATCH_INTERVAL;
+    }
+
+    /**
+     * 订阅者配置
+     */
+    @Data
+    public static class Subscriber {
+        /**
+         * 默认QoS级别
+         */
+        @Min(0)
+        private int defaultQos = EmqxConstants.DEFAULT_QOS;
+
+        /**
+         * 是否共享订阅
+         */
+        private boolean sharedSubscription = false;
+
+        /**
+         * 共享组名称
+         */
+        private String sharedGroup;
+
+        /**
+         * 消息处理器配置
+         */
+        private MessageHandler messageHandler = new MessageHandler();
+    }
+
+    /**
+     * 消息处理器配置
+     */
+    @Data
+    public static class MessageHandler {
+        /**
+         * 超时时间(毫秒)
+         */
+        @Min(1)
+        private int timeout = EmqxConstants.DEFAULT_MESSAGE_HANDLER_TIMEOUT;
+
+        /**
+         * 重试次数
+         */
+        @Min(0)
+        private int retryTimes = EmqxConstants.DEFAULT_RETRY_TIMES;
+
+        /**
+         * 重试间隔(毫秒)
+         */
+        @Min(1)
+        private int retryInterval = EmqxConstants.DEFAULT_RETRY_INTERVAL;
+    }
+
+    /**
+     * 监控配置
+     */
+    @Data
+    public static class Management {
+        /**
+         * 指标配置
+         */
+        private Metrics metrics = new Metrics();
+
+        /**
+         * 端点配置
+         */
+        private Endpoints endpoints = new Endpoints();
+    }
+
+    /**
+     * 指标配置
+     */
+    @Data
+    public static class Metrics {
+        /**
+         * 是否启用指标
+         */
+        private boolean enabled = true;
+
+        /**
+         * 指标前缀
+         */
+        private String prefix = "emqx";
+    }
+
+    /**
+     * 端点配置
+     */
+    @Data
+    public static class Endpoints {
+        /**
+         * Web端点配置
+         */
+        private Web web = new Web();
+    }
+
+    /**
+     * Web端点配置
+     */
+    @Data
+    public static class Web {
+        /**
+         * 暴露的端点
+         */
+        private Exposure exposure = new Exposure();
+    }
+
+    /**
+     * 暴露配置
+     */
+    @Data
+    public static class Exposure {
+        /**
+         * 包含的端点
+         */
+        private List<String> include = Arrays.asList("health", "metrics", "info");
+    }
+} 

+ 41 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/ConnectionState.java

@@ -0,0 +1,41 @@
+package cn.hfln.emqx.connector;
+
+/**
+ * MQTT连接状态
+ */
+public enum ConnectionState {
+    /**
+     * 初始状态
+     */
+    INITIAL,
+    
+    /**
+     * 正在连接
+     */
+    CONNECTING,
+    
+    /**
+     * 已连接
+     */
+    CONNECTED,
+    
+    /**
+     * 正在断开连接
+     */
+    DISCONNECTING,
+    
+    /**
+     * 已断开连接
+     */
+    DISCONNECTED,
+    
+    /**
+     * 连接失败
+     */
+    FAILED,
+    
+    /**
+     * 正在重连
+     */
+    RECONNECTING
+} 

+ 448 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/EmqxConnector.java

@@ -0,0 +1,448 @@
+package cn.hfln.emqx.connector;
+
+import cn.hfln.emqx.handler.EmqxMessageHandler;
+import cn.hfln.emqx.handler.MessageHandler;
+import cn.hfln.emqx.properties.EmqxProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * EMQX连接器
+ * 提供MQTT连接管理、消息发布订阅等功能
+ * 特点:
+ * 1. 使用状态机管理连接状态
+ * 2. 线程安全的连接管理
+ * 3. 自定义指数退避重连策略
+ * 4. 异步消息处理
+ * 5. 完善的资源管理
+ */
+@Slf4j
+@Component
+public class EmqxConnector implements MqttCallbackExtended {
+    /**
+     * EMQX配置属性
+     */
+    private final EmqxProperties properties;
+
+    /**
+     * 重连策略
+     */
+    private final ReconnectStrategy reconnectStrategy;
+
+    /**
+     * 消息处理线程池
+     * 核心线程数:2
+     * 最大线程数:4
+     * 队列容量:1000
+     * 线程存活时间:60秒
+     */
+    private final ExecutorService messageExecutor;
+
+    /**
+     * 连接操作锁
+     * 用于保证连接和断开操作的互斥性
+     */
+    private final ReentrantLock connectionLock;
+
+    /**
+     * 连接状态
+     * 使用原子引用保证状态变更的原子性
+     */
+    private final AtomicReference<ConnectionState> state;
+
+    /**
+     * 重连尝试次数
+     * 用于计算重连延迟
+     */
+    private final AtomicInteger reconnectAttempts;
+
+    /**
+     * MQTT客户端
+     * 使用volatile保证多线程可见性
+     */
+    private volatile MqttAsyncClient client;
+
+    /**
+     * 消息处理器
+     * 用于处理接收到的消息
+     */
+    private volatile EmqxMessageHandler messageHandler;
+
+    /**
+     * 重连调度器
+     * 用于调度重连任务
+     */
+    private volatile ScheduledExecutorService reconnectScheduler;
+
+    /**
+     * 重连任务
+     * 用于取消已调度的重连
+     */
+    private volatile ScheduledFuture<?> reconnectFuture;
+
+    /**
+     * 构造函数
+     * 初始化连接器组件
+     *
+     * @param properties EMQX配置属性
+     */
+    @Autowired
+    public EmqxConnector(EmqxProperties properties) {
+        this.properties = properties;
+        this.reconnectStrategy = ReconnectStrategy.defaultStrategy();
+        this.messageExecutor = new ThreadPoolExecutor(
+                2, 4, 60L, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(1000),
+                new ThreadFactory() {
+                    private final AtomicInteger counter = new AtomicInteger(1);
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        Thread thread = new Thread(r, "mqtt-message-handler-" + counter.getAndIncrement());
+                        thread.setDaemon(true);
+                        return thread;
+                    }
+                },
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+        this.connectionLock = new ReentrantLock();
+        this.state = new AtomicReference<>(ConnectionState.INITIAL);
+        this.reconnectAttempts = new AtomicInteger(0);
+    }
+
+    /**
+     * 初始化连接器
+     * 在Spring容器启动时自动调用
+     */
+    @PostConstruct
+    public void init() {
+        try {
+            connect();
+        } catch (Exception e) {
+            log.error("Failed to initialize EMQX connector", e);
+            state.set(ConnectionState.FAILED);
+        }
+    }
+
+    /**
+     * 销毁连接器
+     * 在Spring容器关闭时自动调用
+     */
+    @PreDestroy
+    public void destroy() {
+        disconnect();
+        shutdown();
+    }
+
+    /**
+     * 连接到EMQX服务器
+     * 使用状态机和锁保证线程安全
+     *
+     * @throws MqttException 连接失败时抛出
+     */
+    public void connect() throws MqttException {
+        // 检查当前状态是否允许连接
+        if (!state.compareAndSet(ConnectionState.INITIAL, ConnectionState.CONNECTING) &&
+            !state.compareAndSet(ConnectionState.DISCONNECTED, ConnectionState.CONNECTING) &&
+            !state.compareAndSet(ConnectionState.FAILED, ConnectionState.CONNECTING)) {
+            log.warn("Cannot connect in current state: {}", state.get());
+            return;
+        }
+
+        connectionLock.lock();
+        try {
+            // 检查是否已经连接
+            if (client != null && client.isConnected()) {
+                state.set(ConnectionState.CONNECTED);
+                return;
+            }
+
+            // 创建连接选项并连接
+            MqttConnectOptions options = createConnectOptions();
+            client = new MqttAsyncClient(
+                    properties.getServerUris()[0],
+                    properties.getClientId(),
+                    new MemoryPersistence()
+            );
+            client.setCallback(this);
+            client.connect(options).waitForCompletion();
+            state.set(ConnectionState.CONNECTED);
+            reconnectAttempts.set(0);
+            log.info("Connected to EMQX server: {}", properties.getServerUris()[0]);
+        } catch (MqttException e) {
+            state.set(ConnectionState.FAILED);
+            log.error("Failed to connect to EMQX server", e);
+            throw e;
+        } finally {
+            connectionLock.unlock();
+        }
+    }
+
+    /**
+     * 断开与EMQX服务器的连接
+     * 使用状态机和锁保证线程安全
+     */
+    public void disconnect() {
+        if (!state.compareAndSet(ConnectionState.CONNECTED, ConnectionState.DISCONNECTING)) {
+            return;
+        }
+
+        connectionLock.lock();
+        try {
+            if (client != null && client.isConnected()) {
+                client.disconnect().waitForCompletion();
+                log.info("Disconnected from EMQX server");
+            }
+        } catch (MqttException e) {
+            log.error("Error disconnecting from EMQX server", e);
+        } finally {
+            state.set(ConnectionState.DISCONNECTED);
+            connectionLock.unlock();
+        }
+    }
+
+    /**
+     * 发布消息到指定主题
+     *
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param qos      服务质量
+     * @param retained 是否保留
+     * @throws MqttException 发布失败时抛出
+     */
+    public void publish(String topic, byte[] payload, int qos, boolean retained) throws MqttException {
+        if (state.get() != ConnectionState.CONNECTED) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        MqttMessage message = new MqttMessage(payload);
+        message.setQos(qos);
+        message.setRetained(retained);
+        client.publish(topic, message).waitForCompletion();
+    }
+
+    /**
+     * 订阅主题
+     *
+     * @param topic 主题
+     * @param qos   服务质量
+     * @throws MqttException 订阅失败时抛出
+     */
+    public void subscribe(String topic, int qos) throws MqttException {
+        if (state.get() != ConnectionState.CONNECTED) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        client.subscribe(topic, qos).waitForCompletion();
+        log.info("Subscribed to topic: {}", topic);
+    }
+
+    /**
+     * 取消订阅主题
+     *
+     * @param topic 主题
+     * @throws MqttException 取消订阅失败时抛出
+     */
+    public void unsubscribe(String topic) throws MqttException {
+        if (state.get() != ConnectionState.CONNECTED) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        client.unsubscribe(topic).waitForCompletion();
+        log.info("Unsubscribed from topic: {}", topic);
+    }
+
+    /**
+     * 设置消息处理器
+     *
+     * @param messageHandler 消息处理器
+     */
+    public void setCallback(EmqxMessageHandler messageHandler) {
+        this.messageHandler = messageHandler;
+    }
+
+    /**
+     * 连接完成回调
+     * 在连接成功或重连成功时调用
+     *
+     * @param reconnect 是否是重连
+     * @param serverURI 服务器地址
+     */
+    @Override
+    public void connectComplete(boolean reconnect, String serverURI) {
+        log.info("EMQX {}connected to {}", reconnect ? "re-" : "", serverURI);
+        state.set(ConnectionState.CONNECTED);
+        reconnectAttempts.set(0);
+        if (reconnectFuture != null) {
+            reconnectFuture.cancel(false);
+        }
+    }
+
+    /**
+     * 连接丢失回调
+     * 在连接断开时调用,触发重连机制
+     *
+     * @param cause 断开原因
+     */
+    @Override
+    public void connectionLost(Throwable cause) {
+        log.warn("Connection to EMQX server lost", cause);
+        state.set(ConnectionState.DISCONNECTED);
+        scheduleReconnect();
+    }
+
+    /**
+     * 消息到达回调
+     * 异步处理接收到的消息
+     *
+     * @param topic   主题
+     * @param message 消息
+     */
+    @Override
+    public void messageArrived(String topic, MqttMessage message) {
+        log.info("[EmqxConnector] messageArrived: topic={}, payload={}", topic, new String(message.getPayload()));
+        if (messageHandler != null) {
+            messageExecutor.submit(() -> {
+                try {
+                    messageHandler.messageArrived(topic, message);
+                } catch (Exception e) {
+                    log.error("Error processing message for topic {}: {}", topic, e.getMessage());
+                }
+            });
+        }
+    }
+
+    /**
+     * 消息发送完成回调
+     *
+     * @param token 发送令牌
+     */
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+        log.debug("Message delivery complete");
+    }
+
+    /**
+     * 创建连接选项
+     * 配置连接参数和遗嘱消息
+     *
+     * @return 连接选项
+     */
+    private MqttConnectOptions createConnectOptions() {
+        MqttConnectOptions options = new MqttConnectOptions();
+        options.setServerURIs(properties.getServerUris());
+        options.setUserName(properties.getUsername());
+        options.setPassword(properties.getPassword().toCharArray());
+        options.setConnectionTimeout(properties.getConnectionTimeout());
+        options.setKeepAliveInterval(properties.getKeepAliveInterval());
+        options.setAutomaticReconnect(true);
+        options.setMaxReconnectDelay(properties.getMaxReconnectDelay());
+        options.setCleanSession(properties.isCleanSession());
+        options.setMaxInflight(properties.getMaxInflight());
+        options.setMqttVersion(properties.getMqttVersion());
+
+        // 设置遗嘱消息,用于检测异常断开
+        options.setWill("$SYS/connection/lost", "Connection lost".getBytes(), 1, true);
+        
+        return options;
+    }
+
+    /**
+     * 安排重连
+     * 使用指数退避算法计算重连延迟
+     */
+    private void scheduleReconnect() {
+        if (reconnectScheduler == null) {
+            reconnectScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
+                Thread thread = new Thread(r, "mqtt-reconnect-scheduler");
+                thread.setDaemon(true);
+                return thread;
+            });
+        }
+
+        int attempts = reconnectAttempts.incrementAndGet();
+        long delay = reconnectStrategy.calculateDelay(attempts);
+        
+        if (reconnectFuture != null) {
+            reconnectFuture.cancel(false);
+        }
+
+        reconnectFuture = reconnectScheduler.schedule(() -> {
+            if (state.get() == ConnectionState.DISCONNECTED) {
+                state.set(ConnectionState.RECONNECTING);
+                try {
+                    connect();
+                } catch (MqttException e) {
+                    log.error("Failed to reconnect to EMQX server", e);
+                    state.set(ConnectionState.FAILED);
+                }
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+
+        log.info("Scheduled reconnect attempt #{} in {}ms", attempts, delay);
+    }
+
+    /**
+     * 关闭资源
+     * 清理线程池和客户端资源
+     */
+    private void shutdown() {
+        // 关闭重连调度器
+        if (reconnectScheduler != null) {
+            reconnectScheduler.shutdown();
+            try {
+                if (!reconnectScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
+                    reconnectScheduler.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                reconnectScheduler.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        // 关闭消息处理线程池
+        messageExecutor.shutdown();
+        try {
+            if (!messageExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
+                messageExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            messageExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+
+        // 关闭MQTT客户端
+        if (client != null) {
+            try {
+                client.close();
+            } catch (MqttException e) {
+                log.error("Error closing MQTT client", e);
+            }
+        }
+    }
+
+    /**
+     * 获取当前连接状态
+     */
+    public ConnectionState getState() {
+        return state.get();
+    }
+
+    // 新增:重载的subscribe方法,支持注册MessageHandler
+    public void subscribe(String topic, int qos, MessageHandler handler) throws MqttException {
+        if (this.messageHandler != null && this.messageHandler instanceof cn.hfln.emqx.handler.EmqxMessageHandler) {
+            cn.hfln.emqx.handler.EmqxMessageHandler emqxHandler = (cn.hfln.emqx.handler.EmqxMessageHandler) this.messageHandler;
+            emqxHandler.getRegistry().addHandler(topic, qos, handler);
+        }
+        subscribe(topic, qos);
+    }
+}

+ 67 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/connector/ReconnectStrategy.java

@@ -0,0 +1,67 @@
+package cn.hfln.emqx.connector;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 重连策略
+ */
+@Data
+@Builder
+public class ReconnectStrategy {
+    private static final Random RANDOM = new Random();
+    
+    /**
+     * 初始重连延迟(毫秒)
+     */
+    private final long initialDelay;
+    
+    /**
+     * 最大重连延迟(毫秒)
+     */
+    private final long maxDelay;
+    
+    /**
+     * 最大重连次数
+     */
+    private final int maxAttempts;
+    
+    /**
+     * 重连冷却时间(毫秒)
+     */
+    private final long cooldownTime;
+    
+    /**
+     * 计算下一次重连延迟
+     *
+     * @param attempt 当前重连次数
+     * @return 重连延迟(毫秒)
+     */
+    public long calculateDelay(int attempt) {
+        if (attempt >= maxAttempts) {
+            return cooldownTime;
+        }
+        
+        // 指数退避算法
+        long delay = Math.min(initialDelay * (1L << attempt), maxDelay);
+        
+        // 添加随机抖动,避免多个客户端同时重连
+        long jitter = (long) (delay * 0.1 * RANDOM.nextDouble());
+        return delay + jitter;
+    }
+    
+    /**
+     * 默认重连策略
+     */
+    public static ReconnectStrategy defaultStrategy() {
+        return ReconnectStrategy.builder()
+                .initialDelay(1000)
+                .maxDelay(30000)
+                .maxAttempts(10)
+                .cooldownTime(60000)
+                .build();
+    }
+} 

+ 90 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/constant/EmqxConstants.java

@@ -0,0 +1,90 @@
+package cn.hfln.emqx.constant;
+
+/**
+ * EMQX常量类
+ */
+public final class EmqxConstants {
+    private EmqxConstants() {
+        throw new IllegalStateException("Constant class");
+    }
+
+    /**
+     * 默认配置前缀
+     */
+    public static final String CONFIG_PREFIX = "emqx";
+
+    /**
+     * 默认服务器地址
+     */
+    public static final String DEFAULT_SERVER_URL = "tcp://localhost:1883";
+
+    /**
+     * 默认客户端ID前缀
+     */
+    public static final String DEFAULT_CLIENT_ID_PREFIX = "emqx-client";
+
+    /**
+     * 默认连接超时时间(秒)
+     */
+    public static final int DEFAULT_CONNECTION_TIMEOUT = 30;
+
+    /**
+     * 默认保活间隔(秒)
+     */
+    public static final int DEFAULT_KEEP_ALIVE_INTERVAL = 60;
+
+    /**
+     * 默认最大重连延迟(毫秒)
+     */
+    public static final int DEFAULT_MAX_RECONNECT_DELAY = 1000;
+
+    /**
+     * 默认最大飞行消息数
+     */
+    public static final int DEFAULT_MAX_INFLIGHT = 1000;
+
+    /**
+     * 默认完成超时时间(毫秒)
+     */
+    public static final int DEFAULT_COMPLETION_TIMEOUT = 3000;
+
+    /**
+     * 默认线程池大小
+     */
+    public static final int DEFAULT_EXECUTOR_SERVICE_SIZE = 10;
+
+    /**
+     * 默认QoS级别
+     */
+    public static final int DEFAULT_QOS = 1;
+
+    /**
+     * 默认消息处理超时时间(毫秒)
+     */
+    public static final int DEFAULT_MESSAGE_HANDLER_TIMEOUT = 5000;
+
+    /**
+     * 默认重试次数
+     */
+    public static final int DEFAULT_RETRY_TIMES = 3;
+
+    /**
+     * 默认重试间隔(毫秒)
+     */
+    public static final int DEFAULT_RETRY_INTERVAL = 1000;
+
+    /**
+     * 默认批处理大小
+     */
+    public static final int DEFAULT_BATCH_SIZE = 100;
+
+    /**
+     * 默认批处理间隔(毫秒)
+     */
+    public static final int DEFAULT_BATCH_INTERVAL = 1000;
+
+    /**
+     * 默认缓冲区大小
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 1000;
+} 

+ 138 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/context/MessageContext.java

@@ -0,0 +1,138 @@
+package cn.hfln.emqx.context;
+
+import lombok.Data;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 消息上下文
+ * 封装消息处理过程中的所有相关信息
+ */
+@Data
+public class MessageContext {
+    /**
+     * 消息ID
+     */
+    private String messageId;
+
+    /**
+     * 消息主题
+     */
+    private String topic;
+
+    /**
+     * 原始消息
+     */
+    private MqttMessage message;
+
+    /**
+     * 消息接收时间
+     */
+    private LocalDateTime receiveTime;
+
+    /**
+     * 消息处理开始时间
+     */
+    private LocalDateTime processStartTime;
+
+    /**
+     * 消息处理结束时间
+     */
+    private LocalDateTime processEndTime;
+
+    /**
+     * 消息处理状态
+     */
+    private ProcessStatus status;
+
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 重试次数
+     */
+    private int retryCount;
+
+    /**
+     * 扩展属性
+     */
+    private Map<String, Object> attributes = new HashMap<>();
+
+    /**
+     * 消息处理状态枚举
+     */
+    public enum ProcessStatus {
+        /**
+         * 待处理
+         */
+        PENDING,
+        
+        /**
+         * 处理中
+         */
+        PROCESSING,
+        
+        /**
+         * 处理成功
+         */
+        SUCCESS,
+        
+        /**
+         * 处理失败
+         */
+        FAILED
+    }
+
+    /**
+     * 获取消息处理耗时(毫秒)
+     */
+    public long getProcessTime() {
+        if (processStartTime == null || processEndTime == null) {
+            return 0;
+        }
+        return java.time.Duration.between(processStartTime, processEndTime).toMillis();
+    }
+
+    /**
+     * 获取消息等待时间(毫秒)
+     */
+    public long getWaitTime() {
+        if (receiveTime == null || processStartTime == null) {
+            return 0;
+        }
+        return java.time.Duration.between(receiveTime, processStartTime).toMillis();
+    }
+
+    /**
+     * 设置扩展属性
+     */
+    public void setAttribute(String key, Object value) {
+        attributes.put(key, value);
+    }
+
+    /**
+     * 获取扩展属性
+     */
+    public Object getAttribute(String key) {
+        return attributes.get(key);
+    }
+
+    /**
+     * 移除扩展属性
+     */
+    public Object removeAttribute(String key) {
+        return attributes.remove(key);
+    }
+
+    /**
+     * 清空扩展属性
+     */
+    public void clearAttributes() {
+        attributes.clear();
+    }
+} 

+ 114 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttClientManager.java

@@ -0,0 +1,114 @@
+package cn.hfln.emqx.core;
+
+import cn.hfln.emqx.config.EmqxProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+public class MqttClientManager {
+    private final Map<String, MqttClient> clients = new ConcurrentHashMap<>();
+    private final EmqxProperties emqxProperties;
+    private final MqttMessageHandlerRegistry messageHandlerRegistry;
+
+    public MqttClientManager(EmqxProperties emqxProperties, MqttMessageHandlerRegistry messageHandlerRegistry) {
+        this.emqxProperties = emqxProperties;
+        this.messageHandlerRegistry = messageHandlerRegistry;
+        log.info("初始化MqttClientManager - 服务器地址: {}", emqxProperties.getServerUri());
+    }
+
+    public void createClient(String clientId, String[] topics, int qos) {
+        log.info("开始创建MQTT客户端 - 客户端ID: {}, 主题: {}", clientId, String.join(",", topics));
+        
+        try {
+            MqttClient client = new MqttClient(emqxProperties.getServerUri(), clientId);
+            log.debug("创建MQTT客户端实例 - 客户端ID: {}", clientId);
+            
+            MqttConnectOptions options = new MqttConnectOptions();
+            options.setServerURIs(new String[]{emqxProperties.getServerUri()});
+            options.setUserName(emqxProperties.getUsername());
+            options.setPassword(emqxProperties.getPassword().toCharArray());
+            options.setConnectionTimeout(emqxProperties.getConnectionTimeout());
+            options.setKeepAliveInterval(emqxProperties.getKeepAliveInterval());
+            options.setAutomaticReconnect(true);
+            log.debug("配置MQTT连接选项 - 客户端ID: {}, 用户名: {}", clientId, emqxProperties.getUsername());
+            
+            client.setCallback(new MqttCallback() {
+                @Override
+                public void connectionLost(Throwable cause) {
+                    log.warn("MQTT连接丢失 - 客户端ID: {}", clientId, cause);
+                }
+
+                @Override
+                public void messageArrived(String topic, MqttMessage message) {
+                    log.debug("收到MQTT消息 - 客户端ID: {}, 主题: {}, 消息: {}", 
+                        clientId, topic, new String(message.getPayload()));
+                    messageHandlerRegistry.handleMessage(clientId, topic, message);
+                }
+
+                @Override
+                public void deliveryComplete(IMqttDeliveryToken token) {
+                    log.debug("消息发送完成 - 客户端ID: {}", clientId);
+                }
+            });
+            log.debug("设置MQTT回调处理器 - 客户端ID: {}", clientId);
+            
+            client.connect(options);
+            log.info("MQTT客户端连接成功 - 客户端ID: {}", clientId);
+            
+            for (String topic : topics) {
+                client.subscribe(topic, qos);
+                log.info("订阅主题成功 - 客户端ID: {}, 主题: {}, QoS: {}", clientId, topic, qos);
+            }
+            
+            clients.put(clientId, client);
+            log.info("MQTT客户端创建完成并添加到管理器 - 客户端ID: {}", clientId);
+            
+        } catch (MqttException e) {
+            log.error("创建MQTT客户端失败 - 客户端ID: {}", clientId, e);
+            throw new RuntimeException("Failed to create MQTT client", e);
+        }
+    }
+
+    public void disconnect(String clientId) {
+        log.info("开始断开MQTT客户端连接 - 客户端ID: {}", clientId);
+        MqttClient client = clients.remove(clientId);
+        if (client != null) {
+            try {
+                if (client.isConnected()) {
+                    client.disconnect();
+                    log.info("MQTT客户端断开连接成功 - 客户端ID: {}", clientId);
+                }
+                client.close();
+                log.info("MQTT客户端关闭成功 - 客户端ID: {}", clientId);
+            } catch (MqttException e) {
+                log.error("断开MQTT客户端连接失败 - 客户端ID: {}", clientId, e);
+                throw new RuntimeException("Failed to disconnect MQTT client", e);
+            }
+        } else {
+            log.warn("未找到要断开的MQTT客户端 - 客户端ID: {}", clientId);
+        }
+    }
+
+    public void disconnectAll() {
+        log.info("开始断开所有MQTT客户端连接");
+        clients.forEach((clientId, client) -> {
+            try {
+                if (client.isConnected()) {
+                    client.disconnect();
+                    log.info("断开MQTT客户端连接成功 - 客户端ID: {}", clientId);
+                }
+                client.close();
+                log.info("关闭MQTT客户端成功 - 客户端ID: {}", clientId);
+            } catch (MqttException e) {
+                log.error("断开MQTT客户端连接失败 - 客户端ID: {}", clientId, e);
+            }
+        });
+        clients.clear();
+        log.info("所有MQTT客户端已断开连接并清理");
+    }
+} 

+ 36 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttMessageHandler.java

@@ -0,0 +1,36 @@
+package cn.hfln.emqx.core;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+
+@Slf4j
+public class MqttMessageHandler {
+    private final Object target;
+    private final Method method;
+
+    public MqttMessageHandler(Object target, Method method) {
+        this.target = target;
+        this.method = method;
+        log.debug("创建MQTT消息处理器 - 目标对象: {}, 方法: {}", target.getClass().getName(), method.getName());
+    }
+
+    public void handleMessage(String topic, MqttMessage message) {
+        log.debug("处理MQTT消息 - 主题: {}, 消息: {}", topic, new String(message.getPayload()));
+        try {
+            ReflectionUtils.invokeMethod(method, target, topic, message);
+            log.debug("MQTT消息处理完成 - 主题: {}", topic);
+        } catch (Exception e) {
+            log.error("处理MQTT消息时发生错误 - 主题: {}", topic, e);
+            throw new RuntimeException("Failed to handle MQTT message", e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("MqttMessageHandler[target=%s, method=%s]", 
+            target.getClass().getName(), method.getName());
+    }
+} 

+ 43 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/core/MqttMessageHandlerRegistry.java

@@ -0,0 +1,43 @@
+package cn.hfln.emqx.core;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+public class MqttMessageHandlerRegistry {
+    private final Map<String, MqttMessageHandler> handlers = new ConcurrentHashMap<>();
+
+    public void registerHandler(String clientId, MqttMessageHandler handler) {
+        log.info("注册MQTT消息处理器 - 客户端ID: {}, 处理器: {}", clientId, handler);
+        handlers.put(clientId, handler);
+        log.debug("MQTT消息处理器注册完成 - 客户端ID: {}", clientId);
+    }
+
+    public void handleMessage(String clientId, String topic, MqttMessage message) {
+        log.debug("开始处理MQTT消息 - 客户端ID: {}, 主题: {}", clientId, topic);
+        MqttMessageHandler handler = handlers.get(clientId);
+        if (handler != null) {
+            try {
+                log.debug("找到消息处理器 - 客户端ID: {}, 处理器: {}", clientId, handler);
+                handler.handleMessage(topic, message);
+                log.debug("消息处理完成 - 客户端ID: {}, 主题: {}", clientId, topic);
+            } catch (Exception e) {
+                log.error("处理MQTT消息时发生错误 - 客户端ID: {}, 主题: {}", clientId, topic, e);
+                throw new RuntimeException("Failed to handle MQTT message", e);
+            }
+        } else {
+            log.warn("未找到消息处理器 - 客户端ID: {}, 主题: {}", clientId, topic);
+        }
+    }
+
+    public void removeHandler(String clientId) {
+        log.info("移除MQTT消息处理器 - 客户端ID: {}", clientId);
+        handlers.remove(clientId);
+        log.debug("MQTT消息处理器移除完成 - 客户端ID: {}", clientId);
+    }
+} 

+ 66 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/exception/EmqxException.java

@@ -0,0 +1,66 @@
+package cn.hfln.emqx.exception;
+
+/**
+ * EMQX异常类
+ */
+public class EmqxException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private final String code;
+
+    /**
+     * 构造函数
+     *
+     * @param message 错误信息
+     */
+    public EmqxException(String message) {
+        super(message);
+        this.code = "EMQX_ERROR";
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param code    错误码
+     * @param message 错误信息
+     */
+    public EmqxException(String code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param message 错误信息
+     * @param cause   异常原因
+     */
+    public EmqxException(String message, Throwable cause) {
+        super(message, cause);
+        this.code = "EMQX_ERROR";
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param code    错误码
+     * @param message 错误信息
+     * @param cause   异常原因
+     */
+    public EmqxException(String code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
+
+    /**
+     * 获取错误码
+     *
+     * @return 错误码
+     */
+    public String getCode() {
+        return code;
+    }
+} 

+ 118 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/AbstractMessageHandler.java

@@ -0,0 +1,118 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.context.MessageContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.Ordered;
+
+/**
+ * 抽象消息处理器
+ * 提供消息处理的通用实现
+ */
+@Slf4j
+public abstract class AbstractMessageHandler implements MessageHandler, Ordered {
+    /**
+     * 处理器名称
+     */
+    private final String name;
+
+    /**
+     * 处理器优先级
+     */
+    private final int order;
+
+    /**
+     * 构造函数
+     *
+     * @param name  处理器名称
+     * @param order 处理器优先级
+     */
+    protected AbstractMessageHandler(String name, int order) {
+        this.name = name;
+        this.order = order;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public int getOrder() {
+        return order;
+    }
+
+    @Override
+    public boolean handle(MessageContext context) {
+        try {
+            context.setProcessStartTime(java.time.LocalDateTime.now());
+            context.setStatus(MessageContext.ProcessStatus.PROCESSING);
+
+            boolean result = doHandle(context);
+
+            context.setProcessEndTime(java.time.LocalDateTime.now());
+            context.setStatus(result ? MessageContext.ProcessStatus.SUCCESS : MessageContext.ProcessStatus.FAILED);
+
+            return result;
+        } catch (Exception e) {
+            handleException(context, e);
+            return false;
+        }
+    }
+
+    @Override
+    public void handleException(MessageContext context, Exception e) {
+        log.error("Error handling message: {}", context.getMessageId(), e);
+        context.setErrorMessage(e.getMessage());
+        context.setStatus(MessageContext.ProcessStatus.FAILED);
+        context.setProcessEndTime(java.time.LocalDateTime.now());
+    }
+
+    @Override
+    public void handleTimeout(MessageContext context) {
+        log.warn("Message handling timeout: {}", context.getMessageId());
+        context.setErrorMessage("Message handling timeout");
+        context.setStatus(MessageContext.ProcessStatus.FAILED);
+        context.setProcessEndTime(java.time.LocalDateTime.now());
+    }
+
+    @Override
+    public boolean handleRetry(MessageContext context) {
+        int retryCount = context.getRetryCount();
+        if (retryCount >= getMaxRetryTimes()) {
+            log.warn("Max retry times reached for message: {}", context.getMessageId());
+            return false;
+        }
+
+        context.setRetryCount(retryCount + 1);
+        log.info("Retrying message: {}, attempt: {}", context.getMessageId(), retryCount + 1);
+        return true;
+    }
+
+    /**
+     * 实际的消息处理逻辑
+     * 由子类实现
+     *
+     * @param context 消息上下文
+     * @return 处理结果
+     * @throws Exception 处理异常
+     */
+    protected abstract boolean doHandle(MessageContext context) throws Exception;
+
+    /**
+     * 获取最大重试次数
+     *
+     * @return 最大重试次数
+     */
+    protected int getMaxRetryTimes() {
+        return 3;
+    }
+
+    /**
+     * 获取重试间隔(毫秒)
+     *
+     * @return 重试间隔
+     */
+    protected long getRetryInterval() {
+        return 1000;
+    }
+} 

+ 79 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/EmqxMessageHandler.java

@@ -0,0 +1,79 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.connector.EmqxConnector;
+import cn.hfln.emqx.context.MessageContext;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * EMQX消息处理器
+ * 负责处理MQTT消息的回调
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class EmqxMessageHandler implements MqttCallbackExtended {
+    private final EmqxConnector connector;
+    private final MessageHandlerRegistry registry;
+
+    @PostConstruct
+    public void init() {
+        connector.setCallback(this);
+    }
+
+    @PreDestroy
+    public void destroy() {
+        registry.shutdown();
+    }
+
+    @Override
+    public void connectComplete(boolean reconnect, String serverURI) {
+        log.info("Connected to MQTT broker: {}, reconnect: {}", serverURI, reconnect);
+    }
+
+    @Override
+    public void connectionLost(Throwable cause) {
+        log.error("Connection to MQTT broker lost", cause);
+    }
+
+    @Override
+    public void messageArrived(String topic, MqttMessage message) {
+        try {
+            MessageContext context = createMessageContext(topic, message);
+            registry.handleMessage(context);
+        } catch (Exception e) {
+            log.error("Error processing message from topic: {}", topic, e);
+        }
+    }
+
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+        log.debug("Message delivery complete: {}", token.getMessageId());
+    }
+
+    /**
+     * 创建消息上下文
+     */
+    private MessageContext createMessageContext(String topic, MqttMessage message) {
+        MessageContext context = new MessageContext();
+        context.setMessageId(UUID.randomUUID().toString());
+        context.setTopic(topic);
+        context.setMessage(message);
+        context.setReceiveTime(LocalDateTime.now());
+        context.setStatus(MessageContext.ProcessStatus.PENDING);
+        return context;
+    }
+
+    public MessageHandlerRegistry getRegistry() {
+        return registry;
+    }
+} 

+ 63 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandler.java

@@ -0,0 +1,63 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.context.MessageContext;
+
+/**
+ * 消息处理器接口
+ * 定义消息处理的标准方法
+ */
+public interface MessageHandler {
+    /**
+     * 处理消息
+     *
+     * @param context 消息上下文
+     * @return 处理结果
+     */
+    boolean handle(MessageContext context);
+
+    /**
+     * 获取处理器名称
+     *
+     * @return 处理器名称
+     */
+    String getName();
+
+    /**
+     * 获取处理器优先级
+     * 数字越小优先级越高
+     *
+     * @return 优先级
+     */
+    int getOrder();
+
+    /**
+     * 是否支持处理该消息
+     *
+     * @param context 消息上下文
+     * @return 是否支持
+     */
+    boolean supports(MessageContext context);
+
+    /**
+     * 处理异常
+     *
+     * @param context 消息上下文
+     * @param e       异常
+     */
+    void handleException(MessageContext context, Exception e);
+
+    /**
+     * 处理超时
+     *
+     * @param context 消息上下文
+     */
+    void handleTimeout(MessageContext context);
+
+    /**
+     * 处理重试
+     *
+     * @param context 消息上下文
+     * @return 是否继续重试
+     */
+    boolean handleRetry(MessageContext context);
+} 

+ 200 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerAdapter.java

@@ -0,0 +1,200 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.annotation.MqttListener;
+import cn.hfln.emqx.context.MessageContext;
+import cn.hfln.emqx.util.ExecutorUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 消息处理方法适配器
+ * 用于处理不同类型的消息处理方法
+ */
+@Slf4j
+public class MessageHandlerAdapter {
+    private final Object target;
+    private final Method method;
+    private final MqttListener annotation;
+    private final MessageHandler handler;
+    private final ExecutorService executor;
+    private final ScheduledExecutorService scheduler;
+
+    public MessageHandlerAdapter(Object target, Method method, MqttListener annotation) {
+        this.target = target;
+        this.method = method;
+        this.annotation = annotation;
+        this.handler = null;
+        
+        // 确保方法可访问
+        ReflectionUtils.makeAccessible(method);
+        
+        if (annotation.async()) {
+            this.executor = Executors.newSingleThreadExecutor(r -> {
+                Thread thread = new Thread(r, "mqtt-handler-" + method.getName());
+                thread.setDaemon(true);
+                return thread;
+            });
+            this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
+                Thread thread = new Thread(r, "mqtt-scheduler-" + method.getName());
+                thread.setDaemon(true);
+                return thread;
+            });
+        } else {
+            this.executor = null;
+            this.scheduler = null;
+        }
+    }
+
+    public MessageHandlerAdapter(MessageHandler handler) {
+        this.target = null;
+        this.method = null;
+        this.annotation = null;
+        this.handler = handler;
+        this.executor = null;
+        this.scheduler = null;
+    }
+
+    /**
+     * 处理消息
+     *
+     * @param context 消息上下文
+     */
+    public void handle(MessageContext context) {
+        if (handler != null) {
+            handler.handle(context);
+        } else if (annotation != null) {
+            if (annotation.advanced()) {
+                handleAdvanced(context);
+            } else {
+                handleSimple(context);
+            }
+        }
+    }
+
+    /**
+     * 处理高级消息(使用MessageContext)
+     */
+    private void handleAdvanced(MessageContext context) {
+        Runnable task = () -> {
+            try {
+                context.setProcessStartTime(java.time.LocalDateTime.now());
+                context.setStatus(MessageContext.ProcessStatus.PROCESSING);
+
+                Class<?>[] parameterTypes = method.getParameterTypes();
+                Object[] args = new Object[parameterTypes.length];
+
+                for (int i = 0; i < parameterTypes.length; i++) {
+                    if (parameterTypes[i] == MessageContext.class) {
+                        args[i] = context;
+                    } else if (parameterTypes[i] == String.class) {
+                        args[i] = new String(context.getMessage().getPayload());
+                    } else if (parameterTypes[i] == byte[].class) {
+                        args[i] = context.getMessage().getPayload();
+                    } else if (parameterTypes[i] == org.eclipse.paho.client.mqttv3.MqttMessage.class) {
+                        args[i] = context.getMessage();
+                    }
+                }
+
+                method.invoke(target, args);
+
+                context.setProcessEndTime(java.time.LocalDateTime.now());
+                context.setStatus(MessageContext.ProcessStatus.SUCCESS);
+            } catch (Exception e) {
+                handleException(context, e);
+            }
+        };
+
+        if (annotation.async()) {
+            executor.submit(task);
+        } else {
+            task.run();
+        }
+    }
+
+    /**
+     * 处理简单消息(使用原始消息类型)
+     */
+    private void handleSimple(MessageContext context) {
+        Runnable task = () -> {
+            try {
+                MqttMessage message = context.getMessage();
+                Class<?>[] parameterTypes = method.getParameterTypes();
+                Object[] args = new Object[parameterTypes.length];
+
+                for (int i = 0; i < parameterTypes.length; i++) {
+                    if (parameterTypes[i] == MqttMessage.class) {
+                        args[i] = message;
+                    } else if (parameterTypes[i] == String.class) {
+                        args[i] = new String(message.getPayload());
+                    } else if (parameterTypes[i] == byte[].class) {
+                        args[i] = message.getPayload();
+                    }
+                }
+
+                method.invoke(target, args);
+            } catch (Exception e) {
+                log.error("Error handling message: {}", context.getMessageId(), e);
+            }
+        };
+
+        if (annotation.async()) {
+            executor.submit(task);
+        } else {
+            task.run();
+        }
+    }
+
+    /**
+     * 处理异常
+     */
+    private void handleException(MessageContext context, Exception e) {
+        log.error("Error handling message: {}", context.getMessageId(), e);
+        context.setErrorMessage(e.getMessage());
+        context.setStatus(MessageContext.ProcessStatus.FAILED);
+        context.setProcessEndTime(java.time.LocalDateTime.now());
+
+        if (annotation.retryTimes() > 0) {
+            handleRetry(context);
+        }
+    }
+
+    /**
+     * 处理重试
+     */
+    private void handleRetry(MessageContext context) {
+        int retryCount = context.getRetryCount();
+        if (retryCount < annotation.retryTimes()) {
+            context.setRetryCount(retryCount + 1);
+            log.info("Retrying message: {}, attempt: {}", context.getMessageId(), retryCount + 1);
+            
+            if (annotation.async()) {
+                scheduler.schedule(() -> handle(context), 
+                    annotation.retryInterval(), TimeUnit.MILLISECONDS);
+            } else {
+                try {
+                    Thread.sleep(annotation.retryInterval());
+                    handle(context);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        } else {
+            log.warn("Max retry times reached for message: {}", context.getMessageId());
+        }
+    }
+
+    /**
+     * 关闭资源
+     */
+    public void shutdown() {
+        ExecutorUtils.shutdown(executor);
+        ExecutorUtils.shutdown(scheduler);
+    }
+} 

+ 99 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerChain.java

@@ -0,0 +1,99 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.context.MessageContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * 消息处理链
+ * 用于组合多个消息处理器
+ */
+@Slf4j
+@Component
+public class MessageHandlerChain {
+    /**
+     * 消息处理器列表
+     */
+    private final List<MessageHandler> handlers = new CopyOnWriteArrayList<>();
+
+    /**
+     * 添加消息处理器
+     *
+     * @param handler 消息处理器
+     */
+    public void addHandler(MessageHandler handler) {
+        handlers.add(handler);
+        handlers.sort(Comparator.comparingInt(MessageHandler::getOrder));
+    }
+
+    /**
+     * 移除消息处理器
+     *
+     * @param handler 消息处理器
+     */
+    public void removeHandler(MessageHandler handler) {
+        handlers.remove(handler);
+    }
+
+    /**
+     * 处理消息
+     *
+     * @param context 消息上下文
+     * @return 处理结果
+     */
+    public boolean handle(MessageContext context) {
+        for (MessageHandler handler : handlers) {
+            if (!handler.supports(context)) {
+                continue;
+            }
+
+            try {
+                if (!handler.handle(context)) {
+                    log.warn("Message handling failed by handler: {}", handler.getName());
+                    return false;
+                }
+            } catch (Exception e) {
+                handler.handleException(context, e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取所有处理器
+     *
+     * @return 处理器列表
+     */
+    public List<MessageHandler> getHandlers() {
+        return new ArrayList<>(handlers);
+    }
+
+    /**
+     * 获取支持的处理器
+     *
+     * @param context 消息上下文
+     * @return 支持的处理器列表
+     */
+    public List<MessageHandler> getSupportedHandlers(MessageContext context) {
+        List<MessageHandler> supportedHandlers = new ArrayList<>();
+        for (MessageHandler handler : handlers) {
+            if (handler.supports(context)) {
+                supportedHandlers.add(handler);
+            }
+        }
+        return supportedHandlers;
+    }
+
+    /**
+     * 清空处理器列表
+     */
+    public void clear() {
+        handlers.clear();
+    }
+} 

+ 182 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/handler/MessageHandlerRegistry.java

@@ -0,0 +1,182 @@
+package cn.hfln.emqx.handler;
+
+import cn.hfln.emqx.annotation.MqttListener;
+import cn.hfln.emqx.context.MessageContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 消息处理方法注册器
+ * 用于注册和管理消息处理方法
+ */
+@Slf4j
+@Component
+public class MessageHandlerRegistry implements BeanPostProcessor {
+    private final Map<String, MessageHandlerAdapter> handlerMap = new ConcurrentHashMap<>();
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        ReflectionUtils.doWithMethods(bean.getClass(), method -> {
+            log.info("Scanning class: {}", bean.getClass().getName());
+            MqttListener annotation = method.getAnnotation(MqttListener.class);
+            if (annotation != null) {
+                registerHandler(bean, method, annotation);
+            }
+        });
+
+        return bean;
+    }
+
+    /**
+     * 注册消息处理方法
+     */
+    private void registerHandler(Object bean, Method method, MqttListener annotation) {
+        String[] topics = annotation.topics();
+        for (String topic : topics) {
+            String key = generateKey(topic, annotation.qos());
+            MessageHandlerAdapter adapter = new MessageHandlerAdapter(bean, method, annotation);
+            handlerMap.put(key, adapter);
+            log.info("Registered message handler for topic: {}, QoS: {}, method: {}", 
+                topic, annotation.qos(), method.getName());
+        }
+    }
+
+    /**
+     * 处理消息
+     */
+    public void handleMessage(MessageContext context) {
+        String topic = context.getTopic();
+        int qos = context.getMessage().getQos();
+        log.info("[MessageHandlerRegistry] handleMessage: received topic={}, qos={}", topic, qos);
+        log.info("[MessageHandlerRegistry] Registered handler keys: {}", handlerMap.keySet());
+        // 尝试直接匹配
+        String key = generateKey(topic, qos);
+        MessageHandlerAdapter adapter = handlerMap.get(key);
+        // 如果没有直接匹配,尝试通配符匹配
+        if (adapter == null) {
+            log.debug("No direct match found, trying wildcard match for topic: {}", topic);
+            for (Map.Entry<String, MessageHandlerAdapter> entry : handlerMap.entrySet()) {
+                String handlerTopic = entry.getKey().split(":")[0];
+                log.debug("Trying wildcard match: actual={}, pattern={}", topic, handlerTopic);
+                if (matchTopic(topic, handlerTopic)) {
+                    adapter = entry.getValue();
+                    log.info("Found matching wildcard handler for topic: {}, pattern: {}", topic, handlerTopic);
+                    break;
+                }
+            }
+        } else {
+            log.info("Found direct matching handler for topic: {}", topic);
+        }
+        if (adapter != null) {
+            adapter.handle(context);
+        } else {
+            log.warn("No handler found for topic: {}, QoS: {}", topic, qos);
+        }
+    }
+
+    /**
+     * 匹配主题
+     * 支持 + 和 # 通配符
+     */
+    private boolean matchTopic(String actualTopic, String patternTopic) {
+        String[] actualParts = actualTopic.split("/");
+        String[] patternParts = patternTopic.split("/");
+        
+        log.debug("Matching topic: {} against pattern: {}", actualTopic, patternTopic);
+        
+        // 如果模式以 # 结尾,且实际主题长度大于等于模式长度
+        if (patternTopic.endsWith("#")) {
+            if (actualParts.length < patternParts.length - 1) {
+                log.debug("Pattern ends with # but actual topic is too short");
+                return false;
+            }
+            // 检查 # 之前的部分是否匹配
+            for (int i = 0; i < patternParts.length - 1; i++) {
+                if (!patternParts[i].equals("+") && !patternParts[i].equals(actualParts[i])) {
+                    log.debug("Mismatch at level {}: pattern={}, actual={}", 
+                        i, patternParts[i], actualParts[i]);
+                    return false;
+                }
+            }
+            log.debug("Pattern with # matched successfully");
+            return true;
+        }
+        
+        // 如果长度不同,且模式不以 # 结尾,则不匹配
+        if (actualParts.length != patternParts.length) {
+            log.debug("Length mismatch: pattern={}, actual={}", 
+                patternParts.length, actualParts.length);
+            return false;
+        }
+        
+        // 逐层比较
+        for (int i = 0; i < patternParts.length; i++) {
+            if (!patternParts[i].equals("+") && !patternParts[i].equals(actualParts[i])) {
+                log.debug("Mismatch at level {}: pattern={}, actual={}", 
+                    i, patternParts[i], actualParts[i]);
+                return false;
+            }
+        }
+        
+        log.debug("Pattern matched successfully");
+        return true;
+    }
+
+    /**
+     * 生成处理器键
+     */
+    private String generateKey(String topic, int qos) {
+        return topic + ":" + qos;
+    }
+
+    /**
+     * 关闭所有处理器
+     */
+    public void shutdown() {
+        handlerMap.values().forEach(MessageHandlerAdapter::shutdown);
+        handlerMap.clear();
+    }
+
+    /**
+     * 添加消息处理器
+     *
+     * @param topic 主题
+     * @param qos QoS级别
+     * @param handler 消息处理器
+     */
+    public void addHandler(String topic, int qos, MessageHandler handler) {
+        String key = generateKey(topic, qos);
+        MessageHandlerAdapter adapter = new MessageHandlerAdapter(handler);
+        handlerMap.put(key, adapter);
+        log.info("Added message handler for topic: {}, QoS: {}", topic, qos);
+    }
+
+//    /**
+//     * 重新订阅所有主题
+//     */
+//    public void resubscribeAll() {
+//        for (Map.Entry<String, Set<Method>> entry : topicMethodMap.entrySet()) {
+//            String topic = entry.getKey();
+//            Set<Method> methods = entry.getValue();
+//            if (!methods.isEmpty()) {
+//                Method method = methods.iterator().next();
+//                MqttListener listener = AnnotationUtils.findAnnotation(method, MqttListener.class);
+//                if (listener != null) {
+//                    try {
+//                        connector.subscribe(topic, listener.qos());
+//                        log.info("Resubscribed to topic: {} with QoS: {}", topic, listener.qos());
+//                    } catch (Exception e) {
+//                        log.error("Failed to resubscribe to topic: {} with QoS: {}", topic, listener.qos(), e);
+//                    }
+//                }
+//            }
+//        }
+//    }
+} 

+ 132 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/model/MqttMessages.java

@@ -0,0 +1,132 @@
+package cn.hfln.emqx.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * MQTT消息对象
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MqttMessages implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 消息ID
+     */
+    private String messageId;
+
+    /**
+     * 主题
+     */
+    private String topic;
+
+    /**
+     * 消息内容
+     */
+    private byte[] payload;
+
+    /**
+     * QoS级别
+     */
+    private int qos;
+
+    /**
+     * 是否保留
+     */
+    private boolean retained;
+
+    /**
+     * 发送时间
+     */
+    private LocalDateTime timestamp;
+
+    /**
+     * 消息类型
+     */
+    private String type;
+
+    /**
+     * 消息来源
+     */
+    private String source;
+
+    /**
+     * 消息标签
+     */
+    private String[] tags;
+
+    /**
+     * 消息属性
+     */
+    private Object properties;
+
+    /**
+     * 创建消息构建器
+     *
+     * @return 消息构建器
+     */
+    public static MqttMessagesBuilder builder() {
+        return new MqttMessagesBuilder()
+                .messageId(java.util.UUID.randomUUID().toString())
+                .timestamp(LocalDateTime.now());
+    }
+
+    /**
+     * 创建简单消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @return 消息对象
+     */
+    public static MqttMessages simple(String topic, byte[] payload) {
+        return builder()
+                .topic(topic)
+                .payload(payload)
+                .qos(1)
+                .retained(false)
+                .timestamp(LocalDateTime.now())
+                .build();
+    }
+
+    /**
+     * 创建保留消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @return 消息对象
+     */
+    public static MqttMessages retained(String topic, byte[] payload) {
+        return builder()
+                .topic(topic)
+                .payload(payload)
+                .qos(1)
+                .retained(true)
+                .timestamp(LocalDateTime.now())
+                .build();
+    }
+
+    /**
+     * 创建高质量消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @return 消息对象
+     */
+    public static MqttMessages highQuality(String topic, byte[] payload) {
+        return builder()
+                .topic(topic)
+                .payload(payload)
+                .qos(2)
+                .retained(false)
+                .timestamp(LocalDateTime.now())
+                .build();
+    }
+} 

+ 172 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/properties/EmqxProperties.java

@@ -0,0 +1,172 @@
+package cn.hfln.emqx.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.*;
+
+/**
+ * EMQX配置属性
+ */
+@Data
+@Validated
+@ConfigurationProperties(prefix = "emqx")
+public class EmqxProperties {
+    /**
+     * 是否启用EMQX
+     */
+    private boolean enabled = true;
+
+    /**
+     * 服务器地址
+     */
+    @NotEmpty(message = "Server URIs cannot be empty")
+    private String[] serverUris = new String[]{"tcp://localhost:1883"};
+
+    /**
+     * 客户端ID
+     */
+    private String clientId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 连接超时时间(秒)
+     */
+    @Min(value = 1, message = "Connection timeout must be at least 1 second")
+    private int connectionTimeout = 30;
+
+    /**
+     * 心跳间隔(秒)
+     */
+    @Min(value = 1, message = "Keep alive interval must be at least 1 second")
+    private int keepAliveInterval = 60;
+
+    /**
+     * 是否自动重连
+     */
+    private boolean automaticReconnect = true;
+
+    /**
+     * 最大重连延迟(毫秒)
+     */
+    @Min(value = 1000, message = "Max reconnect delay must be at least 1000ms")
+    private int maxReconnectDelay = 10000;
+
+    /**
+     * 是否清理会话
+     */
+    private boolean cleanSession = true;
+
+    /**
+     * 最大飞行消息数
+     */
+    @Min(value = 1, message = "Max inflight must be at least 1")
+    private int maxInflight = 100;
+
+    /**
+     * 是否启用共享订阅
+     */
+    private boolean sharedSubscription = false;
+
+    /**
+     * 共享订阅组名
+     */
+    private String sharedGroupName;
+
+    /**
+     * 默认发布QoS
+     */
+    @Min(value = 0, message = "Default publish QoS must be between 0 and 2")
+    private int defaultPublishQos = 0;
+
+    /**
+     * 默认订阅QoS
+     */
+    @Min(value = 0, message = "Default subscribe QoS must be between 0 and 2")
+    private int defaultSubscribeQos = 0;
+
+    /**
+     * 连接监控配置
+     */
+    private Monitor monitor = new Monitor();
+
+    /**
+     * 连接监控配置
+     */
+    @Data
+    public static class Monitor {
+        /**
+         * 是否启用连接监控
+         */
+        private boolean enabled = true;
+
+        /**
+         * 监控间隔(秒)
+         */
+        @Min(value = 1, message = "Monitor interval must be at least 1 second")
+        private int interval = 30;
+
+        /**
+         * 最大重连次数
+         */
+        @Min(value = 1, message = "Max reconnect attempts must be at least 1")
+        private int maxReconnectAttempts = 10;
+
+        /**
+         * 重连冷却时间(秒)
+         */
+        @Min(value = 1, message = "Reconnect cooldown must be at least 1 second")
+        private int reconnectCooldown = 300;
+    }
+
+    /**
+     * 消息配置
+     */
+    private Message message = new Message();
+
+    /**
+     * 消息配置
+     */
+    @Data
+    public static class Message {
+        /**
+         * 消息缓冲区大小
+         */
+        @Min(value = 1, message = "Buffer size must be at least 1")
+        private int bufferSize = 1000;
+
+        /**
+         * 消息处理超时时间(秒)
+         */
+        @Min(value = 1, message = "Process timeout must be at least 1 second")
+        private int processTimeout = 30;
+
+        /**
+         * 是否启用消息持久化
+         */
+        private boolean persistence = false;
+
+        /**
+         * 持久化目录
+         */
+        private String persistenceDirectory = "emqx-messages";
+    }
+
+    /**
+     * MQTT版本
+     * 3.1 = 3
+     * 3.1.1 = 4
+     * 5.0 = 5
+     */
+    private int mqttVersion = 4;
+} 

+ 276 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/publisher/MqttPublisher.java

@@ -0,0 +1,276 @@
+package cn.hfln.emqx.publisher;
+
+import cn.hfln.emqx.connector.EmqxConnector;
+import cn.hfln.emqx.model.MqttMessages;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+/**
+ * MQTT消息发布器
+ * 提供多种消息发布方式和回调支持
+ */
+@Slf4j
+@Component
+public class MqttPublisher {
+    private final EmqxConnector connector;
+    private static final String DEFAULT_CLIENT_ID = "default";
+
+    @Autowired
+    public MqttPublisher(EmqxConnector connector) {
+        this.connector = connector;
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String topic, Object payload) {
+        send(DEFAULT_CLIENT_ID, topic, payload, 1, false, null);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param callback 消息发送完成后的回调
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String topic, Object payload, IMqttActionListener callback) {
+        send(DEFAULT_CLIENT_ID, topic, payload, 1, false, callback);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param clientId 客户端ID
+     * @param topic    主题
+     * @param payload  消息内容
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String clientId, String topic, Object payload) {
+        send(clientId, topic, payload, 1, false, null);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param clientId 客户端ID
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param callback 消息发送完成后的回调
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String clientId, String topic, Object payload, IMqttActionListener callback) {
+        send(clientId, topic, payload, 1, false, callback);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param qos      服务质量
+     * @param retained 是否保留
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String topic, Object payload, int qos, boolean retained) {
+        send(DEFAULT_CLIENT_ID, topic, payload, qos, retained, null);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param clientId 客户端ID
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param qos      服务质量
+     * @param retained 是否保留
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String clientId, String topic, Object payload, int qos, boolean retained) {
+        send(clientId, topic, payload, qos, retained, null);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param qos      服务质量
+     * @param retained 是否保留
+     * @param callback 消息发送完成后的回调
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String topic, Object payload, int qos, boolean retained, IMqttActionListener callback) {
+        send(DEFAULT_CLIENT_ID, topic, payload, qos, retained, callback);
+    }
+
+    /**
+     * 发送消息到指定主题
+     *
+     * @param clientId 客户端ID
+     * @param topic    主题
+     * @param payload  消息内容
+     * @param qos      服务质量
+     * @param retained 是否保留
+     * @param callback 消息发送完成后的回调
+     * @throws IllegalArgumentException if topic is empty
+     */
+    public void send(String clientId, String topic, Object payload, int qos, boolean retained, IMqttActionListener callback) {
+        Assert.isTrue(topic != null && !topic.trim().isEmpty(), "Topic cannot be empty");
+        
+        try {
+            byte[] bytes = convertToBytes(payload);
+            if (bytes == null) {
+                log.warn("Failed to convert payload to bytes for topic: {}", topic);
+                return;
+            }
+
+            MqttMessages message = createMessage(bytes, qos, retained);
+            connector.publish(topic, message.getPayload(), message.getQos(), message.isRetained());
+            
+            if (callback != null) {
+                callback.onSuccess(null);
+            }
+            
+            if (log.isDebugEnabled()) {
+                log.debug("Published message to topic {}: {}", topic, safeToString(bytes));
+            }
+        } catch (Exception e) {
+            log.error("Failed to publish message to topic {}: {}", topic, e.getMessage(), e);
+            if (callback != null) {
+                callback.onFailure(null, e);
+            }
+            throw new RuntimeException("Failed to publish message", e);
+        }
+    }
+
+    /**
+     * 发送MQTT消息对象
+     *
+     * @param message MQTT消息对象
+     */
+    public void send(MqttMessages message) {
+        send(DEFAULT_CLIENT_ID, message);
+    }
+
+    /**
+     * 发送MQTT消息对象
+     *
+     * @param clientId 客户端ID
+     * @param message  MQTT消息对象
+     */
+    public void send(String clientId, MqttMessages message) {
+        Assert.notNull(message, "Message cannot be null");
+        Assert.isTrue(message.getTopic() != null && !message.getTopic().trim().isEmpty(), "Topic cannot be empty");
+
+        try {
+            connector.publish(message.getTopic(), message.getPayload(), message.getQos(), message.isRetained());
+
+        } catch (Exception e) {
+            log.error("Failed to publish message to topic {}: {}", message.getTopic(), e.getMessage(), e);
+            throw new RuntimeException("Failed to publish message", e);
+        }
+    }
+
+    /**
+     * 发送文本消息
+     *
+     * @param topic   主题
+     * @param message 文本消息
+     */
+    public void sendText(String topic, String message) {
+        send(topic, message.getBytes(), 1, false);
+    }
+
+    /**
+     * 发送JSON消息
+     *
+     * @param topic   主题
+     * @param message JSON消息
+     */
+    public void sendJson(String topic, String message) {
+        MqttMessages mqttMessage = MqttMessages.builder()
+                .topic(topic)
+                .payload(message.getBytes())
+                .qos(1)
+                .retained(false)
+                .type("application/json")
+                .build();
+        send(mqttMessage);
+    }
+
+    /**
+     * 发送保留消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     */
+    public void sendRetained(String topic, Object payload) {
+        send(topic, payload, 1, true);
+    }
+
+    /**
+     * 发送高质量消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     */
+    public void sendHighQuality(String topic, Object payload) {
+        send(topic, payload, 2, false);
+    }
+
+    /**
+     * 创建MQTT消息
+     */
+    private MqttMessages createMessage(byte[] payload, int qos, boolean retained) {
+        return MqttMessages.builder()
+                .payload(payload)
+                .qos(qos)
+                .retained(retained)
+                .build();
+    }
+
+    /**
+     * 转换消息内容为字节数组
+     */
+    private byte[] convertToBytes(Object payload) {
+        if (payload == null) {
+            return null;
+        }
+        if (payload instanceof byte[]) {
+            return (byte[]) payload;
+        }
+        if (payload instanceof String) {
+            return ((String) payload).getBytes();
+        }
+        // 可以添加更多类型的转换
+        return payload.toString().getBytes();
+    }
+
+    /**
+     * 安全地将字节数组转换为字符串
+     */
+    private String safeToString(byte[] bytes) {
+        if (bytes == null) {
+            return "null";
+        }
+        try {
+            String content = new String(bytes);
+            // 如果内容太长,截断显示
+            if (content.length() > 100) {
+                return content.substring(0, 100) + "...";
+            }
+            return content;
+        } catch (Exception e) {
+            return "[Binary content, length: " + bytes.length + "]";
+        }
+    }
+} 

+ 210 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/subscriber/MqttSubscriber.java

@@ -0,0 +1,210 @@
+package cn.hfln.emqx.subscriber;
+
+import cn.hfln.emqx.annotation.MqttListener;
+import cn.hfln.emqx.connector.EmqxConnector;
+import cn.hfln.emqx.context.MessageContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * MQTT订阅处理器
+ * 用于处理@MqttListener注解
+ */
+@Slf4j
+@Component
+public class MqttSubscriber implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
+    private final EmqxConnector connector;
+    private ApplicationContext applicationContext;
+    private final Map<String, Set<Method>> topicMethodMap = new ConcurrentHashMap<>();
+    private final Map<String, Object> beanMap = new ConcurrentHashMap<>();
+
+    @Autowired
+    public MqttSubscriber(EmqxConnector connector) {
+        this.connector = connector;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        if (event.getApplicationContext() == this.applicationContext) {
+            initSubscribers();
+        }
+    }
+
+    /**
+     * 初始化所有订阅
+     */
+    private void initSubscribers() {
+        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);
+        for (Object bean : beans.values()) {
+            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
+            for (Method method : targetClass.getDeclaredMethods()) {
+                MqttListener listener = method.getAnnotation(MqttListener.class);
+                if (listener != null) {
+                    processListener(bean, method, listener);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理监听器注解
+     */
+    private void processListener(Object bean, Method method, MqttListener listener) {
+        String[] topics = listener.topics();
+        int qos = listener.qos();
+        boolean shared = listener.shared();
+        String sharedGroup = listener.sharedGroup();
+
+        Assert.isTrue(topics.length > 0, "Topics cannot be empty");
+        Assert.isTrue(qos >= 0 && qos <= 2, "QoS must be 0, 1, or 2");
+        Assert.isTrue(!shared || StringUtils.hasText(sharedGroup), "Shared group name is required when shared is true");
+
+        for (String topic : topics) {
+            String finalTopic = shared ? String.format("$share/%s/%s", sharedGroup, topic) : topic;
+            topicMethodMap.computeIfAbsent(finalTopic, k -> new HashSet<>()).add(method);
+            beanMap.put(method.toString(), bean);
+
+            try {
+                connector.subscribe(finalTopic, qos);
+                log.info("Subscribed to topic: {} with QoS: {}", finalTopic, qos);
+            } catch (Exception e) {
+                log.error("Failed to subscribe to topic: {} with QoS: {}", finalTopic, qos, e);
+            }
+        }
+    }
+
+    /**
+     * 处理接收到的消息
+     */
+    public void handleMessage(String topic, byte[] payload, int qos, boolean retained) {
+        Set<Method> methods = topicMethodMap.get(topic);
+        if (methods == null || methods.isEmpty()) {
+            log.debug("No handlers found for topic: {}", topic);
+            return;
+        }
+
+        MessageContext context = createMessageContext(topic, payload, qos, retained);
+
+        for (Method method : methods) {
+            Object bean = beanMap.get(method.toString());
+            if (bean == null) {
+                log.warn("No bean found for method: {}", method.getName());
+                continue;
+            }
+
+            try {
+                invokeMethod(bean, method, context);
+            } catch (Exception e) {
+                log.error("Failed to handle message for topic: {} with method: {}", topic, method.getName(), e);
+            }
+        }
+    }
+
+    /**
+     * 创建消息上下文
+     */
+    private MessageContext createMessageContext(String topic, byte[] payload, int qos, boolean retained) {
+        MessageContext context = new MessageContext();
+        context.setTopic(topic);
+        context.setMessage(new org.eclipse.paho.client.mqttv3.MqttMessage(payload));
+        context.getMessage().setQos(qos);
+        context.getMessage().setRetained(retained);
+        context.setReceiveTime(java.time.LocalDateTime.now());
+        context.setMessageId(generateMessageId());
+        context.setStatus(MessageContext.ProcessStatus.PENDING);
+        return context;
+    }
+
+    /**
+     * 生成消息ID
+     */
+    private String generateMessageId() {
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * 调用订阅方法
+     */
+    private void invokeMethod(Object bean, Method method, MessageContext context) throws Exception {
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        Object[] args = new Object[parameterTypes.length];
+
+        for (int i = 0; i < parameterTypes.length; i++) {
+            Class<?> paramType = parameterTypes[i];
+            if (paramType == MessageContext.class) {
+                args[i] = context;
+            } else if (paramType == String.class) {
+                args[i] = new String(context.getMessage().getPayload());
+            } else if (paramType == byte[].class) {
+                args[i] = context.getMessage().getPayload();
+            } else if (paramType == Integer.class || paramType == int.class) {
+                args[i] = context.getMessage().getQos();
+            } else if (paramType == Boolean.class || paramType == boolean.class) {
+                args[i] = context.getMessage().isRetained();
+            } else if (paramType == java.time.LocalDateTime.class) {
+                args[i] = context.getReceiveTime();
+            }
+        }
+
+        method.invoke(bean, args);
+    }
+
+    /**
+     * 重新订阅所有主题
+     */
+    public void resubscribeAll() {
+        try {
+            for (Map.Entry<String, Set<Method>> entry : topicMethodMap.entrySet()) {
+                String topic = entry.getKey();
+                Set<Method> methods = entry.getValue();
+                if (!methods.isEmpty()) {
+                    Method method = methods.iterator().next();
+                    MqttListener listener = method.getAnnotation(MqttListener.class);
+                    if (listener != null) {
+                        try {
+                            connector.subscribe(topic, listener.qos());
+                            log.info("Resubscribed to topic: {} with QoS: {}", topic, listener.qos());
+                        } catch (Exception e) {
+                            log.error("Failed to resubscribe to topic: {} with QoS: {}", topic, listener.qos(), e);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("Error during resubscribe process", e);
+        }
+    }
+
+    /**
+     * 处理超时
+     */
+    private void handleTimeout(MessageContext context) {
+        context.setStatus(MessageContext.ProcessStatus.FAILED);
+        context.setErrorMessage("Message processing timeout");
+        context.setProcessEndTime(LocalDateTime.now());
+        log.error("Message processing timeout - Topic: {}, MessageId: {}, ProcessTime: {}ms",
+                context.getTopic(), context.getMessageId(), context.getProcessTime());
+    }
+} 

+ 211 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/util/EmqxUtils.java

@@ -0,0 +1,211 @@
+package cn.hfln.emqx.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * EMQX工具类
+ */
+@Slf4j
+public class EmqxUtils {
+
+    private EmqxUtils() {
+        throw new IllegalStateException("Utility class");
+    }
+
+    /**
+     * 创建MQTT客户端
+     *
+     * @param serverUri MQTT服务器地址
+     * @param clientId 客户端ID
+     * @param username 用户名
+     * @param password 密码
+     * @param timeout 连接超时时间(秒)
+     * @param keepAlive 保活时间(秒)
+     * @return MqttClient实例
+     */
+    public static MqttClient createClient(String serverUri, String clientId, String username, 
+            String password, int timeout, int keepAlive) {
+        try {
+            MqttClient client = new MqttClient(serverUri, clientId, new MemoryPersistence());
+            MqttConnectOptions options = new MqttConnectOptions();
+            options.setServerURIs(new String[]{serverUri});
+            if (StringUtils.hasText(username)) {
+                options.setUserName(username);
+            }
+            if (StringUtils.hasText(password)) {
+                options.setPassword(password.toCharArray());
+            }
+            options.setConnectionTimeout(timeout);
+            options.setKeepAliveInterval(keepAlive);
+            options.setAutomaticReconnect(true);
+            options.setCleanSession(true);
+            
+            client.connect(options);
+            log.info("MQTT客户端创建成功 - 客户端ID: {}", clientId);
+            return client;
+        } catch (MqttException e) {
+            log.error("创建MQTT客户端失败 - 客户端ID: {}", clientId, e);
+            throw new RuntimeException("Failed to create MQTT client", e);
+        }
+    }
+
+    /**
+     * 发布消息
+     *
+     * @param client MQTT客户端
+     * @param topic 主题
+     * @param payload 消息内容
+     * @param qos QoS级别
+     * @param retained 是否保留消息
+     */
+    public static void publish(MqttClient client, String topic, String payload, int qos, boolean retained) {
+        try {
+            MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
+            message.setQos(qos);
+            message.setRetained(retained);
+            client.publish(topic, message);
+            log.debug("消息发布成功 - 主题: {}, QoS: {}, 内容: {}", topic, qos, payload);
+        } catch (MqttException e) {
+            log.error("发布消息失败 - 主题: {}", topic, e);
+            throw new RuntimeException("Failed to publish message", e);
+        }
+    }
+
+    /**
+     * 订阅主题
+     *
+     * @param client MQTT客户端
+     * @param topic 主题
+     * @param qos QoS级别
+     * @param callback 消息回调
+     */
+    public static void subscribe(MqttClient client, String topic, int qos, IMqttMessageListener callback) {
+        try {
+            client.subscribe(topic, qos, callback);
+            log.info("主题订阅成功 - 主题: {}, QoS: {}", topic, qos);
+        } catch (MqttException e) {
+            log.error("订阅主题失败 - 主题: {}", topic, e);
+            throw new RuntimeException("Failed to subscribe topic", e);
+        }
+    }
+
+    /**
+     * 取消订阅
+     *
+     * @param client MQTT客户端
+     * @param topic 主题
+     */
+    public static void unsubscribe(MqttClient client, String topic) {
+        try {
+            client.unsubscribe(topic);
+            log.info("取消订阅成功 - 主题: {}", topic);
+        } catch (MqttException e) {
+            log.error("取消订阅失败 - 主题: {}", topic, e);
+            throw new RuntimeException("Failed to unsubscribe topic", e);
+        }
+    }
+
+    /**
+     * 断开连接
+     *
+     * @param client MQTT客户端
+     */
+    public static void disconnect(MqttClient client) {
+        if (client != null && client.isConnected()) {
+            try {
+                client.disconnect();
+                log.info("MQTT客户端断开连接成功");
+            } catch (MqttException e) {
+                log.error("断开MQTT连接失败", e);
+                throw new RuntimeException("Failed to disconnect MQTT client", e);
+            }
+        }
+    }
+
+    /**
+     * 关闭客户端
+     *
+     * @param client MQTT客户端
+     */
+    public static void close(MqttClient client) {
+        if (client != null) {
+            try {
+                client.close();
+                log.info("MQTT客户端关闭成功");
+            } catch (MqttException e) {
+                log.error("关闭MQTT客户端失败", e);
+                throw new RuntimeException("Failed to close MQTT client", e);
+            }
+        }
+    }
+
+    /**
+     * 生成唯一的客户端ID
+     *
+     * @param prefix 前缀
+     * @return 客户端ID
+     */
+    public static String generateClientId(String prefix) {
+        return prefix + "-" + UUID.randomUUID().toString().replace("-", "");
+    }
+
+    /**
+     * 等待连接建立
+     *
+     * @param client MQTT客户端
+     * @param timeout 超时时间(秒)
+     * @return 是否连接成功
+     */
+    public static boolean waitForConnection(MqttClient client, int timeout) {
+        long startTime = System.currentTimeMillis();
+        while (System.currentTimeMillis() - startTime < TimeUnit.SECONDS.toMillis(timeout)) {
+            if (client.isConnected()) {
+                return true;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 检查主题是否匹配
+     *
+     * @param topicFilter 主题过滤器
+     * @param topic 主题
+     * @return 是否匹配
+     */
+    public static boolean isTopicMatch(String topicFilter, String topic) {
+        String[] filterParts = topicFilter.split("/");
+        String[] topicParts = topic.split("/");
+
+        if (filterParts.length != topicParts.length && !topicFilter.endsWith("#")) {
+            return false;
+        }
+
+        for (int i = 0; i < filterParts.length; i++) {
+            if (filterParts[i].equals("#")) {
+                return true;
+            }
+            if (i >= topicParts.length) {
+                return false;
+            }
+            if (!filterParts[i].equals("+") && !filterParts[i].equals(topicParts[i])) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+} 

+ 43 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/java/cn/hfln/emqx/util/ExecutorUtils.java

@@ -0,0 +1,43 @@
+package cn.hfln.emqx.util;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 执行器工具类
+ */
+public class ExecutorUtils {
+    private ExecutorUtils() {
+        // 私有构造函数,防止实例化
+    }
+
+    /**
+     * 关闭执行器
+     *
+     * @param executor 执行器
+     * @param timeout  超时时间
+     * @param unit     时间单位
+     */
+    public static void shutdown(ExecutorService executor, long timeout, TimeUnit unit) {
+        if (executor != null) {
+            executor.shutdown();
+            try {
+                if (!executor.awaitTermination(timeout, unit)) {
+                    executor.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                executor.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * 关闭执行器(使用默认超时时间5秒)
+     *
+     * @param executor 执行器
+     */
+    public static void shutdown(ExecutorService executor) {
+        shutdown(executor, 5, TimeUnit.SECONDS);
+    }
+} 

+ 120 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/META-INF/spring-configuration-metadata.json

@@ -0,0 +1,120 @@
+{
+  "groups": [
+    {
+      "name": "emqx",
+      "type": "cn.hfln.emqx.properties.EmqxProperties",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties"
+    }
+  ],
+  "properties": [
+    {
+      "name": "emqx.enabled",
+      "type": "java.lang.Boolean",
+      "description": "Whether to enable EMQX auto-configuration.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": true
+    },
+    {
+      "name": "emqx.server-uris",
+      "type": "java.lang.String[]",
+      "description": "EMQX server URIs.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties"
+    },
+    {
+      "name": "emqx.client-id",
+      "type": "java.lang.String",
+      "description": "Client ID for EMQX connection.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties"
+    },
+    {
+      "name": "emqx.username",
+      "type": "java.lang.String",
+      "description": "Username for EMQX authentication.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties"
+    },
+    {
+      "name": "emqx.password",
+      "type": "java.lang.String",
+      "description": "Password for EMQX authentication.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties"
+    },
+    {
+      "name": "emqx.connection-timeout",
+      "type": "java.lang.Integer",
+      "description": "Connection timeout in seconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 30
+    },
+    {
+      "name": "emqx.keep-alive-interval",
+      "type": "java.lang.Integer",
+      "description": "Keep alive interval in seconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 60
+    },
+    {
+      "name": "emqx.automatic-reconnect",
+      "type": "java.lang.Boolean",
+      "description": "Whether to enable automatic reconnection.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": true
+    },
+    {
+      "name": "emqx.max-reconnect-delay",
+      "type": "java.lang.Integer",
+      "description": "Maximum reconnect delay in milliseconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 10000
+    },
+    {
+      "name": "emqx.clean-session",
+      "type": "java.lang.Boolean",
+      "description": "Whether to clean session on connect.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": true
+    },
+    {
+      "name": "emqx.max-inflight",
+      "type": "java.lang.Integer",
+      "description": "Maximum number of inflight messages.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 100
+    },
+    {
+      "name": "emqx.monitor.enabled",
+      "type": "java.lang.Boolean",
+      "description": "Whether to enable connection monitoring.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": true
+    },
+    {
+      "name": "emqx.monitor.interval",
+      "type": "java.lang.Integer",
+      "description": "Connection monitoring interval in seconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 30
+    },
+    {
+      "name": "emqx.monitor.max-reconnect-attempts",
+      "type": "java.lang.Integer",
+      "description": "Maximum number of reconnect attempts.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 10
+    },
+    {
+      "name": "emqx.monitor.reconnect-cooldown",
+      "type": "java.lang.Integer",
+      "description": "Reconnect cooldown period in seconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 60
+    },
+    {
+      "name": "emqx.message.process-timeout",
+      "type": "java.lang.Integer",
+      "description": "Message processing timeout in seconds.",
+      "sourceType": "cn.hfln.emqx.properties.EmqxProperties",
+      "defaultValue": 30
+    }
+  ],
+  "hints": []
+} 

+ 3 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.hfln.emqx.autoconfigure.EmqxAutoConfiguration

+ 51 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/main/resources/application.yml

@@ -0,0 +1,51 @@
+#emqx:
+#  # 服务器配置
+#  server:
+#    urls: tcp://localhost:1883
+#    username: ${EMQX_USERNAME:}
+#    password: ${EMQX_PASSWORD:}
+#    client-id: ${spring.application.name:emqx-client}-${random.uuid}
+#    clean-session: true
+#    connection-timeout: 30
+#    keep-alive-interval: 60
+#    max-reconnect-delay: 1000
+#    automatic-reconnect: true
+#    max-inflight: 1000
+#    completion-timeout: 3000
+#    executor-service-size: 10
+#    ssl:
+#      enabled: false
+#      protocol: TLS
+#      ca-cert: classpath:ca.crt
+#      client-cert: classpath:client.crt
+#      client-key: classpath:client.key
+#      password: ${EMQX_SSL_PASSWORD:}
+#
+#  # 发布者配置
+#  publisher:
+#    default-qos: 1
+#    default-retained: false
+#    async: true
+#    buffer-size: 1000
+#    batch-size: 100
+#    batch-interval: 1000
+#
+#  # 订阅者配置
+#  subscriber:
+#    default-qos: 1
+#    shared-subscription: false
+#    shared-group: ${spring.application.name:emqx-client}
+#    message-handler:
+#      timeout: 5000
+#      retry-times: 3
+#      retry-interval: 1000
+#
+#  # 监控配置
+#  management:
+#    metrics:
+#      enabled: true
+#      prefix: emqx
+#    endpoints:
+#      web:
+#        exposure:
+#          include: health,metrics,info

+ 11 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/TestApplication.java

@@ -0,0 +1,11 @@
+package cn.hfln.emqx;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TestApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(TestApplication.class, args);
+    }
+} 

+ 26 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/annotation/MqttListenerTest.java

@@ -0,0 +1,26 @@
+package cn.hfln.emqx.annotation;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@SpringBootTest
+public class MqttListenerTest {
+
+    @Test
+    public void testMqttListener() {
+        log.info("开始测试MQTT监听器");
+        // 测试将在Spring上下文启动时自动执行
+    }
+
+    @Component
+    public static class TestMqttBean {
+        @MqttListener(topics = "test/topic", qos = 1)
+        public void handleMessage(String topic, MqttMessage message) {
+            log.info("收到MQTT消息 - 主题: {}, 消息: {}", topic, new String(message.getPayload()));
+        }
+    }
+} 

+ 211 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/test/java/cn/hfln/emqx/annotation/MqttPublisherListenerTest.java

@@ -0,0 +1,211 @@
+package cn.hfln.emqx.annotation;
+
+import cn.hfln.emqx.connector.ConnectionState;
+import cn.hfln.emqx.connector.EmqxConnector;
+import cn.hfln.emqx.context.MessageContext;
+import cn.hfln.emqx.publisher.MqttPublisher;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Slf4j
+@SpringBootTest(classes = MqttPublisherListenerTest.TestConfig.class)
+@TestPropertySource(properties = {
+    "spring.main.allow-bean-definition-overriding=true",
+    "emqx.server-uris[0]=tcp://8.130.28.21:1883",
+    "emqx.client-id=test-client",
+    "emqx.username=",
+    "emqx.password=",
+    "emqx.connection-timeout=5",
+    "emqx.keep-alive-interval=10",
+    "emqx.automatic-reconnect=true",
+    "emqx.max-reconnect-delay=1000",
+    "emqx.clean-session=true",
+    "emqx.max-inflight=100"
+})
+public class MqttPublisherListenerTest {
+
+    @SpringBootConfiguration
+    @EnableAutoConfiguration
+    @ComponentScan(basePackages = "cn.hfln.emqx")
+    static class TestConfig {
+        @Bean(name = "testMessageHandler")
+        public TestMessageHandler testMessageHandler() {
+            return new TestMessageHandler();
+        }
+    }
+
+    @Autowired
+    private EmqxConnector connector;
+
+    @Autowired
+    private MqttPublisher publisher;
+
+    @Autowired
+    @Qualifier("testMessageHandler")
+    private TestMessageHandler messageHandler;
+
+    @BeforeEach
+    void setUp() throws MqttException, InterruptedException {
+        try {
+            connector.connect();
+            // 等待连接建立和订阅完成
+            Thread.sleep(2000);
+            log.info("Connection state: {}", connector.getState());
+        } catch (Exception e) {
+            log.error("Error during setup", e);
+            throw e;
+        }
+    }
+
+    @AfterEach
+    void tearDown() {
+        try {
+            connector.disconnect();
+            // 等待断开连接
+            Thread.sleep(2000);
+        } catch (Exception e) {
+            log.error("Error during tearDown", e);
+        }
+    }
+
+    @Test
+    void testMessagePublishingAndReceiving() throws Exception {
+        // Given
+        String topic = "test/topic";
+        String payload = "test message";
+        CountDownLatch messageLatch = new CountDownLatch(1);
+
+        // When
+        messageHandler.setMessageLatch(messageLatch);
+        messageHandler.reset();
+        System.out.println("[testMessagePublishingAndReceiving] messageHandler hashCode: " + messageHandler.hashCode());
+        // 等待订阅建立
+        Thread.sleep(2000);
+        
+        // 发布消息
+        publisher.send(topic, payload.getBytes(), 1, false);
+        log.info("Published message to topic: {}", topic);
+
+        // Then
+        boolean messageReceived = messageLatch.await(10, TimeUnit.SECONDS);
+        assertTrue(messageReceived, "Message should be received within 10 seconds");
+        assertTrue(messageHandler.isCalled(), "Message handler should be called");
+    }
+
+    @Test
+    void testWildcardTopicMatching() throws Exception {
+        // Given
+        String topic = "test/device/status";
+        String payload = "device status";
+        CountDownLatch messageLatch = new CountDownLatch(1);
+
+        // When
+        messageHandler.setMessageLatch(messageLatch);
+        messageHandler.reset();
+        System.out.println("[testWildcardTopicMatching] messageHandler hashCode: " + messageHandler.hashCode());
+        // 等待订阅建立
+        Thread.sleep(2000);
+        
+        // 发布消息
+        publisher.send(topic, payload.getBytes(), 1, false);
+        log.info("Published message to topic: {}", topic);
+
+        // Then
+        boolean messageReceived = messageLatch.await(10, TimeUnit.SECONDS);
+        assertTrue(messageReceived, "Message should be received within 10 seconds");
+        assertTrue(messageHandler.isCalled(), "Message handler should be called");
+    }
+
+    @Test
+    void testRetainedMessage() throws Exception {
+        // Given
+        String topic = "test/retained/topic";
+        String payload = "retained message";
+        CountDownLatch messageLatch = new CountDownLatch(1);
+
+        // When
+        messageHandler.setMessageLatch(messageLatch);
+        messageHandler.reset();
+        System.out.println("[testRetainedMessage] messageHandler hashCode: " + messageHandler.hashCode());
+        // 等待订阅建立
+        Thread.sleep(2000);
+        
+        // 发布保留消息
+        publisher.send(topic, payload.getBytes(), 1, true);
+        log.info("Published retained message to topic: {}", topic);
+
+        // Then
+        boolean messageReceived = messageLatch.await(10, TimeUnit.SECONDS);
+        assertTrue(messageReceived, "Message should be received within 10 seconds");
+        assertTrue(messageHandler.isCalled(), "Message handler should be called");
+    }
+
+    @Component
+    public static class TestMessageHandler {
+        private final AtomicBoolean called = new AtomicBoolean(false);
+        private CountDownLatch messageLatch;
+
+        public void setMessageLatch(CountDownLatch messageLatch) {
+            this.messageLatch = messageLatch;
+        }
+
+        public void reset() {
+            called.set(false);
+        }
+
+        @MqttListener(topics = "test/topic", qos = 1, advanced = true)
+        public void handleMessage(MessageContext context) {
+            System.out.println("[handleMessage] this.hashCode: " + this.hashCode());
+            String message = new String(context.getMessage().getPayload());
+            log.info("Received message: {}", message);
+            called.set(true);
+            if (messageLatch != null) {
+                messageLatch.countDown();
+            }
+        }
+
+        @MqttListener(topics = "test/+/+/status", qos = 1, advanced = true)
+        public void handleWildcardMessage(MessageContext context) {
+            System.out.println("[handleWildcardMessage] this.hashCode: " + this.hashCode());
+            String message = new String(context.getMessage().getPayload());
+            log.info("Received wildcard message: {}", message);
+            called.set(true);
+            if (messageLatch != null) {
+                messageLatch.countDown();
+            }
+        }
+
+        @MqttListener(topics = "test/retained/topic", qos = 1, advanced = true)
+        public void handleRetainedMessage(MessageContext context) {
+            System.out.println("[handleRetainedMessage] this.hashCode: " + this.hashCode());
+            String message = new String(context.getMessage().getPayload());
+            log.info("Received retained message: {}", message);
+            called.set(true);
+            if (messageLatch != null) {
+                messageLatch.countDown();
+            }
+        }
+
+        public boolean isCalled() {
+            return called.get();
+        }
+    }
+} 

+ 15 - 0
hfln-framework-design-starter/emqx-spring-boot-starter/src/test/resources/application.yml

@@ -0,0 +1,15 @@
+emqx:
+  server-uri: tcp://8.130.28.21:1883
+  client-id-prefix: test-client
+  username: 
+  password: 
+  connection-timeout: 5
+  keep-alive-interval: 10
+  automatic-reconnect: true
+  max-reconnect-delay: 1000
+  clean-session: true
+  max-inflight: 100
+
+logging:
+  level:
+    cn.hfln.emqx: DEBUG 

+ 10 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/README.md

@@ -0,0 +1,10 @@
+
+```
+wideth:
+  knife4j:
+    doc:
+      base-package: com.example.demo.controller
+      title: 测试
+      description: 测试环境
+
+```

+ 43 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>hfln-framework-design-starter</artifactId>
+        <groupId>cn.hfln.framework</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>knife4j-doc-spring-boot-starter</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-micro-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 85 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/base/BaseSwaggerConfig.java

@@ -0,0 +1,85 @@
+package cn.hfln.framework.knife4j.doc.base;
+
+import com.google.common.collect.Lists;
+import org.springframework.context.annotation.Bean;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @USER: YangLiu
+ * @DESC:
+ */
+public abstract class BaseSwaggerConfig {
+    @Bean
+    public Docket createRestApi() {
+        SwaggerProperties swaggerProperties = swaggerProperties();
+        Docket docket = new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(apiInfo(swaggerProperties))
+                .select()
+                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getApiBasePackage()))
+                .paths(PathSelectors.any())
+                .build();
+        if (swaggerProperties.isEnableSecurity()) {
+            docket.securitySchemes(Lists.newArrayList(apiKey())).securityContexts(securityContexts());
+        }
+        return docket;
+    }
+
+    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
+        return new ApiInfoBuilder()
+                .title(swaggerProperties.getTitle())
+                .description(swaggerProperties.getDescription())
+                .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(), swaggerProperties.getContactEmail()))
+                .version(swaggerProperties.getVersion())
+                .build();
+    }
+
+    private ApiKey apiKey() {
+        return new ApiKey("Authorization", "Authorization", "header");
+    }
+
+
+    private List<ApiKey> securitySchemes() {
+        //设置请求头信息
+        List<ApiKey> result = new ArrayList<>();
+        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
+        result.add(apiKey);
+        return result;
+    }
+
+    private List<SecurityContext> securityContexts() {
+        //设置需要登录认证的路径
+        List<SecurityContext> result = new ArrayList<>();
+        result.add(getContextByPath());
+        return result;
+    }
+
+    private SecurityContext getContextByPath() {
+        return SecurityContext.builder()
+                .securityReferences(defaultAuth())
+                .forPaths(PathSelectors.regex("/*/.*"))
+                .build();
+    }
+
+    private List<SecurityReference> defaultAuth() {
+        List<SecurityReference> result = new ArrayList<>();
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        result.add(new SecurityReference("Authorization", authorizationScopes));
+        return result;
+    }
+
+    /**
+     * 自定义Swagger配置
+     */
+    public abstract SwaggerProperties swaggerProperties();
+}

+ 47 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/base/SwaggerProperties.java

@@ -0,0 +1,47 @@
+package cn.hfln.framework.knife4j.doc.base;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @USER: YangLiu
+ * @DESC:
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Builder
+public class SwaggerProperties {
+    /**
+     * API文档生成基础路径
+     */
+    private String apiBasePackage;
+    /**
+     * 是否要启用登录认证
+     */
+    private boolean enableSecurity;
+    /**
+     * 文档标题
+     */
+    private String title;
+    /**
+     * 文档描述
+     */
+    private String description;
+    /**
+     * 文档版本
+     */
+    private String version;
+    /**
+     * 文档联系人姓名
+     */
+    private String contactName;
+    /**
+     * 文档联系人网址
+     */
+    private String contactUrl;
+    /**
+     * 文档联系人邮箱
+     */
+    private String contactEmail;
+}

+ 91 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/configure/DocAutoConfigure.java

@@ -0,0 +1,91 @@
+package cn.hfln.framework.knife4j.doc.configure;
+
+import cn.hfln.framework.knife4j.doc.properties.DocProperties;
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import com.google.common.collect.Lists;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.Order;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.List;
+
+/**
+ * @USER: YangLiu
+ * knife4j 接口文档配置
+ */
+@Configuration
+@EnableSwagger2
+@EnableKnife4j
+@Import(BeanValidatorPluginsConfiguration.class)
+@EnableConfigurationProperties(DocProperties.class)
+@ConditionalOnProperty(value = "lnxx.knife4j.doc.enable", havingValue = "true", matchIfMissing = true)
+public class DocAutoConfigure {
+
+    private final DocProperties properties;
+
+    public DocAutoConfigure(DocProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    @Order(-1)
+    public Docket groupRestApi() {
+        Docket docket = new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(groupApiInfo())
+                .select()
+                .apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
+                .paths(PathSelectors.any())
+                .build();
+        if (properties.isEnableSecurity()) {
+            docket.securityContexts(Lists.newArrayList(securityContext()))
+                    .securitySchemes(Lists.newArrayList(apiKey()));
+        }
+        return docket;
+    }
+
+
+    private ApiInfo groupApiInfo() {
+        String description = String.format("<div style='font-size:%spx;color:%s;'>%s</div>",
+                properties.getDescriptionFontSize(), properties.getDescriptionColor(), properties.getDescription());
+        Contact contact = new Contact(properties.getName(), properties.getUrl(), properties.getEmail());
+        return new ApiInfoBuilder()
+                .title(properties.getTitle())
+                .description(description)
+                .termsOfServiceUrl(properties.getTermsOfServiceUrl())
+                .contact(contact)
+                .license(properties.getLicense())
+                .licenseUrl(properties.getLicenseUrl())
+                .version(properties.getVersion())
+                .build();
+    }
+
+    private ApiKey apiKey() {
+        return new ApiKey("Authorization", "Authorization", "header");
+    }
+
+    private SecurityContext securityContext() {
+        return SecurityContext.builder()
+                .securityReferences(defaultAuth())
+                .forPaths(PathSelectors.regex("/.*"))
+                .build();
+    }
+
+    List<SecurityReference> defaultAuth() {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        return Lists.newArrayList(new SecurityReference("Authorization", authorizationScopes));
+    }
+}

+ 91 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/java/cn/hfln/framework/knife4j/doc/properties/DocProperties.java

@@ -0,0 +1,91 @@
+package cn.hfln.framework.knife4j.doc.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @USER: YangLiu
+ */
+@ConfigurationProperties(prefix = "lnxx.knife4j.doc")
+@Data
+public class DocProperties {
+
+    /**
+     * 是否开启doc功能
+     */
+    private Boolean enable = true;
+    /**
+     * 接口扫描路径,如Controller路径
+     */
+    private String basePackage;
+
+
+    /**
+     * 是否要启用登录认证
+     */
+    private boolean enableSecurity = false;
+
+    /**
+     * 文档标题
+     */
+    private String title;
+    /**
+     * 文档描述
+     */
+    private String description;
+    /**
+     * 文档描述颜色
+     */
+    private String descriptionColor = "#42b983";
+    /**
+     * 文档描述字体大小
+     */
+    private String descriptionFontSize = "14";
+    /**
+     * 服务url
+     */
+    private String termsOfServiceUrl;
+    /**
+     * 联系方式:姓名
+     */
+    private String name;
+    /**
+     * 联系方式:个人网站url
+     */
+    private String url;
+    /**
+     * 联系方式:邮箱
+     */
+    private String email;
+    /**
+     * 协议
+     */
+    private String license;
+    /**
+     * 协议地址
+     */
+    private String licenseUrl;
+    /**
+     * 版本
+     */
+    private String version;
+
+    @Override
+    public String toString() {
+        return "DocProperties{" +
+                "enable=" + enable +
+                ", basePackage='" + basePackage + '\'' +
+                ", title='" + title + '\'' +
+                ", description='" + description + '\'' +
+                ", descriptionColor='" + descriptionColor + '\'' +
+                ", descriptionFontSize='" + descriptionFontSize + '\'' +
+                ", termsOfServiceUrl='" + termsOfServiceUrl + '\'' +
+                ", name='" + name + '\'' +
+                ", url='" + url + '\'' +
+                ", email='" + email + '\'' +
+                ", license='" + license + '\'' +
+                ", licenseUrl='" + licenseUrl + '\'' +
+                ", version='" + version + '\'' +
+                '}';
+    }
+}

+ 3 - 0
hfln-framework-design-starter/knife4j-doc-spring-boot-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.hfln.framework.knife4j.doc.configure.DocAutoConfigure

+ 13 - 0
hfln-framework-design-starter/minio-spring-boot-starter/README.md

@@ -0,0 +1,13 @@
+
+```
+wideth:
+  minio:
+    enabled: true  # 默认true
+    secure: false  # 是否启动https访问, 默认 false
+    endpoint: http://192.168.3.23/ 
+    port: 9000     # API端口
+    accessKey: admin
+    secretKey: minio123321
+    default-bucket: test  # 默认桶,如不存在服务启动后将自动创建
+
+```

+ 47 - 0
hfln-framework-design-starter/minio-spring-boot-starter/pom.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.hfln.framework</groupId>
+        <artifactId>hfln-framework-design-starter</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>minio-spring-boot-starter</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 83 - 0
hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/configure/MinioAutoConfig.java

@@ -0,0 +1,83 @@
+package cn.hfln.framework.minio.configure;
+
+import cn.hfln.framework.minio.properties.MinioProperties;
+import cn.hfln.framework.minio.service.MinioBucketService;
+import cn.hfln.framework.minio.service.MinioObjectService;
+import io.minio.MinioClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+/**
+ * MinIO配置
+ *
+ * @author yangliu
+ **/
+@Configuration
+@EnableConfigurationProperties({MinioProperties.class})
+@ConditionalOnProperty(name = "lnxx.minio.enabled", matchIfMissing = true)
+public class MinioAutoConfig {
+
+    private final MinioProperties minio;
+
+    public MinioAutoConfig(MinioProperties minioProperties) {
+        if (minioProperties.getDefaultBucket() != null) {
+            validateBucketName(minioProperties.getDefaultBucket());
+        }
+        this.minio = minioProperties;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MinioClient minioClient() {
+        // Create a minioClient with the MinIO server playground, its access key and secret key.
+        return MinioClient.builder()
+                .endpoint(minio.getEndpoint(), minio.getPort(), minio.isSecure())
+                .credentials(minio.getAccessKey(), minio.getSecretKey())
+                .build();
+    }
+
+    @Bean(initMethod = "init")
+    @ConditionalOnMissingBean
+    public MinioBucketService minioBucketService(MinioClient minioClient) {
+        if (StringUtils.isEmpty(minio.getDefaultBucket())) {
+            return new MinioBucketService(minioClient);
+        }
+        return new MinioBucketService(minioClient, minio.getDefaultBucket());
+    }
+
+    @Bean(initMethod = "init")
+    @ConditionalOnMissingBean
+    public MinioObjectService minIoObjectService(MinioClient minioClient) {
+        if (StringUtils.isEmpty(minio.getDefaultBucket())) {
+            return new MinioObjectService(minioClient);
+        }
+        return new MinioObjectService(minioClient, minio);
+    }
+
+    /**
+     * 验证桶名称
+     *
+     * @param name 桶名
+     */
+    private static void validateBucketName(String name) {
+        // Bucket names cannot be no less than 3 and no more than 63 characters long.
+        if (name.length() < 3 || name.length() > 63) {
+            throw new IllegalArgumentException(name + " : " + "defaultBucket name must be at least 3 and no more than 63 characters long");
+        }
+        // Successive periods in bucket names are not allowed.
+        if (name.contains("..")) {
+            String msg = "defaultBucket name cannot contain successive periods.";
+            throw new IllegalArgumentException(name + " : " + msg);
+        }
+        // Bucket names should be dns compatible.
+        if (!name.matches("^[a-z0-9][a-z0-9\\.\\-]+[a-z0-9]$")) {
+            String msg = "defaultBucket name does not follow Amazon S3 standards.";
+            throw new IllegalArgumentException(name + " : " + msg);
+        }
+    }
+
+}

+ 52 - 0
hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/properties/MinioProperties.java

@@ -0,0 +1,52 @@
+package cn.hfln.framework.minio.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * MinIO属性配置
+ *
+ * @author yangliu
+ **/
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "lnxx.minio")
+public class MinioProperties {
+
+    /**
+     * Minio 启用
+     */
+    private boolean enabled = true;
+
+    /**
+     * Minio 启动 https
+     */
+    private boolean secure = false;
+
+    /**
+     * Minio 服务地址
+     */
+    private String endpoint;
+
+    /**
+     * Minio port
+     */
+    private int port;
+
+    /**
+     * Minio ACCESS_KEY
+     */
+    private String accessKey;
+
+    /**
+     * Minio SECRET_KEY
+     */
+    private String secretKey;
+
+    /**
+     * Minio DEFAULT_BUCKET ,可选
+     */
+    private String defaultBucket;
+}

+ 173 - 0
hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/service/MinioBucketService.java

@@ -0,0 +1,173 @@
+package cn.hfln.framework.minio.service;
+
+import io.minio.*;
+import io.minio.messages.Item;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MinIO桶相关操作
+ *
+ * @author yangliu
+ **/
+@Slf4j
+public class MinioBucketService {
+    private final MinioClient minioClient;
+
+    private String defaultBucket;
+
+    public MinioBucketService(MinioClient minioClient) {
+        this.minioClient = minioClient;
+    }
+
+    public MinioBucketService(MinioClient minioClient, String defaultBucket) {
+        this.minioClient = minioClient;
+        this.defaultBucket = defaultBucket;
+    }
+
+    /**
+     * 初始化默认桶
+     */
+    public void init() {
+        if (!StringUtils.isEmpty(defaultBucket)) {
+            try {
+                if (!bucketExists()) {
+                    makeBucket(defaultBucket);
+                }
+            } catch (Exception e) {
+                log.info("Create Default Bucket Error:{}", e.getMessage());
+            }
+            log.info("defaultBucket: {}", defaultBucket);
+        }
+        log.info("MinioBucketService Init Success!");
+    }
+
+    /**
+     * 验证桶名称规则
+     *
+     * @param name 桶名称
+     */
+    protected void validateBucketName(String name) {
+        validateNotNull(name);
+        // Bucket names cannot be no less than 3 and no more than 63 characters long.
+        if (name.length() < 3 || name.length() > 63) {
+            throw new IllegalArgumentException(
+                    name + " : " + "bucket name must be at least 3 and no more than 63 characters long");
+        }
+        // Successive periods in bucket names are not allowed.
+        if (name.contains("..")) {
+            String msg = "bucket name cannot contain successive periods. For more information refer "
+                    + "http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html";
+            throw new IllegalArgumentException(name + " : " + msg);
+        }
+        // Bucket names should be dns compatible.
+        if (!name.matches("^[a-z0-9][a-z0-9\\.\\-]+[a-z0-9]$")) {
+            String msg = "bucket name does not follow Amazon S3 standards. For more information refer "
+                    + "http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html";
+            throw new IllegalArgumentException(name + " : " + msg);
+        }
+    }
+
+    /**
+     * 验证是否配置默认桶名称
+     *
+     * @param arg
+     */
+    protected void validateNotNull(Object arg) {
+        if (arg == null) {
+            throw new IllegalArgumentException("bucket name must not be null,Must be configured dist.minio.config.default-bucket=");
+        }
+    }
+
+    /**
+     * 指定桶-桶是否存在
+     *
+     * @return 是否存在
+     */
+    public boolean bucketExists() {
+        validateBucketName(defaultBucket);
+        return bucketExists(defaultBucket);
+    }
+
+    /**
+     * 判断桶是否存在
+     *
+     * @param bucketName 桶名
+     * @return 是否存在
+     */
+    @SneakyThrows
+    public boolean bucketExists(String bucketName) {
+        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+    }
+
+    /**
+     * 创建新的桶
+     *
+     * @param bucketName 桶名
+     * @return 是否创建成功
+     */
+    public boolean makeBucket(String bucketName) {
+        try {
+            boolean isExist = bucketExists(bucketName);
+            if (!isExist) {
+                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("{}", e.getMessage(), e);
+            return false;
+        }
+    }
+
+
+    /**
+     * 获取桶中的对象名称列表
+     *
+     * @return 对象列表
+     */
+    public List<String> listObjectNames() {
+        validateBucketName(defaultBucket);
+        return listObjectNames(defaultBucket);
+    }
+
+
+    /**
+     * 获取桶中的对象名称列表
+     *
+     * @param bucketName 桶名
+     * @return 对象列表
+     */
+    @SneakyThrows
+    public List<String> listObjectNames(String bucketName) {
+        List<String> listObjectNames = new ArrayList<>();
+        Iterable<Result<Item>> myObjects = listObjects(bucketName);
+        if (null == myObjects) {
+            return null;
+        }
+        for (Result<Item> result : myObjects) {
+            Item item = result.get();
+            listObjectNames.add(item.objectName());
+        }
+        return listObjectNames;
+    }
+
+    /**
+     * 桶中的对象列表
+     *
+     * @param bucketName 桶名
+     * @return 对象列表
+     */
+    @SneakyThrows
+    public Iterable<Result<Item>> listObjects(String bucketName) {
+        boolean flag = bucketExists(bucketName);
+        if (flag) {
+            return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
+        }
+        return null;
+    }
+
+}

+ 663 - 0
hfln-framework-design-starter/minio-spring-boot-starter/src/main/java/cn/hfln/framework/minio/service/MinioObjectService.java

@@ -0,0 +1,663 @@
+package cn.hfln.framework.minio.service;
+
+import cn.hfln.framework.minio.properties.MinioProperties;
+import com.google.common.io.ByteStreams;
+import io.minio.*;
+import io.minio.http.Method;
+import io.minio.messages.Item;
+import io.minio.messages.Tags;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+
+/**
+ * MinIO对象相关操作
+ *
+ * @author yangliu
+ **/
+@Slf4j
+public class MinioObjectService {
+
+    private final MinioClient minioClient;
+
+    private String url;
+
+    private String defaultBucket;
+
+    public MinioObjectService(MinioClient minioClient) {
+        this.minioClient = minioClient;
+    }
+
+    public MinioObjectService(MinioClient minioClient, MinioProperties minIo) {
+        this.minioClient = minioClient;
+        this.url = minIo.getEndpoint() + ":" + minIo.getPort();
+        this.defaultBucket = minIo.getDefaultBucket();
+    }
+
+    public void init() {
+        log.info("MinIoObjectService Init Success!");
+    }
+
+
+    /**
+     * 默认桶-创建对象(文件夹or目录)
+     *
+     * @param folderName 对象id(存储名称)
+     * @return
+     */
+    public ObjectWriteResponse putObjectByFolder(String folderName) {
+        return putObjectByFolder(defaultBucket, folderName);
+    }
+
+    /**
+     * 指定桶-创建对象(文件夹or目录
+     *
+     * @param bucketName 桶名
+     * @param folderName 对象id(存储名称)
+     * @return
+     */
+    @SneakyThrows
+    public ObjectWriteResponse putObjectByFolder(String bucketName, String folderName) {
+        return minioClient.putObject(
+                PutObjectArgs.builder().bucket(bucketName).object(folderName + "/").stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build()
+        );
+    }
+
+
+    /**
+     * 默认桶-对象上传-本地对象路径
+     *
+     * @param objectName 对象id(存储名称)
+     * @param folderPath 本地对象路径
+     */
+    public void putObjectFolder(String objectName, String folderPath) {
+        putObjectFolder(defaultBucket, objectName, folderPath);
+    }
+
+
+    /**
+     * 默认桶-对象上传-本地对象路径
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象id(存储名称)
+     * @param folderPath 本地对象路径
+     */
+    public void putObjectFolder(String bucketName, String objectName, String folderPath) {
+        if (Files.isDirectory(Paths.get(folderPath))) {
+            File dir = new File(folderPath);
+            dir.list();
+            uploadFolder(bucketName, objectName, dir);
+        }
+    }
+
+    /**
+     * 默认桶-对象上传-本地对象路径
+     *
+     * @param objectName 对象id(存储名称)
+     * @param filename   本地对象路径
+     * @return
+     */
+    public ObjectWriteResponse putObject(String objectName, String filename) {
+        return putObject(defaultBucket, objectName, filename);
+    }
+
+
+    /**
+     * 指定桶-对象上传-本地对象路径
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象id(存储名称)
+     * @param filename   本地对象路径
+     * @return
+     */
+    @SneakyThrows
+    public ObjectWriteResponse putObject(String bucketName, String objectName, String filename) {
+        return minioClient.uploadObject(
+                UploadObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .filename(filename)
+                        .build());
+    }
+
+
+    /**
+     * 默认桶-对象上传-multipartFile
+     * 单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证
+     *
+     * @param objectName    存储桶里的对象名称
+     * @param multipartFile 文件
+     * @return 文件永久访问地址
+     */
+    public String putObjectByMultipartFile(String objectName, MultipartFile multipartFile) {
+        putObjectByMultipartFile(defaultBucket, objectName, multipartFile);
+        return url + "/" + defaultBucket + "/" + objectName;
+    }
+
+
+    /**
+     * 指定桶-对象上传-multipartFile
+     * 单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证
+     *
+     * @param bucketName    桶名
+     * @param objectName    存储桶里的对象名称
+     * @param multipartFile 文件
+     * @return 文件永久访问地址
+     */
+    public String putObjectByMultipartFile(String bucketName, String objectName, MultipartFile multipartFile) {
+        try (InputStream inputStream = multipartFile.getInputStream()) {
+            minioClient.putObject(PutObjectArgs.builder()
+                    .bucket(bucketName)
+                    .object(objectName)
+                    .stream(inputStream, multipartFile.getSize(), -1)
+                    .contentType(multipartFile.getContentType())
+                    .build());
+            return url + "/" + defaultBucket + "/" + objectName;
+        } catch (Exception e) {
+            log.error("{}", e.getMessage(), e);
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+
+    /**
+     * 默认桶-上传对象-InputStream
+     * 单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证
+     *
+     * @param objectName 对象名称
+     * @param stream     对象流
+     * @return 文件永久访问地址
+     */
+    public String putObject(String objectName, InputStream stream) {
+        putObject(defaultBucket, objectName, stream);
+        return url + "/" + defaultBucket + "/" + objectName;
+    }
+
+
+    /**
+     * 指定桶-上传对象-InputStream
+     * 单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象名称
+     * @param stream     对象流
+     * @return 文件永久访问地址
+     */
+    @SneakyThrows
+    public String putObject(String bucketName, String objectName, InputStream stream) {
+        minioClient.putObject(PutObjectArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .stream(stream, stream.available(), -1)
+                .build());
+        stream.close();
+        return url + "/" + defaultBucket + "/" + objectName;
+    }
+
+
+    /**
+     * 默认桶-上传对象-InputStream
+     *
+     * @param objectName  对象名称
+     * @param stream      对象流
+     * @param contentType 内容类型
+     * @return
+     */
+    public ObjectWriteResponse putObject(String objectName, InputStream stream, String contentType) {
+        return putObject(defaultBucket, objectName, stream, contentType);
+    }
+
+
+    /**
+     * 指定桶-上传对象-InputStream
+     * 单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证
+     *
+     * @param bucketName  桶名
+     * @param objectName  对象名称
+     * @param stream      对象流
+     * @param contentType 内容类型
+     * @return
+     */
+    public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream stream, String contentType) {
+        try {
+            ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder()
+                    .bucket(bucketName)
+                    .object(objectName)
+                    .stream(stream, stream.available(), -1)
+                    .contentType(contentType)
+                    .build());
+            return objectWriteResponse;
+        } catch (Exception e) {
+            log.error("{}", e.getMessage(), e);
+            throw new RuntimeException(e.getMessage());
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    /**
+     * 默认桶-获取对象流
+     *
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public GetObjectResponse getObject(String objectName) {
+        return getObject(defaultBucket, objectName);
+    }
+
+
+    /**
+     * 指定桶-获取对象流
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    @SneakyThrows
+    public GetObjectResponse getObject(String bucketName, String objectName) {
+        return minioClient.getObject(
+                GetObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+
+    /**
+     * 默认桶-对象下载-流-response方式
+     *
+     * @param objectName 存储桶里的对象名称
+     * @param response
+     */
+    public void downloadObject(String objectName, HttpServletResponse response) {
+        downloadObject(defaultBucket, objectName, response);
+    }
+
+
+    /**
+     * 指定桶-对象下载-流-response方式
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @param response
+     */
+    @SneakyThrows
+    public void downloadObject(String bucketName, String objectName, HttpServletResponse response) {
+        // 设置编码
+        response.setCharacterEncoding("UTF-8");
+        response.setHeader("Content-Disposition", "attachment;objectName=" + URLEncoder.encode(objectName, "utf-8"));
+        ServletOutputStream os = response.getOutputStream();
+        GetObjectResponse is = minioClient.getObject(
+                GetObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build()
+        );
+        ByteStreams.copy(is, os);
+        os.flush();
+    }
+
+
+    /**
+     * 默认桶-下载对象-下载到本服务器
+     *
+     * @param objectName 存储桶里的对象名称
+     * @param filename   对象存储位置
+     */
+    public void downloadObject(String objectName, String filename) {
+        downloadObject(defaultBucket, objectName, filename);
+    }
+
+    /**
+     * 指定桶-下载对象-下载到本服务器
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @param filename   对象存储位置
+     */
+    @SneakyThrows
+    public void downloadObject(String bucketName, String objectName, String filename) {
+        minioClient.downloadObject(DownloadObjectArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .filename(filename)
+                .build());
+    }
+
+    /**
+     * 默认桶-获取对象信息和对象的元数据
+     * 如果不存在, 抛出异常
+     *
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public StatObjectResponse statObject(String objectName) {
+        return statObject(defaultBucket, objectName);
+    }
+
+    /**
+     * 指定桶-获取对象信息和对象的元数据
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    @SneakyThrows
+    public StatObjectResponse statObject(String bucketName, String objectName) {
+        return minioClient.statObject(StatObjectArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .build());
+    }
+
+    /**
+     * 默认桶-获取对象的元数据
+     *
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public Map<String, String> getObjectUserMetadata(String objectName) {
+        return getObjectUserMetadata(defaultBucket, objectName);
+    }
+
+    /**
+     * 指定桶-获取对象的元数据
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    @SneakyThrows
+    public Map<String, String> getObjectUserMetadata(String bucketName, String objectName) {
+        return minioClient.statObject(StatObjectArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .build()).userMetadata();
+    }
+
+    /**
+     * 默认桶-获取对象永久网址-URL
+     * 必须设置桶策略为可读(下载),只写权限,用户直接访问地址是查看不了的
+     *
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public String getObjectURL(String objectName) {
+        return getObjectURL(defaultBucket, objectName);
+    }
+
+    /**
+     * 指定桶-获取对象永久网址-URL
+     * 必须设置桶策略为可读(下载)。只写权限,用户直接访问地址无法查看
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public String getObjectURL(String bucketName, String objectName) {
+        statObject(bucketName, objectName);
+        return url + "/" + bucketName + "/" + objectName;
+    }
+
+    /**
+     * 默认桶-获取预签名对象网址-URL
+     * 默认分享链接地址失效时间为7天
+     *
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    public String getObjectShareLink(String objectName) {
+        return getObjectShareLink(defaultBucket, objectName);
+    }
+
+    /**
+     * 指定桶-获取预签名对象网址-URL
+     * 默认分享链接地址失效时间为7天
+     *
+     * @param bucketName 桶名
+     * @param objectName 存储桶里的对象名称
+     * @return
+     */
+    @SneakyThrows
+    public String getObjectShareLink(String bucketName, String objectName) {
+        return minioClient.getPresignedObjectUrl(
+                GetPresignedObjectUrlArgs.builder()
+                        .method(Method.GET)
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+    /**
+     * 默认桶-获取对象外链-url-自定义设置分享过期时间
+     * 设置有效期的分享链接(共享文件时间最大7天)。生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param expiry     失效时间(以秒为单位),默认是7天,不得大于七天
+     * @return
+     */
+    public String getObjectShareLink(String objectName, int expiry) {
+        return getObjectShareLink(defaultBucket, objectName, expiry);
+    }
+
+
+    /**
+     * 指定桶-获取对象外链-url-自定义设置分享过期时间
+     * 设置有效期的分享链接(共享文件时间最大7天)。生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param expiry     失效时间(以秒为单位),默认是7天,不得大于七天
+     * @return
+     */
+    @SneakyThrows
+    public String getObjectShareLink(String bucketName, String objectName, int expiry) {
+        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .method(Method.GET)
+                .expiry(expiry)
+                .build());
+    }
+
+    /**
+     * 默认桶-根据对象前缀查询对象
+     *
+     * @param prefix    桶中对象的前缀 默认 空字符串
+     * @param recursive 是否递归子目录
+     * @return
+     */
+    public List<Item> getAllObjectsByPrefix(String prefix, boolean recursive) {
+        return getAllObjectsByPrefix(defaultBucket, prefix, recursive);
+    }
+
+    /**
+     * 指定桶-根据对象前缀查询对象
+     *
+     * @param bucketName 桶名
+     * @param prefix     桶中对象的前缀 默认 空字符串
+     * @param recursive  是否递归子目录
+     * @return
+     */
+    @SneakyThrows
+    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
+        List<Item> list = new ArrayList<>();
+        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder()
+                .bucket(bucketName)
+                .prefix(prefix)
+                .recursive(recursive)
+                .build());
+        if (objectsIterator != null) {
+            Iterator<Result<Item>> iterator = objectsIterator.iterator();
+            if (iterator != null) {
+                while (iterator.hasNext()) {
+                    Result<Item> result = iterator.next();
+                    Item item = result.get();
+                    list.add(item);
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 默认桶-通过复制已存在对象来创建对象
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param source     已存在的源对象
+     * @return
+     */
+    public ObjectWriteResponse copyObject(String objectName, CopySource source) {
+        return copyObject(defaultBucket, objectName, source);
+    }
+
+
+    /**
+     * 指定桶-通过服务器端从另一个对象复制数据来创建对象
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param source     已存在的源对象
+     * @return
+     */
+    @SneakyThrows
+    public ObjectWriteResponse copyObject(String bucketName, String objectName, CopySource source) {
+        return minioClient.copyObject(
+                CopyObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .source(source)
+                        .build());
+    }
+
+    /**
+     * 默认桶-设置对象的标签
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param tags       标签
+     */
+    public void setObjectTags(String objectName, Tags tags) {
+        setObjectTags(defaultBucket, objectName, tags);
+    }
+
+    /**
+     * 指定桶-设置对象的标签
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @param tags       标签
+     */
+    @SneakyThrows
+    public void setObjectTags(String bucketName, String objectName, Tags tags) {
+        minioClient.setObjectTags(
+                SetObjectTagsArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .tags(tags).build());
+    }
+
+    /**
+     * 默认桶-获取对象的标签
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     */
+    public void getObjectTags(String objectName) {
+        getObjectTags(defaultBucket, objectName);
+    }
+
+
+    /**
+     * 指定桶-获取对象的标签
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     * @return
+     */
+    @SneakyThrows
+    public Tags getObjectTags(String bucketName, String objectName) {
+        return minioClient.getObjectTags(
+                GetObjectTagsArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+
+    /**
+     * 默认桶-删除对象标签
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     */
+    public void deleteObjectTags(String objectName) {
+        deleteObjectTags(defaultBucket, objectName);
+    }
+
+
+    /**
+     * 指定桶-删除对象标签
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     */
+    @SneakyThrows
+    public void deleteObjectTags(String bucketName, String objectName) {
+        minioClient.deleteObjectTags(
+                DeleteObjectTagsArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+
+    /**
+     * 默认桶-删除对象-单个
+     *
+     * @param objectName 对象ID(存储桶里的对象名称)
+     */
+    public void deleteObject(String objectName) {
+        deleteObject(defaultBucket, objectName);
+    }
+
+    /**
+     * 指定桶-删除对象-单个
+     *
+     * @param bucketName 桶名
+     * @param objectName 对象ID(存储桶里的对象名称)
+     */
+    @SneakyThrows
+    public void deleteObject(String bucketName, String objectName) {
+        minioClient.removeObject(
+                RemoveObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+    @SneakyThrows
+    private void uploadFolder(String bucketName, String parentName, File file) {
+        for (File fileElem : Objects.requireNonNull(file.listFiles())) {
+            if (Files.isDirectory(Paths.get(fileElem.toURI()))) {
+                uploadFolder(bucketName, parentName + "/" + fileElem.getName(), fileElem);
+            } else {
+                putObject(bucketName, parentName + "/" + fileElem.getName(), fileElem.getAbsolutePath());
+            }
+        }
+    }
+
+}

+ 2 - 0
hfln-framework-design-starter/minio-spring-boot-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.hfln.framework.minio.configure.MinioAutoConfig

+ 234 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/README.md

@@ -0,0 +1,234 @@
+# MQTT Spring Boot Starter
+
+一个基于 Spring Boot 的 MQTT 客户端启动器,提供了简单易用的 MQTT 消息发布订阅功能。
+
+## 特性
+
+- 支持 MQTT 消息的发布和订阅
+- 支持通配符主题订阅(+ 和 #)
+- 支持 JSON 消息的自动转换
+- 支持消息拦截器
+- 支持连接状态监听
+- 支持消息监听器
+- 支持 SSL/TLS 加密连接
+- 支持自动重连
+- 支持消息持久化
+
+## 快速开始
+
+### 1. 添加依赖
+
+```xml
+<dependency>
+    <groupId>cn.hfln.framework</groupId>
+    <artifactId>mqtt-spring-boot-starter</artifactId>
+    <version>${latest.version}</version>
+</dependency>
+```
+
+### 2. 配置属性
+
+在 `application.yml` 中添加以下配置:
+
+```yaml
+mqtt:
+  enabled: true
+  broker: tcp://localhost:1883
+  client-id: ${spring.application.name}
+  username: your-username
+  password: your-password
+  timeout: 30
+  keepalive: 60
+  clean-session: true
+  automatic-reconnect: true
+  max-reconnect-delay: 10000
+  connection-timeout: 30
+  max-message-size: 1048576
+  ssl: false
+  # SSL 配置(如果需要)
+  ssl-ca-path: /path/to/ca.crt
+  ssl-client-cert-path: /path/to/client.crt
+  ssl-client-key-path: /path/to/client.key
+  ssl-client-key-password: your-key-password
+```
+
+### 3. 使用示例
+
+#### 3.1 发送消息
+
+```java
+@Autowired
+private MqttTemplate mqttTemplate;
+
+// 发送字符串消息
+mqttTemplate.send("test/topic", "Hello MQTT!");
+
+// 发送 JSON 消息
+TestMessage message = new TestMessage("test", 123);
+mqttTemplate.sendJson("test/json", message);
+```
+
+#### 3.2 订阅消息
+
+```java
+@Component
+public class MqttMessageHandler {
+    
+    @MqttSubscribe(topic = "test/+/topic")
+    public void handleStringMessage(String message) {
+        System.out.println("Received message: " + message);
+    }
+    
+    @MqttSubscribe(topic = "test/json")
+    public void handleJsonMessage(TestMessage message) {
+        System.out.println("Received JSON message: " + message);
+    }
+}
+```
+
+#### 3.3 自定义消息拦截器
+
+```java
+@Component
+public class CustomMqttMessageInterceptor implements MqttMessageInterceptor {
+    
+    @Override
+    public boolean preSend(String topic, MqttMessage message) {
+        // 发送消息前的处理
+        return true;
+    }
+    
+    @Override
+    public void postSend(String topic, MqttMessage message) {
+        // 发送消息后的处理
+    }
+    
+    @Override
+    public boolean preReceive(String topic, MqttMessage message) {
+        // 接收消息前的处理
+        return true;
+    }
+    
+    @Override
+    public void postReceive(String topic, MqttMessage message) {
+        // 接收消息后的处理
+    }
+}
+```
+
+#### 3.4 连接状态监听
+
+```java
+@Component
+public class CustomMqttConnectionListener implements MqttConnectionListener {
+    
+    @Override
+    public void onConnected() {
+        System.out.println("MQTT client connected");
+    }
+    
+    @Override
+    public void onDisconnected() {
+        System.out.println("MQTT client disconnected");
+    }
+    
+    @Override
+    public void onConnectionLost(Throwable cause) {
+        System.out.println("MQTT connection lost: " + cause.getMessage());
+    }
+}
+```
+
+## 高级特性
+
+### 1. 通配符主题
+
+支持 MQTT 通配符主题订阅:
+
+- `+`:匹配单层任意字符
+- `#`:匹配多层任意字符
+
+示例:
+```java
+@MqttSubscribe(topic = "test/+/topic")  // 匹配 test/1/topic, test/2/topic 等
+@MqttSubscribe(topic = "test/#")        // 匹配 test/ 下的所有主题
+```
+
+### 2. 消息转换
+
+支持自动的 JSON 消息转换:
+
+```java
+@MqttSubscribe(topic = "test/json")
+public void handleJsonMessage(TestMessage message) {
+    // message 会自动从 JSON 转换为 TestMessage 对象
+}
+```
+
+### 3. 异步消息发送
+
+支持异步消息发送:
+
+```java
+mqttTemplate.send("test/topic", "Hello MQTT!")
+    .thenRun(() -> System.out.println("Message sent successfully"))
+    .exceptionally(throwable -> {
+        System.out.println("Failed to send message: " + throwable.getMessage());
+        return null;
+    });
+```
+
+## 配置说明
+
+| 配置项 | 说明 | 默认值 |
+|--------|------|--------|
+| mqtt.enabled | 是否启用 MQTT | true |
+| mqtt.broker | MQTT 服务器地址 | - |
+| mqtt.client-id | 客户端 ID | ${spring.application.name} |
+| mqtt.username | 用户名 | - |
+| mqtt.password | 密码 | - |
+| mqtt.timeout | 超时时间(秒) | 30 |
+| mqtt.keepalive | 保活时间(秒) | 60 |
+| mqtt.clean-session | 是否清除会话 | true |
+| mqtt.automatic-reconnect | 是否自动重连 | true |
+| mqtt.max-reconnect-delay | 最大重连延迟(毫秒) | 10000 |
+| mqtt.connection-timeout | 连接超时时间(秒) | 30 |
+| mqtt.max-message-size | 最大消息大小(字节) | 1048576 |
+| mqtt.ssl | 是否启用 SSL | false |
+| mqtt.ssl-ca-path | CA 证书路径 | - |
+| mqtt.ssl-client-cert-path | 客户端证书路径 | - |
+| mqtt.ssl-client-key-path | 客户端密钥路径 | - |
+| mqtt.ssl-client-key-password | 客户端密钥密码 | - |
+
+## 注意事项
+
+1. 确保 MQTT 服务器地址配置正确
+2. 如果使用 SSL/TLS,确保证书配置正确
+3. 建议在生产环境中启用自动重连功能
+4. 注意设置合适的消息大小限制
+5. 建议使用唯一的客户端 ID,避免冲突
+
+## 常见问题
+
+1. 连接失败
+   - 检查服务器地址是否正确
+   - 检查用户名密码是否正确
+   - 检查网络连接是否正常
+
+2. 消息发送失败
+   - 检查客户端是否已连接
+   - 检查主题格式是否正确
+   - 检查消息大小是否超限
+
+3. 消息接收失败
+   - 检查订阅主题是否正确
+   - 检查消息格式是否匹配
+   - 检查处理方法参数类型是否正确
+
+## 贡献指南
+
+欢迎提交 Issue 和 Pull Request。
+
+## 许可证
+
+本项目采用 MIT 许可证。 

+ 105 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/pom.xml

@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.hfln.framework</groupId>
+        <artifactId>hfln-framework-design-starter</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mqtt-spring-boot-starter</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>1.2.5</version>
+        </dependency>
+
+        <!--        <dependency>-->
+        <!--            <groupId>com.hivemq</groupId>-->
+        <!--            <artifactId>hivemq-mqtt-client</artifactId>-->
+        <!--            <version>1.2.1</version>-->
+        <!--        </dependency>-->
+
+        <!-- slf4j: 2.x -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.36</version>
+        </dependency>
+
+        <!-- spring-boot: 2.x -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <!-- optional: configuration-processor -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <!-- optional: jackson -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>5.9.0</version>
+            <optional>true</optional>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Spring Boot Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+        <!-- JUnit Jupiter -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.6.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.6.3</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Logging -->
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.11</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+</project>

+ 18 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/annotation/MqttSubscribe.java

@@ -0,0 +1,18 @@
+package cn.hfln.framework.mqtt.annotation;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MqttSubscribe {
+    /**
+     * MQTT主题
+     */
+    String topic();
+
+    /**
+     * QoS级别 (0, 1, 2)
+     */
+    int qos() default 0;
+} 

+ 23 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttAutoConfiguration.java

@@ -0,0 +1,23 @@
+package cn.hfln.framework.mqtt.config;
+
+import cn.hfln.framework.mqtt.converter.JsonMessageConverter;
+import cn.hfln.framework.mqtt.converter.MessageConverter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Configuration
+@ConditionalOnProperty(prefix = "mqtt", name = "enabled", havingValue = "true", matchIfMissing = true)
+@EnableConfigurationProperties(MqttProperties.class)
+@Import(MqttClientConfig.class)
+public class MqttAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MessageConverter messageConverter() {
+        return new JsonMessageConverter();
+    }
+} 

+ 51 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttClientConfig.java

@@ -0,0 +1,51 @@
+package cn.hfln.framework.mqtt.config;
+
+import cn.hfln.framework.mqtt.interceptor.MqttMessageInterceptor;
+import cn.hfln.framework.mqtt.listener.MqttConnectionListener;
+import cn.hfln.framework.mqtt.listener.MqttMessageListener;
+import cn.hfln.framework.mqtt.handler.MqttMessageHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.List;
+
+@Slf4j
+@Configuration
+@EnableConfigurationProperties(MqttProperties.class)
+public class MqttClientConfig {
+
+    @Autowired
+    private MqttProperties mqttProperties;
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MqttConnectOptions mqttConnectOptions() {
+        MqttConnectOptions options = new MqttConnectOptions();
+        options.setServerURIs(new String[]{mqttProperties.getBroker()});
+        options.setUserName(mqttProperties.getUsername());
+        options.setPassword(mqttProperties.getPassword().toCharArray());
+        options.setConnectionTimeout(mqttProperties.getConnectionTimeout());
+        options.setKeepAliveInterval(mqttProperties.getKeepalive());
+        options.setAutomaticReconnect(mqttProperties.isAutomaticReconnect());
+        options.setCleanSession(mqttProperties.isCleanSession());
+        return options;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public MqttClient mqttClient(MqttConnectOptions options, @Lazy MqttMessageHandler mqttMessageHandler) throws MqttException {
+        log.info("Creating MQTT client with client ID: {}", mqttProperties.getClientId());
+        MqttClient mqttClient = new MqttClient(mqttProperties.getBroker(), mqttProperties.getClientId());
+        mqttClient.setCallback(mqttMessageHandler);
+        mqttClient.connect(options);
+        log.info("MQTT client connected successfully");
+        return mqttClient;
+    }
+} 

+ 26 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/config/MqttProperties.java

@@ -0,0 +1,26 @@
+package cn.hfln.framework.mqtt.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttProperties {
+    private boolean enabled = true;
+    private String broker;
+    private String clientId;
+    private String username;
+    private String password;
+    private int timeout = 30;
+    private int keepalive = 60;
+    private boolean cleanSession = true;
+    private boolean automaticReconnect = true;
+    private int maxReconnectDelay = 10000;
+    private int connectionTimeout = 30;
+    private int maxMessageSize = 1024 * 1024;
+    private boolean ssl = false;
+    private String sslCaPath;
+    private String sslClientCertPath;
+    private String sslClientKeyPath;
+    private String sslClientKeyPassword;
+}

+ 38 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/converter/JsonMessageConverter.java

@@ -0,0 +1,38 @@
+package cn.hfln.framework.mqtt.converter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+public class JsonMessageConverter implements MessageConverter {
+    private final ObjectMapper objectMapper;
+
+    public JsonMessageConverter() {
+        this.objectMapper = new ObjectMapper();
+    }
+
+    @Override
+    public MqttMessage toMessage(Object payload) {
+        try {
+            String json = objectMapper.writeValueAsString(payload);
+            return new MqttMessage(json.getBytes(StandardCharsets.UTF_8));
+        } catch (Exception e) {
+            log.error("Failed to convert object to message", e);
+            throw new RuntimeException("Failed to convert object to message", e);
+        }
+    }
+
+    @Override
+    public <T> T fromMessage(MqttMessage message, Class<T> targetType) {
+        try {
+            String json = new String(message.getPayload(), StandardCharsets.UTF_8);
+            return objectMapper.readValue(json, targetType);
+        } catch (Exception e) {
+            log.error("Failed to convert message to object", e);
+            throw new RuntimeException("Failed to convert message to object", e);
+        }
+    }
+} 

+ 25 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/converter/MessageConverter.java

@@ -0,0 +1,25 @@
+package cn.hfln.framework.mqtt.converter;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * MQTT消息转换器接口
+ */
+public interface MessageConverter {
+    /**
+     * 将对象转换为MQTT消息
+     *
+     * @param payload 消息内容
+     * @return MqttMessage
+     */
+    MqttMessage toMessage(Object payload);
+
+    /**
+     * 将MQTT消息转换为对象
+     *
+     * @param message MQTT消息
+     * @param targetType 目标类型
+     * @return 转换后的对象
+     */
+    <T> T fromMessage(MqttMessage message, Class<T> targetType);
+} 

+ 83 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/handler/MqttMessageHandler.java

@@ -0,0 +1,83 @@
+package cn.hfln.framework.mqtt.handler;
+
+import cn.hfln.framework.mqtt.converter.MessageConverter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+
+@Component
+@Slf4j
+public class MqttMessageHandler implements MqttCallback {
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    @Override
+    public void connectionLost(Throwable cause) {
+        log.error("Connection lost", cause);
+    }
+
+    @Override
+    public void messageArrived(String topic, MqttMessage message) {
+        log.info("Message arrived on topic: {}", topic);
+        log.info("Message content: {}", new String(message.getPayload()));
+
+        // 从 ApplicationContext 中获取 MqttSubscribeProcessor
+        MqttSubscribeProcessor subscribeProcessor = applicationContext.getBean(MqttSubscribeProcessor.class);
+        Method method = subscribeProcessor.getMethodForTopic(topic);
+        
+        if (method != null) {
+            try {
+                Object bean = applicationContext.getBean(method.getDeclaringClass());
+                log.info("Found handler bean: {} for topic: {}", bean.getClass().getName(), topic);
+                
+                // 获取方法参数类型
+                Class<?>[] parameterTypes = method.getParameterTypes();
+                Object[] args = new Object[parameterTypes.length];
+                
+                // 根据参数类型转换消息内容
+                if (parameterTypes.length == 1) {
+                    if (parameterTypes[0] == String.class) {
+                        args[0] = new String(message.getPayload(), StandardCharsets.UTF_8);
+                        log.info("Converting message to String: {}", args[0]);
+                    } else {
+                        // 尝试使用消息转换器转换
+                        MessageConverter converter = applicationContext.getBean(MessageConverter.class);
+                        args[0] = converter.fromMessage(message, parameterTypes[0]);
+                        log.info("Converting message to {}: {}", parameterTypes[0].getName(), args[0]);
+                    }
+                } else {
+                    log.error("Handler method must have exactly one parameter, but found {} parameters", parameterTypes.length);
+                    return;
+                }
+                
+                log.info("Invoking method: {} on bean: {} with args: {}", method.getName(), bean.getClass().getName(), args[0]);
+                Object result = method.invoke(bean, args);
+                log.info("Method invocation result: {}", result);
+                log.info("Successfully processed message for topic: {}", topic);
+            } catch (Exception e) {
+                log.error("Error invoking method for topic: {} - Error: {}", topic, e.getMessage(), e);
+                log.error("Stack trace:", e);
+            }
+        } else {
+            log.warn("No handler method found for topic: {}", topic);
+        }
+    }
+
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+        log.info("Message delivery complete for token: {}", token);
+        try {
+            log.info("Message delivered to topics: {}", token.getTopics());
+        } catch (Exception e) {
+            log.error("Error getting delivery topics", e);
+        }
+    }
+} 

+ 81 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/handler/MqttSubscribeProcessor.java

@@ -0,0 +1,81 @@
+package cn.hfln.framework.mqtt.handler;
+
+import cn.hfln.framework.mqtt.annotation.MqttSubscribe;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Component
+public class MqttSubscribeProcessor implements BeanPostProcessor {
+
+    private final Map<String, Method> topicMethodMap = new HashMap<>();
+    
+    @Autowired
+    @Lazy
+    private MqttClient mqttClient;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) {
+        log.info("Processing bean: {} for MQTT subscriptions", beanName);
+
+        Class<?> beanClass = bean.getClass();
+        for (Method method : beanClass.getMethods()) {
+            MqttSubscribe annotation = method.getAnnotation(MqttSubscribe.class);
+            if (annotation != null) {
+                String topic = annotation.topic();
+                int qos = annotation.qos();
+                
+                if (!mqttClient.isConnected()) {
+                    log.error("MQTT client is not connected, cannot subscribe to topic: {}", topic);
+                    continue;
+                }
+
+                try {
+                    log.info("Subscribing to topic: {} with QoS: {}", topic, qos);
+                    mqttClient.subscribe(topic, qos);
+                    topicMethodMap.put(topic, method);
+                    log.info("Successfully subscribed to topic: {} with QoS: {}", topic, qos);
+                } catch (MqttException e) {
+                    log.error("Failed to subscribe to topic: {}", topic, e);
+                }
+            }
+        }
+        return bean;
+    }
+
+    public Method getMethodForTopic(String topic) {
+        // 首先尝试精确匹配
+        Method method = topicMethodMap.get(topic);
+        if (method != null) {
+            return method;
+        }
+
+        // 如果没有精确匹配,尝试通配符匹配
+        for (Map.Entry<String, Method> entry : topicMethodMap.entrySet()) {
+            String pattern = entry.getKey()
+                    .replace("+", "[^/]+")  // 将 + 替换为匹配除 / 外的任意字符
+                    .replace("#", ".*");     // 将 # 替换为匹配任意字符
+            if (Pattern.matches(pattern, topic)) {
+                log.info("Found matching method for topic: {} using pattern: {}", topic, pattern);
+                return entry.getValue();
+            }
+        }
+
+        log.warn("No method found for topic: {}", topic);
+        return null;
+    }
+} 

+ 31 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/interceptor/DefaultMqttMessageInterceptor.java

@@ -0,0 +1,31 @@
+package cn.hfln.framework.mqtt.interceptor;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class DefaultMqttMessageInterceptor implements MqttMessageInterceptor {
+    @Override
+    public boolean preSend(String topic, MqttMessage message) {
+        log.debug("Pre-send message to topic: {}", topic);
+        return true;
+    }
+
+    @Override
+    public void postSend(String topic, MqttMessage message) {
+        log.debug("Post-send message to topic: {}", topic);
+    }
+
+    @Override
+    public boolean preReceive(String topic, MqttMessage message) {
+        log.debug("Pre-receive message from topic: {}", topic);
+        return true;
+    }
+
+    @Override
+    public void postReceive(String topic, MqttMessage message) {
+        log.debug("Post-receive message from topic: {}", topic);
+    }
+} 

+ 42 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/interceptor/MqttMessageInterceptor.java

@@ -0,0 +1,42 @@
+package cn.hfln.framework.mqtt.interceptor;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * MQTT消息拦截器接口
+ */
+public interface MqttMessageInterceptor {
+    /**
+     * 发送消息前拦截
+     *
+     * @param topic   主题
+     * @param message 消息
+     * @return 是否继续发送
+     */
+    boolean preSend(String topic, MqttMessage message);
+
+    /**
+     * 发送消息后拦截
+     *
+     * @param topic   主题
+     * @param message 消息
+     */
+    void postSend(String topic, MqttMessage message);
+
+    /**
+     * 接收消息前拦截
+     *
+     * @param topic   主题
+     * @param message 消息
+     * @return 是否继续处理
+     */
+    boolean preReceive(String topic, MqttMessage message);
+
+    /**
+     * 接收消息后拦截
+     *
+     * @param topic   主题
+     * @param message 消息
+     */
+    void postReceive(String topic, MqttMessage message);
+} 

+ 23 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/DefaultMqttConnectionListener.java

@@ -0,0 +1,23 @@
+package cn.hfln.framework.mqtt.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class DefaultMqttConnectionListener implements MqttConnectionListener {
+    @Override
+    public void onConnected() {
+        log.info("MQTT client connected successfully");
+    }
+
+    @Override
+    public void onDisconnected(Throwable cause) {
+        log.warn("MQTT client disconnected", cause);
+    }
+
+    @Override
+    public void onConnectionFailed(Throwable cause) {
+        log.error("MQTT client connection failed", cause);
+    }
+} 

+ 15 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/DefaultMqttMessageListener.java

@@ -0,0 +1,15 @@
+package cn.hfln.framework.mqtt.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class DefaultMqttMessageListener implements MqttMessageListener {
+    @Override
+    public void onMessage(String topic, MqttMessage message) {
+        log.info("Received message on topic: {}", topic);
+        log.info("Message content: {}", new String(message.getPayload()));
+    }
+} 

+ 25 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/MqttConnectionListener.java

@@ -0,0 +1,25 @@
+package cn.hfln.framework.mqtt.listener;
+
+/**
+ * MQTT连接状态监听器接口
+ */
+public interface MqttConnectionListener {
+    /**
+     * 连接成功回调
+     */
+    void onConnected();
+
+    /**
+     * 连接断开回调
+     *
+     * @param cause 断开原因
+     */
+    void onDisconnected(Throwable cause);
+
+    /**
+     * 连接失败回调
+     *
+     * @param cause 失败原因
+     */
+    void onConnectionFailed(Throwable cause);
+} 

+ 16 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/listener/MqttMessageListener.java

@@ -0,0 +1,16 @@
+package cn.hfln.framework.mqtt.listener;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * MQTT消息监听器接口
+ */
+public interface MqttMessageListener {
+    /**
+     * 处理接收到的消息
+     *
+     * @param topic   主题
+     * @param message 消息
+     */
+    void onMessage(String topic, MqttMessage message);
+} 

+ 210 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/java/cn/hfln/framework/mqtt/template/MqttTemplate.java

@@ -0,0 +1,210 @@
+package cn.hfln.framework.mqtt.template;
+
+import cn.hfln.framework.mqtt.converter.MessageConverter;
+import cn.hfln.framework.mqtt.interceptor.MqttMessageInterceptor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@Slf4j
+@Component
+public class MqttTemplate {
+    private final MqttClient mqttClient;
+    private final ObjectMapper objectMapper;
+    private final List<MqttMessageInterceptor> interceptors;
+    private final MessageConverter messageConverter;
+
+    @Autowired
+    public MqttTemplate(MqttClient mqttClient, List<MqttMessageInterceptor> interceptors, MessageConverter messageConverter) {
+        this.mqttClient = mqttClient;
+        this.objectMapper = new ObjectMapper();
+        this.interceptors = interceptors;
+        this.messageConverter = messageConverter;
+    }
+
+    /**
+     * 发送字符串消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> send(String topic, String payload) {
+        return send(topic, payload.getBytes(StandardCharsets.UTF_8), 0, false);
+    }
+
+    /**
+     * 发送字符串消息(指定QoS和保留标志)
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @param qos     QoS级别
+     * @param retain  是否保留
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> send(String topic, String payload, int qos, boolean retain) {
+        return send(topic, payload.getBytes(StandardCharsets.UTF_8), qos, retain);
+    }
+
+    /**
+     * 发送字节数组消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> send(String topic, byte[] payload) {
+        return send(topic, payload, 0, false);
+    }
+
+    /**
+     * 发送字节数组消息(指定QoS和保留标志)
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @param qos     QoS级别
+     * @param retain  是否保留
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> send(String topic, byte[] payload, int qos, boolean retain) {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        try {
+            MqttMessage message = new MqttMessage(payload);
+            message.setQos(qos);
+            message.setRetained(retain);
+
+            // 执行发送前拦截
+            boolean shouldSend = interceptors.stream()
+                    .allMatch(interceptor -> interceptor.preSend(topic, message));
+
+            if (shouldSend) {
+                mqttClient.publish(topic, message);
+                // 执行发送后拦截
+                interceptors.forEach(interceptor -> interceptor.postSend(topic, message));
+                future.complete(null);
+            } else {
+                future.completeExceptionally(new RuntimeException("Message sending was intercepted"));
+            }
+        } catch (MqttException e) {
+            future.completeExceptionally(e);
+        }
+        return future;
+    }
+
+    /**
+     * 发送JSON对象消息
+     *
+     * @param topic 主题
+     * @param obj   对象
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> sendJson(String topic, Object obj) {
+        try {
+            String json = objectMapper.writeValueAsString(obj);
+            return send(topic, json);
+        } catch (JsonProcessingException e) {
+            CompletableFuture<Void> future = new CompletableFuture<>();
+            future.completeExceptionally(e);
+            return future;
+        }
+    }
+
+    /**
+     * 发送JSON对象消息(指定QoS和保留标志)
+     *
+     * @param topic  主题
+     * @param obj    对象
+     * @param qos    QoS级别
+     * @param retain 是否保留
+     * @return CompletableFuture<Void>
+     */
+    public CompletableFuture<Void> sendJson(String topic, Object obj, int qos, boolean retain) {
+        try {
+            String json = objectMapper.writeValueAsString(obj);
+            return send(topic, json, qos, retain);
+        } catch (JsonProcessingException e) {
+            CompletableFuture<Void> future = new CompletableFuture<>();
+            future.completeExceptionally(e);
+            return future;
+        }
+    }
+
+    /**
+     * 同步发送消息
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @throws MqttException 发送异常
+     */
+    public void sendSync(String topic, String payload) throws MqttException {
+        sendSync(topic, payload.getBytes(StandardCharsets.UTF_8), 0, false);
+    }
+
+    /**
+     * 同步发送消息(指定QoS和保留标志)
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     * @param qos     QoS级别
+     * @param retain  是否保留
+     * @throws MqttException 发送异常
+     */
+    public void sendSync(String topic, byte[] payload, int qos, boolean retain) throws MqttException {
+        MqttMessage message = new MqttMessage(payload);
+        message.setQos(qos);
+        message.setRetained(retain);
+
+        // 执行发送前拦截
+        boolean shouldSend = interceptors.stream()
+                .allMatch(interceptor -> interceptor.preSend(topic, message));
+
+        if (shouldSend) {
+            mqttClient.publish(topic, message);
+            // 执行发送后拦截
+            interceptors.forEach(interceptor -> interceptor.postSend(topic, message));
+        } else {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_EXCEPTION);
+        }
+    }
+
+    /**
+     * 同步发送JSON对象消息
+     *
+     * @param topic 主题
+     * @param obj   对象
+     * @throws MqttException 发送异常
+     */
+    public void sendJsonSync(String topic, Object obj) throws MqttException {
+        try {
+            String json = objectMapper.writeValueAsString(obj);
+            sendSync(topic, json);
+        } catch (JsonProcessingException e) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_EXCEPTION, e);
+        }
+    }
+
+    /**
+     * 同步发送JSON对象消息(指定QoS和保留标志)
+     *
+     * @param topic  主题
+     * @param obj    对象
+     * @param qos    QoS级别
+     * @param retain 是否保留
+     * @throws MqttException 发送异常
+     */
+    public void sendJsonSync(String topic, Object obj, int qos, boolean retain) throws MqttException {
+        try {
+            String json = objectMapper.writeValueAsString(obj);
+            sendSync(topic, json.getBytes(StandardCharsets.UTF_8), qos, retain);
+        } catch (JsonProcessingException e) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_EXCEPTION, e);
+        }
+    }
+} 

+ 2 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.hfln.framework.mqtt.config.MqttAutoConfiguration 

+ 167 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/MqttTemplateTest.java

@@ -0,0 +1,167 @@
+package cn.hfln.framework.mqtt;
+
+import cn.hfln.framework.mqtt.config.TestConfig;
+import cn.hfln.framework.mqtt.template.MqttTemplate;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Slf4j
+@SpringBootTest(classes = {TestApplication.class, TestConfig.class})
+public class MqttTemplateTest {
+
+    @Autowired
+    private MqttTemplate mqttTemplate;
+
+    @Autowired
+    private TestMqttMessageHandler messageHandler;
+
+    @Autowired
+    private MqttClient mqttClient;
+
+    @BeforeEach
+    public void setup() {
+        log.info("Setting up test...");
+        // 等待连接建立
+        int retryCount = 0;
+        while (!mqttClient.isConnected() && retryCount < 5) {
+            try {
+                log.info("Waiting for MQTT connection...");
+                Thread.sleep(1000);
+                retryCount++;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                break;
+            }
+        }
+        if (!mqttClient.isConnected()) {
+            log.error("MQTT client is not connected after {} retries", retryCount);
+        } else {
+            log.info("MQTT client is connected");
+        }
+    }
+
+    @Test
+    public void testSendAndReceiveMessage() throws Exception {
+        log.info("Starting testSendAndReceiveMessage...");
+        
+        // 确保客户端已连接
+        if (!mqttClient.isConnected()) {
+            log.error("MQTT client is not connected, cannot proceed with test");
+            return;
+        }
+        
+        // 创建CountDownLatch用于等待消息接收
+        CountDownLatch latch = new CountDownLatch(1);
+        messageHandler.setStringMessageLatch(latch);
+        
+        // 创建测试消息
+        String testTopic = "test/1/topic";
+        String testMessage = "Hello MQTT!";
+        
+        log.info("Sending message to topic: {}", testTopic);
+        // 发送消息
+        mqttTemplate.send(testTopic, testMessage)
+                .thenRun(() -> log.info("Message sent successfully"))
+                .exceptionally(throwable -> {
+                    log.error("Failed to send message", throwable);
+                    return null;
+                });
+
+        // 等待消息接收,最多等待10秒
+        log.info("Waiting for message reception...");
+        boolean received = latch.await(10, TimeUnit.SECONDS);
+        if (!received) {
+            log.error("Message was not received within 10 seconds");
+            log.error("Last received string message: {}", messageHandler.getReceivedStringMessage());
+            log.error("MQTT client connection status: {}", mqttClient.isConnected());
+        }
+        assertTrue(received, "Message should be received within 10 seconds");
+        log.info("Test completed successfully");
+    }
+
+    @Test
+    public void testSendAndReceiveJsonMessage() throws Exception {
+        log.info("Starting testSendAndReceiveJsonMessage...");
+        
+        // 确保客户端已连接
+        if (!mqttClient.isConnected()) {
+            log.error("MQTT client is not connected, cannot proceed with test");
+            return;
+        }
+        
+        // 创建CountDownLatch用于等待消息接收
+        CountDownLatch latch = new CountDownLatch(1);
+        messageHandler.setJsonMessageLatch(latch);
+        
+        // 创建测试消息
+        String testTopic = "test/json";
+        TestMessage testMessage = new TestMessage("test", 123);
+        
+        log.info("Sending JSON message to topic: {}", testTopic);
+        // 发送JSON消息
+        mqttTemplate.sendJson(testTopic, testMessage)
+                .thenRun(() -> log.info("JSON message sent successfully"))
+                .exceptionally(throwable -> {
+                    log.error("Failed to send JSON message", throwable);
+                    return null;
+                });
+
+        // 等待消息接收,最多等待10秒
+        log.info("Waiting for JSON message reception...");
+        boolean received = latch.await(10, TimeUnit.SECONDS);
+        if (!received) {
+            log.error("JSON message was not received within 10 seconds");
+            log.error("Last received JSON message: {}", messageHandler.getReceivedJsonMessage());
+            log.error("MQTT client connection status: {}", mqttClient.isConnected());
+        }
+        assertTrue(received, "JSON message should be received within 10 seconds");
+        log.info("Test completed successfully");
+    }
+
+    // 测试消息类
+    private static class TestMessage {
+        private String name;
+        private int value;
+
+        public TestMessage() {
+        }
+
+        public TestMessage(String name, int value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public void setValue(int value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return "TestMessage{" +
+                    "name='" + name + '\'' +
+                    ", value=" + value +
+                    '}';
+        }
+    }
+} 

+ 13 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestApplication.java

@@ -0,0 +1,13 @@
+package cn.hfln.framework.mqtt;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan("cn.hfln.framework.mqtt")
+public class TestApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(TestApplication.class, args);
+    }
+} 

Some files were not shown because too many files changed in this diff