first commit

This commit is contained in:
zc
2025-12-08 10:40:43 +08:00
commit 871ae8be0a
410 changed files with 38212 additions and 0 deletions

7
.codacy.yml Normal file
View File

@@ -0,0 +1,7 @@
---
exclude_paths:
- '.mvn/**'
- '**.md'
- '**/**.md'
- 'mica-mqtt-example/**'
- '**/test/**'

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
# http://editorconfig.org
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.java]
indent_style = tab
[*.{json,yml}]
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

22
.gitattributes vendored Normal file
View File

@@ -0,0 +1,22 @@
# All text files should have the "lf" (Unix) line endings
* text eol=lf
# windows cmd shoud have the "crlf" (Win32) line endings
*.cmd eol=crlf
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.java text
*.js text
*.css text
*.html text
*.properties text
*.xml text
*.yml text
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.ttf binary
*.jar binary
*.db binary
*.xdb binary

65
.gitignore vendored Normal file
View File

@@ -0,0 +1,65 @@
### gradle ###
.gradle
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.settings/
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
bin/
### IntelliJ IDEA ###
!.idea/icon.png
.idea
out/
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### vscode ###
.vscode
### maven ###
target/
*.war
*.ear
*.zip
*.tar
*.tar.gz
# logs #
logs
# temp ignore
*.log
*.cache
*.diff
*.patch
*.tmp
*.java~
*.properties~
*.xml~
# system ignore
.DS_Store
Thumbs.db
Servers
.metadata
upload
gen_code
# Flattened pom
.flattened-pom.xml
/**/.flattened-pom.xml

625
CHANGELOG.md Normal file
View File

@@ -0,0 +1,625 @@
# 变更记录
## 发行版本
### v2.5.9 - 2025-11-29
- :sparkles: mica-mqtt-client solon 和 spring 插件 MQTT 客户端订阅中的 beanName 支持占位符解析,感谢 `@tan90` 反馈gitee #ID7PF6
- :sparkles: mica-mqtt-server ClientInfo 添加 SSL 和 WebSocket 标识。
- :arrow_up: 升级到 mica-net 1.2.4,优化 sse修复 jackson3 方法错误。
### v2.5.7 - 2025-11-07
- :sparkles: mica-mqtt-server 新增 `/api/v1/stats/sse` 接口,支持通过 SSE 实时获取服务器统计信息
- :sparkles: example 升级到 solon 3.7.0 更改相关依赖命名规则
- :arrow_up: 升级 mica-net 到 1.2.2 支持 snack4 json 序列化,内存优化和消息发送性能优化
- :bug: mica-mqtt-server-solon-plugin 移除 MqttServerConfiguration bean MqttFunctionManager 的 static 修饰符
### v2.5.6 - 2025-10-27
- :bug: 修复 MQTT 解码器中的缓冲区读取问题,修复解码异常重连后无法恢复的问题。(所有版本)
- :arrow_up: 升级 mica-net 到 1.2.1,修改慢包读取 (gitee #ID3IAU)影响范围2.5.5
### v2.5.5 - 2025-10-10
- :sparkles: mqtt-client 添加通过 `Consumer<MqttWillPublishProperties>` 函数式接口自定义遗嘱属性
- :sparkles: mqtt-client 添加直接使用 MqttPublishBuilder 发布消息
- :sparkles: mqtt-client 添加 disconnectBeforeStop 配置(默认 true断开连接前是否发送 disconnect 消息,感谢 `@steven` 反馈gitee #ICXY4A
- :sparkles: mica-mqtt-server 使用 ConcurrentHashMap 替代 IntObjectHashMap优化内存会话管理
- :sparkles: mica-mqtt-server-spring-boot-starter bean 加载顺序优化,避免出现提示
- :arrow_up: 升级 mica-net 到 1.2.0,调整慢包攻击规则和支持 jackson3感谢 `@well` 反馈gitee #ICXF5N
### v2.5.4 - 2025-08-29
- :sparkles: mica-mqtt-server 使用前缀树管理 MQTT 订阅。
- :sparkles: mica-mqtt-server 心跳超时小于等于0时不开启心跳检测。不建议这样使用感谢 `@刘业兴` 反馈gitee #ICTT2V
- :sparkles: mica-mqtt-server solon 和 spring 插件,将 `@MqttServerFunction` 统一到 mica-mqtt-common 包中,不兼容。
- :sparkles: mica-mqtt-server solon 和 spring 插件,`@MqttServerFunction` 增加 topic 变量解析功能,支持解析 Map<String, String> 类型的 topic 中的 ${topicVars} 变量参数。
- :sparkles: mica-mqtt-client solon 和 spring 插件,将 `@MqttClientSubscribe` 统一到 mica-mqtt-common 包中MqttClientTemplate 中的 `DEFAULT_CLIENT_TEMPLATE_BEAN` 常量定义移动到了 `@MqttClientSubscribe`,不兼容。
- :sparkles: mica-mqtt-client solon 和 spring 插件,`@MqttClientSubscribe` 注解订阅,增加 topic 变量解析功能,支持解析 Map<String, String> 类型的 topic 中的 ${topicVars} 变量参数。
- :sparkles: mica-mqtt-codec 移除了 MqttCodecUtil 中的 `isValidPublishTopicName` 方法 ,直接使用 `isTopicFilter` 校验发布主题名称是否包含通配符。
- :sparkles: mica-mqtt-codec 包调整,重命名类名、方法名,重构 MQTT 消息构建器类(为后续方便做准备),不兼容。
- :sparkles: mica-mqtt-common `TopicUtil` 调整 validateTopicFilter 方法移除了对空白字符的校验。注意emqx 支持使用空白字符mosquitto 不支持)。
- :sparkles: `TopicUtil``MqttCodecUtil` 增加对 topic 中空白符的日志提示。感谢 `@长草颜团子` 反馈gitee #26
### v2.5.3 - 2025-08-03
- :sparkles: mica-mqtt-server-spring-boot-starter 支持注解 `@MqttServerFunction` 监听
- :sparkles: mica-mqtt-server-solon-plugin 支持注解 `@MqttServerFunction` 监听
- :sparkles: mica-mqtt-client-solon-plugin 更新 `solon-configuration-metadata.json`
- :sparkles: mica-mqtt-codec `ReasonCode` 统一移动到 `codes` 包(不影响老用户升级)
### v2.5.2 - 2025-07-27
- :bug: mica-mqtt-server 修复启动报错(影响范围 `2.5.0` ~ `2.5.1`),感谢 `CoderKK` 反馈gitee #ICOQ3Q
### v2.5.1 - 2025-07-24
- :sparkles: mica-mqtt-server 优化 sse mcp添加 sse 心跳
- :sparkles: mica-mqtt-client 内置 ssl SNI 支持,感谢 `sword007``@TomatoLay` 反馈gitee #ICKBAY #ICEANP
- :sparkles: mica-mqtt-client 支持多网卡下指定网卡 `bindIp`网卡对应IP`bindNetworkInterface`(网卡名) 配置2个方法使用任意一个即可。感谢 `@iovera` 反馈gitee #ICO699)
- :bug: mica-mqtt-client 高CPU下 packetId 生成超限,感谢 `@火焰之魂` 反馈gitee #ICLXC3
### v2.5.0 - 2025-07-12
- :sparkles: mica-mqtt X AImica-mqtt-server 支持大模型 mcp
- :sparkles: mica-mqtt-server 支持同时配置多协议支持,拆分 `http(默认端口18083)``websocket(默认端口8083)`,使统计更加准确
- :sparkles: mica-mqtt-server spring、solon 插件删除不推荐使用的 `EventMqttMessageListener`
- :sparkles: mica-mqtt-server 调整保留消息的规则,支持 `$retain` 带存储周期的保留消息 感谢 `@tan90` pr #ICB9I2 #23
- :sparkles: mica-mqtt-client 删除 `IMqttClientMessageIdGenerator` 接口合入 `IMqttClientSession` 接口
- :sparkles: mica-mqtt-client 默认 mqtt5.0cleanSession 改为 cleanStart 感谢 `@tan90` 反馈 (gitee #IBKKAG)
- :sparkles: mica-mqtt-client MqttClient 和 MqttClientTemplate 支持通过代理接口来进行Publish 感谢 `@galaxy-sea` pr (github #100)
- :sparkles: mica-mqtt-codec 完全同步成私服版,将 MqttProperties 内部类拆解出来,方便使用
- :sparkles: mica-mqtt client、server solon 插件添加对 [Solon IDEA](https://plugins.jetbrains.com/plugin/21380-solon) 插件配置提示支持。
- :sparkles: 移除 mica-mqtt-broker未来重构
- :sparkles: 统一参数命名userName 统一为 username。
- :sparkles: 优化部分日志,使用中文,方便大家排查问题
### v2.4.9 - 2025-06-27
- :bug: mqtt server 修复 gitee #ICID15
### v2.4.8 - 2025-06-20
- :sparkles: mica-mqtt-client-spring-boot-starter `MqttClientSubscribeDetector` bean 配置改成 `static` 方法。
- :sparkles: mica-mqtt-server 调整保留消息标志位的规则。
### v2.4.7 - 2025-06-02
- :sparkles: mica-mqtt-client、mica-mqtt-server publish相关接口支持object发送 (github #98) 感谢 `@galaxy-sea` 贡献
- :sparkles: mica-mqtt-client 调整 MqttClient#reconnect 策略gitee #IBY5LQ)感谢 `@拉风的CC` 反馈。
- :sparkles: mica-mqtt-codec MqttCodecUtil#isTopicFilter 代码优化改为逆序循环
- :sparkles: mica-mqtt-codec 代码优化详见: https://github.com/netty/netty/pull/15227
- :sparkles: mica-net-http http api 响应头 name 不转换成小写
- :wrench: mica-mqtt-common 更新 `module-info.java` 添加序列化模块
- :bug: mica-net-utils DefaultThreadFactory 不应该共用。
### v2.4.6 - 2025-05-19
- :sparkles: mica-mqtt-client-spring-boot-starter MqttDeserializer 接口重写,支持泛型调用 (github #95) 感谢 `@galaxy-sea` 贡献
- :sparkles: mica-mqtt-client 批量订阅兼容 mqtt 3.1.1 部分 broker 只返回一个 reasonCode 的情况。感谢 `@Jacky` 反馈
- :sparkles: mica-mqtt-server-solon-plugin 添加对 metrics 指标的支持
- :sparkles: mica-mqtt-client-solon-plugin 注解订阅支持自定义序列化(默认 json 序列化)和泛型
### v2.4.5 - 2025-05-06
- :sparkles: mica-mqtt-client-spring-boot-starter 的 `@MqttClientSubscribe` 注解支持自定义反序列化。
- :sparkles: 优化代码 Spring Boot Client 可以自定义 MqttClientSubscribeDetector github #90 感谢 `@galaxy-sea` 贡献
- :sparkles: 升级 mica-net 到 1.1.6,解决 eclipse paho mqtt websocket client 连接 mica-mqtt server 报错
- :sparkles: 依赖调整,减少 example 示例项目的安全提示
- :bug: 解决服务端重启时 client有消息发送导致 client 无法正常重连 gitee #IC4DWT 感谢 `@wtjperi2003` 反馈
- :bug: 同步 netty mqtt codec Fix the assignment error of maxQoS parameter in ConnAck Properties
### v2.4.4 - 2025-04-13
- :sparkles: mica-mqtt-server更好的兼容 Android 环境。github #81 感谢 `@KittenBall` 的联调测试。
### v2.4.3 - 2025-03-23
- :sparkles: Central Portal 开始支持 Snapshots仅存储90天需尽快切到最新的正式版dev 分支提交后 Github action 自动发布快照版。
- :sparkles: 精简,删除没有用到的代码,下沉到 mica-net。
- :sparkles: mica-mqtt-client 添加 heartbeatMode 和 heartbeatTimeoutStrategy用于某些弱网场景 gitee #IBSMZ7 感谢 `@拉风的CC` 反馈。
- :sparkles: mica-mqtt-server 默认依赖上 mica-net-http不再需要手动添加依赖简化使用。
- :sparkles: mica-mqtt-server-spring-boot-starter MqttServerTemplate 暴露 `getMqttServer()` 方法,方便使用。
- :sparkles: mica-mqtt-server-spring-boot-starter 兼容存在 `MeterRegistry` 类,但是 `MeterRegistry` bean 不存在的情况。gitee #IBLBCY 感谢 `@xxg` 反馈。
### v2.4.2 - 2025-01-24
- :sparkles: mica-mqtt-client Spring Boot stater 和 solon 插件添加工作线程数配置 `bizThreadPoolSize` (默认2如果消息量大业务复杂处理慢例如做emqx消息转发处理可调大此配置)。
- :sparkles: mica-mqtt-client Spring Boot stater 和 solon 插件添加 MQTT5.0 的 `sessionExpiryIntervalSecs` 配置 gitee #IBIE27 感谢 `@cyber` 反馈。
- :sparkles: mica-mqtt-client 调整重连重新订阅逻辑Spring Boot stater 和 solon 插件 `@MqttClientSubscribe` 注解订阅,保留 session 重连时不丢失消息 gitee #IBIE27 感谢 `@cyber` 反馈。
- :sparkles: mica-mqtt-client DefaultMqttClientSession 移除 `final` 修饰,方便继承自定义。
- :sparkles: mica-mqtt-client 将 clientId 绑定到 context 上,可以使用 `context.getId()` 获取,方便多 mqtt client 实例下使用gitee #IBHHB1 感谢 `@cv` 反馈。
- :sparkles: mica-mqtt-server proxy 代理协议简化,已测底抽象到 mica-net。
- :sparkles: mica-mqtt-common 调整 `TopicUtil`,支持原生 Androidgitee #IBJBFL 感谢 `@DeanNode` 反馈。
- :sparkles: mica-mqtt-server 默认的 nodeName 改为随机 `nanoId`,支持原生 Androidgitee #IBJBFL 感谢 `@DeanNode` 反馈。
- :sparkles: 将 MqttServerCustomizer 和 MqttClientCustomizer 抽到 mica-mqtt-server、mica-mqtt-client 方便组件封装需要调整包名请先将老的包导入删除idea 会自动引入新的包。
- :bug: mica-mqtt-client-spring-boot-starter 修复 Spring Boot 3.2 启动时出现警告 gitee #IBITP5 感谢 `@cyber` 反馈。
### v2.4.2-M2 - 2025-01-23
- :sparkles: mica-mqtt-client Spring boot stater 和 solon 添加工作线程数配置 `bizThreadPoolSize`
- :sparkles: mica-mqtt-server proxy 代理简化,已测底抽象到 mica-net。
- :sparkles: mica-mqtt-common 调整 TopicUtil支持原生 Androidgitee #IBJBFL 感谢 `@DeanNode` 反馈。
- :sparkles: mica-mqtt-server nodeName 改为随机 nanoId ,支持原生 Androidgitee #IBJBFL 感谢 `@DeanNode` 反馈。
- :bug: mica-mqtt-client-spring-boot-starter 修复 Spring Boot 3.2 启动时出现警告 gitee #IBITP5 感谢 `@cyber` 反馈。
- :arrow_up: 升级 mica-net 到 1.0.12tcp server 如果非 debug 不开启版本信息等打印,方便支持 AndroidAndroid 下没有 ManagementFactory
### v2.4.2-M1 - 2025-01-17
- :sparkles: mica-mqtt client 调整重连重新订阅的逻辑 gitee #IBIE27 感谢 `@cyber` 反馈。
- :sparkles: mica-mqtt client solon 和 spring boot 插件添加 MQTT5.0 的 sessionExpiryIntervalSecs 配置 gitee #IBIE27 感谢 `@cyber` 反馈。
- :sparkles: mica-mqtt client DefaultMqttClientSession 移除 final 修饰,方便继承自定义。
- :sparkles: mica-mqtt client 将 clientId 绑定到 context 上,可以使用 `context.getId()` 获取gitee #IBHHB1 感谢 `@cv` 反馈。
- :sparkles: 将 MqttServerCustomizer 和 MqttClientCustomizer 抽到 mica-mqtt-server、mica-mqtt-client 方便组件封装 gitee #IBIJDF 感谢 `@cyber` 反馈
### v2.4.1 - 2025-01-04
- :sparkles: mqtt server 统一 topic 订阅、发布认证日志方便排查问题。
- :sparkles: mqtt server 添加 PROXY protocol v1 支持nginx 可开启 tcp proxy_protocol on; 时转发源 ip 信息。
- :memo: 修复文档 maven 坐标错误。
- :bug: 修复 spring boot 项目使用全局懒加载 topic无法订阅 gitee #IBFIV8 感谢 `@xixuanhao` 反馈
### v2.4.0 - 2024-12-07
- :sparkles: http api 添加 `stats``clients` 列表和 `client详情` 接口。
- :sparkles: MqttServer 和 MqttServerTemplate 添加 `getClientInfo` `getClients` 系列客户端信息接口。
- :sparkles: MqttServer 和 MqttServerTemplate 添加 `getSubscriptions` 获取客户端订阅列表接口。
- :sparkles: MqttServer 和 MqttServerTemplate 添加 `getStat` 统计接口。
- :truck: 调整 maven groupId `net.dreamlu` 到新的 `org.dromara.mica-mqtt`
- :truck: 调整包名 `net.dreamlu.iot.mqtt` 到新的 `org.dromara.mica.mqtt`,其他均保持不变。
- :truck: 切换到 central sonatypecentral sonatype 不支持快照版mica-mqtt 不再发布快照版。
- :bug: 修复订阅发送时机问题 gitee #IB72L6 感谢 `@江上烽` 反馈
### v2.4.0-M2 - 2024-12-01
- :sparkles: mica-mqtt-server 暴露获取 clientId 和状态统计接口
- :bug: 修复订阅发送时机问题 gitee #IB72L6 感谢 `@江上烽` 反馈
### v2.4.0-M1 - 2024-11-24
- :sparkles: 调整 groupId 到 org.dromara.mica-mqtt
- :truck: 切换包名到 org.dromara
- :truck: 切换到 central sonatype central sonatype 不支持快照版
### v2.3.9 - 2024-11-16
- :sparkles: ssl 支持 **PKCS12** 证书,根据文件后缀自动判断 `.jks、.keystore` 识别为 **JKS证书**`.p12、.pfx` 识别成 **PKCS12证书**。其他默认成**JKS**
- :sparkles: 优化 **Solon** 版本依赖(仅编译依赖),兼容 `2.8.0``2.8.0` 以上版本。
### v2.3.8 - 2024-09-26
- :sparkles: 升级到 solon v3, 调整 solon 版本兼容
- :bug: mica-net 心跳支持了 `keepAliveBackoff`mica-mqtt 漏改规则影响范围mica-mqtt server 开源版,私服版无此问题。) gitee #IAW9FC 感谢 `tan90` 反馈。
### v2.3.7 - 2024-09-22
- :sparkles: 优化 Mqtt server starter 添加 schedule 系列方法。
- :sparkles: MqttClient schedule 系列方法下层到底层,方法改造。
### v2.3.6 - 2024-09-14
- :sparkles: mica-mqtt server 和 client 优化 stop支持 stop 后重新配置再启动(注意:需要重新配置,因为老的线程池已经停止)。
- :sparkles: mica-mqtt server 和 client Spring boot starter 支持 Spring boot devtools 热启动。
- :sparkles: `FastJsonMessageSerializer` 重构为 `JsonMessageSerializer`
- :sparkles: 添加 `module-info.java` 方便模块化。
### v2.3.5 - 2024-09-01
- :sparkles: 新增 `SSLEngineCustomizer`,用于自定义 tls 协议版本和加密套件。
- :sparkles: !20 修改了 solon 插件的默认配置数值,感谢 `@peigen` pr。
### v2.3.4 - 2024-08-10
- :sparkles: mica-mqtt 合入 `mica-mqtt-client-solon-plugin``mica-mqtt-server-solon-plugin` 感谢 `@peigenlpy`
- :sparkles: jfinal 插件重命名为 `mica-mqtt-client-jfinal-plugin``mica-mqtt-server-jfinal-plugin`
- :bug: mica-mqtt-server 修复分组订阅删除,感谢 `@tangjj` 反馈。
### v2.3.3 - 2024-07-22
- :sparkles: mica-mqtt-server 可停止,同步捐助版。
- :sparkles: mica-mqtt-server 添加 schedule 系列方法,同步捐助版。
- :sparkles: mica-mqtt 代码优化 TopicUtil 优化 getTopicFilter 方法。
- :sparkles: mica-mqtt 优化 AckTimerTask 和 retry 重发日志。gitee #IABQ7L 感谢 `@tan90` 反馈。
- :sparkles: mica-mqtt-client-spring-boot-starter 更加方便自定义 MqttClientTemplate。
- :sparkles: mica-mqtt-client-spring-boot-starter MqttClientTemplate 暴露更多方法,方便使用。
- :sparkles: mica-mqtt-example 添加 ssl 测试代码
- :bug: mica-mqtt-client 修复 ssl 服务端重启问题 gitee #IA9FFW #IAEHOD 感谢 `@geekerstar` `@hangrj` 反馈。
### v2.3.1 - 2024-06-25
- :sparkles: mica-mqtt-server 重构心跳心跳检测模式默认为最后接收的数据时间。gitee #I9R0SN #IA69SM 感谢 `@HY` `@tan90` 反馈。
- :sparkles: mica-mqtt-server 优化端口占用的异常提示,方便排查。
- :sparkles: mica-mqtt client 使用 mica-net 内置的心跳检测,内置心跳已重构。
- :sparkles: mica-mqtt-client 重连不管服务端是否存在 session 都发送订阅。gitee #I9VIUV 感谢 `@xiaochonzi` 反馈。
- :sparkles: 快照版也打 source jar 方便使用。
- :sparkles: 添加 renovate bot 方便更新依赖和插件版本。
- :sparkles: 优化 issue.yml 和 github action。
### v2.3.0 - 2024-05-26
- :sparkles: mica-mqtt 优化 MqttQoS 枚举,改为 `MqttQoS.QOS0`,方便使用(不兼容)。
- :sparkles: mica-mqtt-client 同步私服部分功能,支持 stop 完全停止。
- :sparkles: mica-mqtt-client 同步私服部分功能MqttClient 都添加了 `schedule``scheduleOnce` 方法,(**耗时任务,请务必自定义线程池**
- :sparkles: mica-mqtt-server 优化设备离线,简化代码。
- :sparkles: mica-mqtt-server 用户绑定使用 tio 内置 `Tio.bindUser(context, username)`
- :bug: 修复 @MqttClientSubscribe 类型错误时的异常提示。
- :bug: mica-mqtt-client 修复重连可能失败的问题 gitee #I9RI8E 感谢 `@YYGuo` 反馈。
### v2.2.13 - 2024-05-12
- :sparkles: mica-mqtt-codec MqttVersion 添加版本全名。
- :sparkles: mica-mqtt-codec MqttConnectReasonCode 添加中文说明。
- :bug: mica-mqtt-server 保留消息下发时没有订阅也应该先存储 gitee #I9IYX1
### v2.2.12 - 2024-04-16
- :bug: mica-mqtt-server 遗嘱消息发送判断
### v2.2.11 - 2024-04-13
- :sparkles: mica-mqtt-client-spring-boot-starter 简化 MqttClientTemplate 构造,方便自定义。
- :sparkles: mica-mqtt-client-spring-boot-starter 优化 spring event mqtt client 连接监听。
- :sparkles: mica-mqtt-client-spring-boot-starter 优化注解订阅。
- :bug: mqtt-client 修复 mqtt5 props 和遗嘱同时配置时连接编码问题。
### v2.2.10 - 2024-03-23
- :sparkles: mica-mqtt-client 优化 client publish 时还没有认证的情况。
- :sparkles: mica-mqtt-client-spring-boot-starter 优化注解订阅,支持 clean session false 重启接收消息。
### v2.2.9 - 2024-02-25
- :sparkles: mica-mqtt-server 拦截器 IMqttMessageInterceptor 添加 onAfterConnected 方法,方便在连接时做黑名单等功能。
- :sparkles: mica-mqtt-client 添加私服版客户端全局订阅功能和添加使用文档。
- :boom: mica-mqtt-common 删除弃用的 ThreadUtil。
### v2.2.8 - 2024-01-19
- :sparkles: jfinal-mica-mqtt-client 启动改为同步连接。
- :bug: mica-mqtt-client 修复 `isConnected` 判断。`2.2.7` 中存在此问题。
- :arrow_up: 依赖升级
### v2.2.7 - 2024-01-03
- :sparkles: mica-mqtt-server mqttws开启了ssl后使用mqtt.js去连接多刷新几次就会超时 gitee #I8LCMY
- :sparkles: mica-mqtt-example 优化 graalvm 配置,感谢 github `@litongjava` 反馈
### v2.2.6 - 2023-11-26
- :sparkles: mica-mqtt-server 添加 `webConfigCustomize` 支持自定义 http 和 ws 配置,可用于 gitee #I8HF7P
- :sparkles: mica-mqtt-client 添加连接测试功能 connectTest gitee #I8J35M 感谢 `@彭蕾` 反馈
- :sparkles: mica-mqtt-example 更新 graalvm 配置
### v2.2.5.1 - 2023-11-01
- :poop: mica-mqtt-client mqttExecutor 方法参数类型漏改。
### v2.2.5 - 2023-10-05
- :sparkles: mqtt 业务线程池支持自定义设置为 java21虚拟线程。
- :sparkles: 更新 GitHub actionjava17 改为 java21。
- :sparkles: ThreadUtil 弃用(暂时未删),切换到 mica-net 中的 ThreadUtils。
### v2.2.4 - 2023-09-02
- :sparkles: 合并去年开源之夏的服务端共享订阅和完善捐助VIP版采用 topic 树存储,跟 topic 数无关,百万 topic 性能依旧)。
- :sparkles: 优化 topic 检验
- :bug: 相同 clientId 订阅相同 匹配 topic 应该取最大的qos gitee #I7WWPN
### v2.2.3 - 2023-07-23
- :sparkles: mqtt server http api publish 不按 clientId 进行路由(无实际意义),而是按 topic规则改为同 emqx。
- :sparkles: mqtt server http api publish 触发 onMessage 消息监听。
- :arrow_up: 依赖升级
### v2.2.2 - 2023-06-17
- :sparkles: mica-mqtt-client 心跳包日志受 debug 控制
- :sparkles: mica-mqtt-broker 的集群改为 redis stream 实现。
- :bug: 修复 starter ssl truststorePass 配置github #6 感谢 `@zkname` 反馈
### v2.2.1 - 2023-05-28
- :zap: mica-mqtt-client 共享订阅更好的兼容 emqx 高版本gitee #I786GU
- :arrow_up: 依赖升级
### v2.2.0 - 2023-05-14
- :sparkles: MqttPublishMessage payload 参数均由 `ByteBuffer` 改为 `byte[]`,简化代码,方便使用。
- :bug: 修复 高并发场景下取消订阅时报 ConcurrentModificationException github #5 感谢 `@yinyuncan` 反馈
### v2.1.2 - 2023-04-26
- :sparkles: mica-mqtt-client 支持 `reconnect(String ip, int port)` 转移到其他服务,订阅保留,连接成功时自动重新订阅。感谢 `@powerxie` 反馈
- :sparkles: 优化 `TopicUtil#getTopicFilter()` topic 占位符替换。
- :sparkles: 调整 mica-mqtt-client-spring-boot-starter 启动时机。`MqttClientCustomizer` 支持从数据库中获取配置。感谢 `@powerxie` 反馈
- :memo: 修复迁移指南**ssl配置**文档错误
- :bug: 修复包长度计算错误,压测下协议解析异常 gitee #I6YOMD 感谢 `@powerxie` 反馈
### v2.1.1 - 2023-04-08
- :sparkles: mica-mqtt-server http-api 不再强制依赖 `fastjson` 还支持 `Jackson``Fastjson2``Gson``hutool-json` 和自定义, `@皮球` 反馈 gitee #I6O49D
- :sparkles: mica-mqtt-codec 删除 `org.dromara.mica.mqtt.codec.ByteBufferUtil`2.1.0 漏删。
- :sparkles: mica-mqtt-codec 兼容 qos大于0messageId == 0做 qos 降级处理,`@那一刹的容颜` 反馈,详见 gitee #I6PFIH
- :sparkles: mica-mqtt-codec maxClientIdLength 默认改为 64gitee #I6P2CG
- :sparkles: mica-mqtt-client 优化链接时的遗嘱消息构建,默认为 qos0。`@tan90` 反馈 gitee #I6BRBV
- :bug: mqtt-server 修复 mqtt.js websocket 空包问题,感谢群友反馈。
- :bug: mqtt-server 修复 websocket mqtt 包长度判断问题。
- :arrow_up: 依赖升级
### v2.1.0 - 2023-03-05
- :sparkles: 【不兼容】调整接口参数,方便使用
- :sparkles: 【不兼容】底层重构调整
- :sparkles: 兼容更多 Spring boot 版本,支持 `2.1.0.RELEASE` 以上版本。
- :sparkles: ssl 支持双向认证 gitee #I61AHJ 感谢 @DoubleH 反馈
- :bug: 修复遗嘱消息判断 gitee #I6BRBV 感谢 @tan90 反馈。
- :bug: 修复错别字 gitee #I6F2PA 感谢 @hpz 反馈
- :arrow_up: 依赖升级
### v2.0.3 - 2022-09-18
- :sparkles: 完善 ssl 方法,方便使用。
- :arrow_up: 依赖升级,避免依赖导致的 bug。
### v2.0.2 - 2022-09-13
- :bug: 彻底修复解码异常: `BufferUnderflowException`
### v2.0.1 - 2022-09-12
- :sparkles: 优化 MqttWebServer 配置。
- :sparkles: mica-mqtt-example 添加华为云iot连接示例。
- :sparkles: mica-mqtt-example 改为使用 tinylog。
- :bug: 修复解码异常: `BufferUnderflowException`
### v2.0.0 - 2022-09-04
- :sparkles: mica mqtt server 完善方法,方便使用。
- :sparkles: 切换到自维护的 java8 t-io注意升级了 t-io 部分类名变更。
### v1.3.9 - 2022-08-28
- :sparkles: mica-mqtt server 添加消息拦截器gitee #I5KLST
- :sparkles: mica-mqtt client、server ack 优化和完善,可自定义 ackService。
- :sparkles: mica-mqtt client stater MqttClientTemplate 完善,统一调整客户端示例。
- :sparkles: mica-mqtt client 优化客户端心跳和心跳日志优化。
- :sparkles: mica-mqtt client 订阅代码优化。
- :sparkles: mica-mqtt codec 代码优化。
- :sparkles: test 代码优化,更加符合 junit5 规范。
- :bug: mqtt client Qos2 修复。
### v1.3.8 - 2022-08-11
- :sparkles: mica-mqtt codec 代码优化。
- :sparkles: mica-mqtt server 使用 Spring event 解耦消息监听。
- :sparkles: mica-mqtt client stater@MqttClientSubscribe topic 支持其他变量 ${productKey} 自动替换成 +。
- :memo: 添加演示地址
- :bug: 修复 mica-mqtt client 心跳发送问题。gitee #I5LQXV 感谢 `@iTong` 反馈。
### v1.3.7 - 2022-07-24
- :sparkles: 添加 mica-mqtt jfinal client 和 server 插件。
- :sparkles: mica-mqtt server 代码优化useQueueDecode 默认为 true。
- :sparkles: mica-mqtt client 监听回调代码优化。
- :memo: 添加赞助,让你我走的更远!!!
- :arrow_up: 依赖升级。
### v1.3.6 - 2022-06-25
- :sparkles: mica-mqtt 统一调整最大的消息体和一次读取的字节数。
- :sparkles: mica-mqtt client 简化 ssl 开启。
- :sparkles: mica-mqtt server 添加默认的账号密码配置。
- :arrow_up: 依赖升级
### v1.3.4 - 2022-06-06
- :sparkles: mica-mqtt starter 使用 Spring event 解耦 mqtt client 断连事件。
- :sparkles: mica-mqtt server `IMqttConnectStatusListener#offline` 方法添加 `reason` 断开原因字段。
- :sparkles: 添加赞助计划。**捐助共勉,让你我走的更远!!!**
- :bug: 修复 http api 响应问题。
### v1.3.3 - 2022-05-28
- :sparkles: mica-mqtt 优化线程池。
- :sparkles: mica-mqtt 添加 Compression 压缩接口。
- :sparkles: mica-mqtt 添加 kafka TimingWheel 重构 ack。
- :sparkles: mica-mqtt server 添加 `MqttClusterMessageListener` 方便集群消息处理。
- :sparkles: mica-mqtt client 优化客户端取消订阅逻辑gitee #I5779A 感谢 `@杨钊` 同学反馈。
- :arrow_up: 升级 fastjson 到 1.2.83。
### v1.3.2 - 2022-05-09
- :sparkles: mica-mqtt topic 匹配完善。
- :sparkles: mica-mqtt 订阅、发布时添加 topicFilter、topicName 校验。
### v1.3.1 - 2022-05-08
- :sparkles: mica-mqtt-broker 默认开启 http 和 basic auth。
- :sparkles: mica-mqtt server 添加服务端共享订阅接口,方便开源之夏学生参与。
- :sparkles: mica-mqtt server 添加 IMqttSessionListener。
- :sparkles: mica-mqtt server publish 保留消息存储。
- :sparkles: mica-mqtt server 统一 http 响应模型、优化 http 请求判断。
- :sparkles: mica-mqtt server 优化 MqttHttpRoutes添加获取所有路由的方法。
- :sparkles: mica-mqtt server 完善 Result 和 http api。
- :sparkles: mica-mqtt server http api 添加 endpoints 列表接口。
- :sparkles: mica-mqtt client 添加同步连接 connectSync 方法。
- :sparkles: mica-mqtt client 优化 bean 依赖,减少循环依赖可能性。
- :bug: 重构 mqtt topic 匹配规则,提升性能减少内存占用,修复 gitee #I56BTC
- :arrow_up: spring boot、mica 版本升级
### v1.3.0 - 2022-04-17
- :sparkles: mica-mqtt mqtt-server 简化,默认多设备可以直接互相订阅和处理消息。
- :sparkles: mica-mqtt server、client 添加 `tioConfigCustomize` 方法,方便更大程度的自定义 TioConfig。
- :sparkles: 拆分 mica-mqtt-client-spring-boot-starter 和 mica-mqtt-server-spring-boot-starter gitee #I4OTC5
- :sparkles: mica-mqtt-client-spring-boot-example 添加重连动态更新 clientId、username、password 示例。
- :sparkles: mica-mqtt server 添加根据踢出指定 clientId 的 http api 接口。
- :sparkles: mica-mqtt server IMqttConnectStatusListener api 调整,添加用户名字段。
- :sparkles: mica-mqtt server IMqttMessageListener 不再强制要求实现。
- :sparkles: 使用 netty IntObjectHashMap 优化默认 session 存储。
- :sparkles: 添加 github action用于自动构建开发阶段的 SNAPSHOT 版本。
- :sparkles: 示例项目拆分到 example 目录mica-mqtt client、server starter 拆分到 starter 目录。
- :arrow_up: 依赖升级.
### v1.2.10 - 2022-03-20
- :sparkles: mica-mqtt server 添加 MQTT 客户端 keepalive 系数 `keepalive-backoff`
- :sparkles: mica-mqtt client、server 调整发布的日志级别为 debug。
- :sparkles: mica-mqtt client 优化 javadoc。
- :sparkles: mica-mqtt client 重连时,支持重新设置新的鉴权密码。
### v1.2.9 - 2022-03-06
- :sparkles: mqttServer#publishAll() 日志级别调整 gitee #I4W4IS
- :sparkles: @MqttClientSubscribe 支持 springboot 配置 gitee #I4UOR3
- :sparkles: mica-mqtt client 代码优化
- :sparkles: mica-mqtt-spring-boot-example 拆分
### v1.2.8 - 2022-02-20
- :sparkles: mica-mqtt server 优化连接 connect 日志。
- :sparkles: mica-mqtt server 代码优化。
- :sparkles: mica-mqtt server 添加 statEnable 配置,默认关闭,开启 Prometheus 监控,需要设置为 true。
- :sparkles: mica-mqtt client 添加 statEnable 配置,默认关闭。
- :sparkles: mica-mqtt client 优化默认线程池。
### v1.2.7 - 2022-02-13
- :sparkles: mica-mqtt-spring-boot-starter 完善。
- :sparkles: mica-mqtt client 考虑一开始就没有连接上服务端的情况。
- :sparkles: mica-mqtt client 添加 isConnected 方法
- :sparkles: mica-mqtt client、server connectListener 改为异步
- :sparkles: mica-mqtt server ChannelContext 添加用户名,使用 (String) context.get(MqttConst.USER_NAME_KEY) 获取。
- :sparkles: websocket ssl 配置
- :sparkles: 尝试新版 graalvm
- :bug: 修复多个 mica mqtt client 消息id生成器隔离。
### v1.2.6 - 2022-01-19
- :sparkles: mica-mqtt-client 支持 `$share``$queue` 共享订阅
### v1.2.5 - 2022-01-16
- :sparkles: mica mqtt server 调整发布权限规则。
- :sparkles: mica mqtt server 自定义接口的异常处理。
- :sparkles: mica mqtt server 放开 tio 队列配置。
- :sparkles: mica mqtt client publish 添加一批 byte[] payload 参数方法。
- :sparkles: mica-mqtt-model DefaultMessageSerializer 重构,**不兼容**。
- :memo: 添加日志,避免遗忘。
- :bug: http websocket 都不开启并排除 tio-websocket-server 依赖时 gitee #I4Q3CP
### v1.2.4 - 2022-01-09
- :fire: mica-mqtt-core 排除一些不需要的依赖。
- :fire: mica-mqtt-core http websocket 都不开启时,可以排除 tio-websocket-server 依赖。
- :sparkles: mica-mqtt-core MqttTopicUtil 改名为 TopicUtil。
- :sparkles: mica-mqtt-spring-boot-starter `@MqttClientSubscribe` 支持 IMqttClientMessageListener bean。
- :sparkles: mica-mqtt-spring-boot-starter `@MqttClientSubscribe` 支持自定义 MqttClientTemplate Bean。
- :sparkles: mica-mqtt-spring-boot-starter 完善。
- :sparkles: mica-mqtt-codec 缩短 mqtt 版本 key。
- :bug: mica-mqtt-codec 修复 will message。
### v1.2.3 - 2022-01-03
- :sparkles: mica-mqtt-spring-boot-starter `@MqttClientSubscribe` value 改为数组,支持同时订阅多 topic。
- :sparkles: mica-mqtt-core 缓存 TopicFilter Pattern。
- :sparkles: mica-mqtt-core 优化客户端和服务端订阅逻辑 `IMqttServerSubscribeValidator` 接口调整。
- :sparkles: mica-mqtt client 添加批量订阅。
- :sparkles: mica-mqtt client 添加批量取消订阅。
- :sparkles: mica-mqtt client 添加客户端是否断开连接。
- :sparkles: mica-mqtt client 客户端断开重新订阅时支持配置批次大小。
- :bookmark: mica-mqtt client 订阅 `IMqttClientMessageListener` 添加 `onSubscribed` 默认方法。
- :arrow_up: mica-mqtt-example 升级 log4j2 到 2.17.1
### v1.2.2 - 2021-12-26
- :sparkles: mica-mqtt server 添加发布权限接口,无权限直接断开连接,避免高级别 qos 重试浪费资源。
- :sparkles: mica-mqtt-broker 优化节点信息存储
- :sparkles: mica-mqtt client 重复订阅优化。感谢 `@一片小雨滴`
- :sparkles: mica-mqtt client 抽象 IMqttClientSession 接口。
- :bug: 修复重构 AbstractMqttMessageDispatcher 保持跟 mica-mqtt-broker 逻辑一致 gitee #I4MA6A 感谢 `@胡萝博`
- :arrow_up: mica-mqtt-example 升级 log4j2 到 2.17.0
### v1.2.1 - 2021-12-11
- :sparkles: mica-mqtt 优化 topic 匹配。
- :sparkles: mica-mqtt client disconnect 不再自动重连 gitee #I4L0WK 感谢 `@willianfu`
- :sparkles: mica-mqtt client 添加 retryCount 配置 gitee #I4L0WK 感谢 `@willianfu`
- :sparkles: mica-mqtt-model message 添加 json 序列化。
- :sparkles: mica-mqtt-broker 重新梳理逻辑。
- :bug: mica-mqtt-spring-boot-starter 在 boot 2.6.x 下 bean 循环依赖 gitee #I4LUZP 感谢 `@hongfeng11`
- :bug: mica-mqtt server 同一个 clientId 踢出时清除老的 session。
- :bug: mica-mqtt server 集群下一个 clientId 只允许连接到一台服务器。
- :bug: mica-mqtt client 修复 IMqttClientConnectListener onDisconnect 空指针。
- :memo: mica-mqtt-model 添加 README.md
### v1.2.0 - 2021-11-28
- :sparkles: mqtt-mqtt-core client IMqttClientConnectListener 添加 onDisconnect 方法。gitee #I4JT1D 感谢 `@willianfu` 同学反馈。
- :sparkles: mica-mqtt-core server IMqttMessageListener 接口调整,不兼容老版本。
- :sparkles: mica-mqtt-broker 调整上下行消息通道。
- :sparkles: mica-mqtt-broker 添加节点管理。
- :sparkles: mica-mqtt-broker 调整默认的 Message 序列化方式,不兼容老版本。
- :sparkles: mica-mqtt-broker 优化设备上下线,处理节点停机的情况。
- :sparkles: 抽取 mica-mqtt-model 模块方便后期支持消息桥接Message 添加默认的消息序列化。 gitee #I4ECEO
- :sparkles: mica-mqtt-model 完善 Message 消息模型,方便集群。
- :bug: mica-mqtt-core MqttClient 修复 ssl 没有设置,感谢 `@hjkJOJO` 同学反馈。
- :bug: 修复 websocket mqtt.js 需要拆包 gitee #I4JYJX 感谢 `@Symous` 同学反馈。
- :memo: 完善 mica-mqtt-broker README.md添加二开说明。
- :memo: 统一 mica-mqtt server ip 文档。
- :memo: 更新 README.md
- :arrow_up: 升级 tio 到 3.7.5.v20211028-RELEASE AioDecodeException 改为 TioDecodeException
### v1.1.4 - 2021-10-16
- :sparkles: 添加 uniqueId 概念,用来处理 clientId 不唯一的场景。详见gitee #I4DXQU
- :sparkles: 微调 `IMqttServerAuthHandler` 认证,添加 uniqueId 参数。
### v1.1.3 - 2021-10-13
- :sparkles: 状态事件接口 `IMqttConnectStatusListener` 添加 ChannelContext 参数。
- :sparkles: 从认证中拆分 `IMqttServerSubscribeValidator` 订阅校验接口,添加 ChannelContext、clientId 参数。
- :sparkles: 认证 `IMqttServerAuthHandler` 调整包、添加 ChannelContext 参数。
- :sparkles: 完善文档和示例,添加默认端口号说明。
- :arrow_up: 依赖升级
### v1.1.2 - 2021-09-12
- :sparkles: 添加 mica-mqtt-broker 模块,基于 redis pub/sub 实现 mqtt 集群。
- :sparkles: mica-mqtt-broker 基于 redis 实现客户端状态存储。
- :sparkles: mica-mqtt-broker 基于 redis 实现遗嘱、保留消息存储。
- :sparkles: mqtt-server http api 调整订阅和取消订阅,方便集群处理。
- :sparkles: mica-mqtt-spring-boot-example 添加 mqtt 和 http api 认证示例。
- :sparkles: 添加 mqtt 5 所有 ReasonCode。
- :sparkles: 优化解码 PacketNeededLength 计算。
- :bug: 修复遗嘱消息,添加消息类型。
- :bug: 修复 mqtt-server 保留消息匹配规则。
### v1.1.1 - 2021-09-05
- :sparkles: mqtt-server 优化连接关闭日志。
- :sparkles: mqtt-server 优化订阅,相同 topicFilter 订阅对 qos 判断。
- :sparkles: mqtt-server 监听器添加 try catch避免因业务问题导致连接断开。
- :sparkles: mqtt-server 优化 topicFilters 校验。
- :sparkles: mqtt-client 优化订阅 reasonCodes 判断。
- :sparkles: mqtt-client 监听器添加 try catch避免因业务问题导致连接断开。
- :sparkles: mqtt-client 添加 session 有效期。
- :sparkles: 代码优化,减少 codacy 上的问题。
- :bug: mqtt-server 修复心跳时间问题。
- :bug: 修复 mqtt-server 多个订阅同时匹配时消息重复的问题。
- :bug: mqtt-client 优化连接处理的逻辑mqtt 连接之后再订阅。
- :bug: 修复 MqttProperties 潜在的一个空指针。
### v1.1.0 - 2021-08-29
- :sparkles: 重构,内置 httphttp 和 websocket 公用端口。
- :sparkles: 添加 mica-core 中的 HexUtil。
- :sparkles: 添加 PayloadEncode 工具。
- :sparkles: ServerTioConfig#share 方法添加 groupStat。
- :sparkles: 考虑使用 udp 多播做集群。
- :sparkles: MqttServer、MqttServerTemplate 添加 close、getChannelContext 等方法。
- :sparkles: 重构 MqttServerConfiguration 简化代码。
- :sparkles: 配置项 `mqtt.server.websocket-port` 改为 `mqtt.server.web-port`
- :memo: 添加 JetBrains 连接。
- :bug: 修复默认的消息转发器逻辑。
- :bug: 修复 websocket 下线无法触发offline gitee #I47K13 感谢 `@willianfu` 同学反馈。
### v1.0.6 - 2021-08-21
- :sparkles: 添加订阅 topicFilter 校验。
- :sparkles: 优化压测工具,更新压测说明,添加 tcp 连接数更改文档地址。
- :sparkles: mica-mqtt-example 添加多设备交互示例。
- :sparkles: 优化 mica-mqtt-spring-boot-example。
- :sparkles: 优化 deploy.sh 脚本。
- :bug: 优化解码异常处理。
- :bug: 修复心跳超时处理。
- :arrow_up: 升级 spring boot 到 2.5.4
### v1.0.5 - 2021-08-15
- :bug: 修复编译导致的 java8 运行期间的部分问题NoSuchMethodError: java.nio.ByteBuffer.xxx
### v1.0.3 - 2021-08-15
- :sparkles: mica-mqtt server 添加 websocket mqtt 子协议支持(支持 mqtt.js
- :sparkles: mica-mqtt server ip默认为空可不设置。
- :sparkles: mica-mqtt client去除 CountDownLatch 避免启动时未连接上服务端卡住。
- :sparkles: mica-mqtt client 添加最大包体长度字段,避免超过 8092 长度的包体导致解析异常。
- :sparkles: mica-mqtt client 添加连接监听 IMqttClientConnectListener。
- :sparkles: mica-mqtt 3.1 协议会校验 clientId 长度,添加配置项 maxClientIdLength。
- :sparkles: mica-mqtt 优化 mqtt 解码异常处理。
- :sparkles: mica-mqtt 日志优化,方便查询。
- :sparkles: mica-mqtt 代码优化,部分 Tio.close 改为 Tio.remove。
- :sparkles: mica-mqtt-spring-boot-example 添加 Dockerfile支持 `spring-boot:build-image`
- :sparkles: 完善 mica-mqtt-spring-boot-starter添加遗嘱消息配置。
- :arrow_up: 升级 t-io 到 3.7.4。
### v1.0.3-RC - 2021-08-12
- :sparkles: 添加 websocket mqtt 子协议支持(支持 mqtt.js
- :sparkles: mqtt 客户端去除 CountDownLatch 避免启动时未连接上服务端卡住。
- :sparkles: mica-mqtt 服务端 ip默认为空可不设置。
- :sparkles: 完善 mica-mqtt-spring-boot-starter添加遗嘱消息配置。
- :sparkles: mqtt 3.1 协议会校验 clientId 长度,添加设置。
- :sparkles: mqtt 日志优化,方便查询。
- :sparkles: 代码优化,部分 Tio.close 改为 Tio.remove。
- :arrow_up: 升级 t-io 到 3.7.4。
### v1.0.2 - 2021-08-08
- :memo: 文档添加集群处理步骤说明,添加遗嘱消息、保留消息的使用场景。
- :sparkles: 去除演示中的 qos2 参数,性能损耗大避免误用。
- :sparkles: 遗嘱、保留消息内部消息转发抽象。
- :sparkles: mqtt server 连接时先判断 clientId 是否存在连接关系,有则先关闭已有连接。
- :sparkles: 添加 mica-mqtt-spring-boot-example 。感谢 wsq @冷月宫主 pr。
- :sparkles: mica-mqtt-spring-boot-starter 支持客户端接入和服务端优化。感谢 wsq @冷月宫主 pr。
- :sparkles: mica-mqtt-spring-boot-starter 服务端支持指标收集。可对接 `Prometheus + Grafana` 监控。
- :sparkles: mqtt server 接受连接时,先判断该 clientId 是否存在其它连接,有则解绑并关闭其他连接。
- :arrow_up: 升级 mica-auto 到 2.1.3 修复 ide 多模块增量编译问题。
### v1.0.2-RC - 2021-08-04
- :sparkles: 添加 mica-mqtt-spring-boot-example 。感谢 wsq @冷月宫主 pr。
- :sparkles: mica-mqtt-spring-boot-starter 支持客户端接入和服务端优化。感谢 wsq @冷月宫主 pr。
- :sparkles: mica-mqtt-spring-boot-starter 服务端支持指标收集。可对接 `Prometheus + Grafana` 监控。
- :sparkles: mqtt server 接受连接时,先判断该 clientId 是否存在其它连接,有则解绑并关闭其他连接。
### v1.0.1 - 2021-08-02
- :sparkles: 订阅管理集成到 session 管理中。
- :sparkles: MqttProperties.MqttPropertyType 添加注释,考虑 mqtt V5.0 新特性处理。
- :sparkles: 添加 Spring boot starter 方便接入,兼容低版本 Spring boot。
- :sparkles: 调研 t-io websocket 子协议。
- :bug: 修复 java8 运行期间的部分问题NoSuchMethodError: java.nio.ByteBuffer.xxx
### v1.0.1-RC - 2021-07-31
- :sparkles: 添加 Spring boot starter 方便接入。
- :sparkles: 调研 t-io websocket 子协议。
### v1.0.0 - 2021-07-29
- :sparkles: 基于低延迟高性能的 t-io AIO 框架。
- :sparkles: 支持 MQTT v3.1、v3.1.1 以及 v5.0 协议。
- :sparkles: 支持 MQTT client 客户端。
- :sparkles: 支持 MQTT server 服务端。
- :sparkles: 支持 MQTT 遗嘱消息。
- :sparkles: 支持 MQTT 保留消息。
- :sparkles: 支持自定义消息mq处理转发实现集群。
- :sparkles: 支持 GraalVM 编译成本机可执行程序。

191
LICENSE Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

129
README.en.md Normal file
View File

@@ -0,0 +1,129 @@
# 🌐 Dromara mica mqtt
[![Java CI](https://github.com/dromara/mica-mqtt/workflows/Java%20CI/badge.svg)](https://github.com/dromara/mica-mqtt/actions)
![JAVA 8](https://img.shields.io/badge/JDK-1.8+-brightgreen.svg)
[![Mica Maven release](https://img.shields.io/maven-central/v/org.dromara.mica-mqtt/mica-mqtt-codec?style=flat-square)](https://central.sonatype.com/artifact/org.dromara.mica-mqtt/mica-mqtt-codec/versions)
![Mica Maven SNAPSHOT](https://img.shields.io/maven-metadata/v?metadataUrl=https://central.sonatype.com/repository/maven-snapshots/org/dromara/mica-mqtt/mica-mqtt-codec/maven-metadata.xml)
[![GitHub](https://img.shields.io/github/license/dromara/mica-mqtt.svg?style=flat-square)](https://github.com/dromara/mica-mqtt/blob/master/LICENSE)
[![star](https://gitcode.com/dromara/mica-mqtt/star/badge.svg)](https://gitcode.com/dromara/mica-mqtt)
[![star](https://gitee.com/dromara/mica-mqtt/badge/star.svg?theme=dark)](https://gitee.com/dromara/mica-mqtt/stargazers)
[![GitHub Repo stars](https://img.shields.io/github/stars/dromara/mica-mqtt?label=Github%20Stars)](https://github.com/dromara/mica-mqtt)
---
📖English | [📖简体中文](README.md)
Dromara `mica-mqtt` is a **low-latency, high-performance** MQTT IoT component designed for seamless integration and scalability. For detailed usage guides, refer to the **mica-mqtt-example** module.
## 🍱 Use Cases
- Internet of Things (cloud-based MQTT broker)
- Internet of Things (edge messaging communication)
- Group IM
- Message push
- Easy-to-use MQTT client
## 🚀 Key Advantages
- **Simplicity & Flexibility**: Intuitive design for easy integration while retaining extensibility for advanced use cases.
- **Manual Control**: Explicit API design to facilitate secondary development and customization.
- **Future-ready**: Built with scalability in mind, supporting evolving IoT requirements.
## ✨ Features
- [x] Supports MQTT v3.1, v3.1.1, and v5.0 protocols.
- [x] Supports WebSocket MQTT sub-protocol (fully compatible with mqtt.js).
- [x] Supports HTTP REST API - see [HTTP API Documentation](docs/http-api.md) for details.
- [x] Support for MQTT client, support Android native.
- [x] Support for MQTT server, support Android native.
- [x] Support for MQTT Will messages.
- [x] Support for MQTT Retained messages.
- [x] Support for custom message (MQ) processing and forwarding to achieve clustering.
- [x] MQTT client **Alibaba Cloud MQTT**、**HuaWei MQTT** connection demo.
- [x] Support for GraalVM compilation into native executable programs.
- [x] Support for rapid access to Spring Boot、Solon and JFinal projects.
- [x] Spring boot and Solon client plugins support session retention.
- [x] Support for integration with Prometheus + Grafana for monitoring.
- [x] Cluster implementation based on Redis pub/sub, see [mica-mqtt-broker module](mica-mqtt-broker) for details.
## 🌱 To-do
- [ ] Optimize the handling of MQTT server sessions and simplify the use of MQTT v5.0.
- [ ] Implement rule engine based on easy-rule + druid sql parsing.
## 🚨 Default Ports
| Port | Protocol | Description |
|-------|---------------|-------------------------|
| 1883 | tcp | mqtt tcp port |
| 8883 | tcp ssl | mqtt tcp ssl port |
| 8083 | websocket | websocket mqtt port |
| 8084 | websocket ssl | websocket ssl mqtt port |
| 18083 | http | http、MCP api port |
**Demo Address**: mqtt.dreamlu.net, same portsusername: mica password: mica
## 📦️ Dependencies
### Spring Boot Project
**Client:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**Configuration Details**: [mica-mqtt-client-spring-boot-starter Documentation](starter/mica-mqtt-client-spring-boot-starter/README.md)
**Server:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-spring-boot-starter</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**Configuration Details**: [mica-mqtt-server-spring-boot-starter Documentation](starter/mica-mqtt-server-spring-boot-starter/README.md)
### Non-Spring Boot Project
**Client:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**Configuration Details**: [mica-mqtt-client Documentation](mica-mqtt-client/README.md)
**Server:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**Configuration Details**: [mica-mqtt-server Documentation](mica-mqtt-server/README.md)
## 📝 Documentation
- [Introduction to MQTT, mqttx, and mica-mqtt **Video**](https://www.bilibili.com/video/BV1wv4y1F7Av/)
- [Getting Started with mica-mqtt](example/README.md)
- [mica-mqtt HTTP API Documentation](docs/http-api.md)
- [Frequently Asked Questions about mica-mqtt Usage](https://mica-mqtt.dreamlu.net/faq/faq.html)
- [mica-mqtt Release Versions](CHANGELOG.md)
## 🍻 Open Source Recommendations
- `Avue`: A Vue-based configurable front-end framework: [https://gitcode.com/superwei/avue](https://gitcode.com/superwei/avue)
- `Pig`: Microservice framework featured on CCTV (architectural essential): [https://gitcode.com/pig-mesh/pig](https://gitcode.com/pig-mesh/pig)
- `SpringBlade`: Enterprise-level solution (essential for enterprise development): [https://gitcode.com/bladex/SpringBlade](https://gitcode.com/bladex/SpringBlade)
## 📱 WeChat
![DreamLuTech](docs/img/dreamlu-weixin.jpg)
**JAVA Architecture Diary**, daily recommended exciting content!

180
README.md Normal file
View File

@@ -0,0 +1,180 @@
# 🌐 Dromara mica mqtt 组件
[![Java CI](https://github.com/dromara/mica-mqtt/workflows/Java%20CI/badge.svg)](https://github.com/dromara/mica-mqtt/actions)
![JAVA 8](https://img.shields.io/badge/JDK-1.8+-brightgreen.svg)
[![Mica Maven release](https://img.shields.io/maven-central/v/org.dromara.mica-mqtt/mica-mqtt-codec?style=flat-square)](https://central.sonatype.com/artifact/org.dromara.mica-mqtt/mica-mqtt-codec/versions)
![Mica Maven SNAPSHOT](https://img.shields.io/maven-metadata/v?metadataUrl=https://central.sonatype.com/repository/maven-snapshots/org/dromara/mica-mqtt/mica-mqtt-codec/maven-metadata.xml)
[![GitHub](https://img.shields.io/github/license/dromara/mica-mqtt.svg?style=flat-square)](https://github.com/dromara/mica-mqtt/blob/master/LICENSE)
[![star](https://gitcode.com/dromara/mica-mqtt/star/badge.svg)](https://gitcode.com/dromara/mica-mqtt)
[![star](https://gitee.com/dromara/mica-mqtt/badge/star.svg?theme=dark)](https://gitee.com/dromara/mica-mqtt/stargazers)
[![GitHub Repo stars](https://img.shields.io/github/stars/dromara/mica-mqtt?label=Github%20Stars)](https://github.com/dromara/mica-mqtt)
---
📖简体中文 | [📖English](README.en.md)
Dromara `mica-mqtt` **低延迟**、**高性能**的 `mqtt` 物联网组件。更多使用方式详见: **mica-mqtt-example** 模块。
✨✨✨**最佳实践**✨✨✨ [**BladeX 物联网平台(「mica-mqtt加强版」+「EMQX+Kafka插件」双架构)**](https://iot.bladex.cn?from=mica-mqtt)
## 🍱 使用场景
- 物联网(云端 mqtt broker
- 物联网(边缘端消息通信)
- 群组类 IM
- 消息推送
- 简单易用的 mqtt 客户端
## 🚀 优势
- ✓ 轻如蝉翼 - 核心依赖仅 500KB
- ✓ 新手友好 - 5 分钟极速上手
- ✓ 自由操控 - 手动档设计,扩展随心
- ✓ 潜力无限 - 小而强大,未来可期
## ✨ 功能
- [x] 支持 MQTT v3.1、v3.1.1 以及 v5.0 协议。
- [x] 支持 websocket mqtt 子协议(支持 mqtt.js
- [x] 支持 http rest api[http api 文档详见](docs/http-api.md)。
- [x] 支持 MQTT client 客户端,支持 **Android** 最低要求 API 26Android 8.0)。
- [x] 支持 MQTT server 服务端,支持 **Android** 最低要求 API 26Android 8.0)。
- [x] 支持 MQTT client、server 共享订阅支持。
- [x] 支持 MQTT 遗嘱消息。
- [x] 支持 MQTT 保留消息。
- [x] 支持自定义消息mq处理转发实现集群。
- [x] MQTT 客户端 **阿里云 mqtt**、**华为云 mqtt** 连接 demo 示例。
- [x] 支持 GraalVM 编译成本机可执行程序。
- [x] 支持 Spring boot、Solon 和 JFinal 项目快速接入。
- [x] Spring boot、Solon client 插件支持保留 session。
- [x] 支持对接 Prometheus + Grafana 实现监控。
- [x] 基于 redis stream 实现集群,详见 [mica-mqtt-broker 模块](https://gitee.com/dromara/mica-mqtt/tree/2.4.x/mica-mqtt-broker)。
- [x] [mica mqtt 控制台](https://gitee.com/dreamlu/mica-mqtt-dashboard)
## 🌱 待办
- [ ] 优化处理 mqtt 服务端 session以及简化 mqtt v5.0 使用。
- [ ] 基于 easy-rule + druid sql 解析,实现规则引擎。
## 🚨 默认端口
| 端口号 | 协议 | 说明 |
|-------|---------------|--------------------------|
| 1883 | tcp | mqtt tcp 端口 |
| 8883 | tcp ssl | mqtt tcp ssl 端口 |
| 8083 | websocket | websocket mqtt 子协议端口 |
| 8084 | websocket ssl | websocket ssl mqtt 子协议端口 |
| 18083 | http | http、大模型 MCP 接口端口 |
**演示地址**mqtt.dreamlu.net 端口同上账号mica 密码mica
## 📦️ 依赖
### Spring boot 项目
**客户端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-client-spring-boot-starter 使用文档](starter/mica-mqtt-client-spring-boot-starter/README.md)
**服务端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-spring-boot-starter</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-server-spring-boot-starter 使用文档](starter/mica-mqtt-server-spring-boot-starter/README.md)
### Solon 项目
**客户端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-solon-plugin</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-client-solon-plugin 使用文档](starter/mica-mqtt-client-solon-plugin/README.md)
**服务端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-solon-plugin</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-server-solon-plugin 使用文档](starter/mica-mqtt-server-solon-plugin/README.md)
### JFinal 项目
**客户端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-jfinal-plugin</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-client-jfinal-plugin 使用文档](starter/mica-mqtt-client-jfinal-plugin/README.md)
**服务端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-jfinal-plugin</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-server-jfinal-plugin 使用文档](starter/mica-mqtt-server-jfinal-plugin/README.md)
### 其他项目
**客户端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-client 使用文档](mica-mqtt-client/README.md)
**服务端:**
```xml
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server</artifactId>
<version>${mica-mqtt.version}</version>
</dependency>
```
**配置详见**[mica-mqtt-server 使用文档](mica-mqtt-server/README.md)
## 📝 文档
- [mqtt科普、mqttx、mica-mqtt的使用**视频**](https://www.bilibili.com/video/BV1wv4y1F7Av/)
- [mica-mqtt 快速开始](https://mica-mqtt.dreamlu.net/guide/)
- [mica-mqtt 使用常见问题汇总](https://mica-mqtt.dreamlu.net/faq/faq.html)
- [mica-mqtt 发行版本](https://mica-mqtt.dreamlu.net/version/changelog.html)
- [mica-mqtt 老版本迁移指南](https://mica-mqtt.dreamlu.net/version/update.html)
## 🍻 开源推荐
- `Avue` 基于 vue 可配置化的前端框架:[https://gitcode.com/superwei/avue](https://gitcode.com/superwei/avue)
- `pig` 上央视的微服务框架(架构必备):[https://gitcode.com/pig-mesh/pig](https://gitcode.com/pig-mesh/pig)
- `SpringBlade` 企业级解决方案(企业开发必备):[https://gitcode.com/bladex/SpringBlade](https://gitcode.com/bladex/SpringBlade)
## 📱 微信
![如梦技术](docs/img/dreamlu-weixin.jpg)
**JAVA架构日记**,精彩内容每日推荐!

14
SECURITY.md Normal file
View File

@@ -0,0 +1,14 @@
# Security Policy安全策略
## Supported Versions支持的版本
| Version | Supported |
|---------|--------------------|
| 2.4.x | :white_check_mark: |
| < 2.4.x | :x: |
## Reporting a Vulnerability报告漏洞
如果你发现有安全问题或漏洞,请发送邮件到 `596392912@qq.com`
To report any found security issues or vulnerabilities, please send a mail to `596392912@qq.com`.

43
deploy.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/sh
## 0. java
if command -v vfox >/dev/null 2>&1; then
vfox use java@8.0.342+7
else
echo "Warning: vfox command not found, skipping Java version switch"
fi
## 1. java version
java -version
printf "\n"
## 2. mvn version
mvn -version
printf "\n"
## 3. 环境
if [ -z $1 ]; then
profile="release"
else
profile="$1"
fi
printf "profile [%s] \n" "$profile"
## 4. modules
modules="mica-mqtt-codec,mica-mqtt-common,"
modules="$modules mica-mqtt-client,mica-mqtt-server,"
modules="$modules starter/mica-mqtt-client-spring-boot-starter,"
modules="$modules starter/mica-mqtt-server-spring-boot-starter,"
modules="$modules starter/mica-mqtt-client-solon-plugin,"
modules="$modules starter/mica-mqtt-server-solon-plugin,"
modules="$modules starter/mica-mqtt-client-jfinal-plugin,"
modules="$modules starter/mica-mqtt-server-jfinal-plugin"
printf "modules [%s] \n" "$modules"
## 5. deploy
if [ "$profile" = "snapshot" ]; then
mvn clean deploy -U -P!develop,snapshot -pl "$modules"
else
mvn clean deploy -Prelease -pl "$modules"
fi

2
docs/graalvm.md Normal file
View File

@@ -0,0 +1,2 @@
# mica-mqtt 之 GraalVM native-image 编译成本机可执行程序

426
docs/http-api.md Normal file
View File

@@ -0,0 +1,426 @@
# Http Api 接口
注意mica-mqtt 2.5.x 开始http api 端口默认 18083老版本~~8083~~)。
### HTTP 状态码 (status codes)
接口在调用成功时总是返回 200 OK响应内容则以 JSON 格式返回。
可能的状态码如下:
| Status Code | Description |
| ----------- | -------------------------------------------------------- |
| 200 | 成功,返回的 JSON 数据将提供更多信息 |
| 400 | 客户端请求无效,例如请求体或参数错误 |
| 401 | 客户端未通过服务端认证,使用无效的身份验证凭据可能会发生 |
| 404 | 找不到请求的路径或者请求的对象不存在 |
| 405 | 请求方法错误 |
| 500 | 服务端处理请求时发生内部错误 |
### 返回码 (result codes)
接口的响应消息体为 JSON 格式,其中总是包含返回码 `code`
可能的返回码如下:
| Return Code | Description |
| ----------- | -------------------------- |
| 1 | 成功 |
| 101 | 关键请求参数缺失 |
| 102 | 请求参数错误 |
| 103 | 用户名或密码错误 |
| 104 | 请求方法错误 |
| 105 | 未知错误 |
## 获取所有 api 接口列表
### GET /api/v1/endpoints
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- |---------|-------------|
| code | Integer | 1 |
| data | Array | 接口列表 |
| method | String | 方法名 |
| path | String | 路径 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica "http://localhost:18083/api/v1/endpoints"
{
"data": [
{
"method": "GET",
"path": "/api/v1/endpoints"
},
{
"method": "GET",
"path": "/api/v1/clients"
},
{
"method": "POST",
"path": "/api/v1/mqtt/unsubscribe"
},
{
"method": "GET",
"path": "/api/v1/clients/info"
},
{
"method": "GET",
"path": "/api/v1/stats"
},
{
"method": "GET",
"path": "/api/v1/stats/sse"
},
{
"method": "POST",
"path": "/api/v1/mqtt/publish/batch"
},
{
"method": "POST",
"path": "/api/v1/mqtt/subscribe/batch"
},
{
"method": "GET",
"path": "/api/v1/client/subscriptions"
},
{
"method": "POST",
"path": "/api/v1/mqtt/publish"
},
{
"method": "POST",
"path": "/api/v1/mqtt/unsubscribe/batch"
},
{
"method": "POST",
"path": "/api/v1/mqtt/subscribe"
},
{
"method": "POST",
"path": "/api/v1/clients/delete"
}
],
"code": 1
}
```
## 消息发布
### POST /api/v1/mqtt/publish
发布 MQTT 消息。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| -------- | ------- | -------- | ------- |------------------------------------------------|
| topic | String | Required | | 主题,消息会按 topic 订阅投递 |
| clientId | String | Required | | 客户端标识符,不为空参数即可,无实际意义,建议可以取名 httpApi |
| payload | String | Required | | 消息正文 |
| encoding | String | Optional | plain | 消息正文使用的编码方式,目前仅支持 目前仅支持 `plain``hex``base64` |
| qos | Integer | Optional | 0 | QoS 等级 |
| retain | Boolean | Optional | false | 是否为保留消息 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/publish" -d '{"topic":"a/b/c","payload":"Hello World","qos":1,"retain":false,"clientId":"example"}'
{"code":1}
```
## 主题订阅
### POST /api/v1/mqtt/subscribe
订阅 MQTT 主题。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| -------- | ------- | -------- | ------- | ----------------------------------------------------- |
| topic | String | Required | | 主题 |
| clientId | String | Required | | 客户端标识符 |
| qos | Integer | Optional | 0 | QoS 等级 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
同时订阅 `a`, `b`, `c` 三个主题
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/subscribe" -d '{"topic":"a/b/c","qos":1,"clientId":"example"}'
{"code":1}
```
### POST /api/v1/mqtt/unsubscribe
取消订阅。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| -------- | ------ | -------- | ------- | ------------ |
| topic | String | Required | | 主题 |
| clientId | String | Required | | 客户端标识符 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
取消订阅 `a` 主题
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/unsubscribe" -d '{"topic":"a","clientId":"example"}'
{"code":1}
```
## 消息批量发布
### POST /api/v1/mqtt/publish/batch
批量发布 MQTT 消息。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| ------------ | ------- | -------- | ------- |------------------------------------------|
| [0].topic | String | Required | | 主题,消息按订阅投递 |
| [0].clientId | String | Required | | 客户端标识符,不为空参数即可,无实际意义,建议可以取名 httpApi |
| [0].payload | String | Required | | 消息正文 |
| [0].encoding | String | Optional | plain | 消息正文使用的编码方式,目前仅支持 `plain``hex``base64` |
| [0].qos | Integer | Optional | 0 | QoS 等级 |
| [0].retain | Boolean | Optional | false | 是否为保留消息 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/publish/batch" -d '[{"topic":"a/b/c","payload":"Hello World","qos":1,"retain":false,"clientId":"example"},{"topic":"a/b/c","payload":"Hello World Again","qos":0,"retain":false,"clientId":"example"}]'
{"code":1}
```
## 主题批量订阅
### POST /api/v1/mqtt/subscribe/batch
批量订阅 MQTT 主题。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| ------------ | ------- | -------- | ------- | ----------------------------------------------------- |
| [0].topic | String | Required | | 主题 |
| [0].clientId | String | Required | | 客户端标识符 |
| [0].qos | Integer | Optional | 0 | QoS 等级 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
一次性订阅 `a`, `b`, `c` 三个主题
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/subscribe/batch" -d '[{"topic":"a","qos":1,"clientId":"example"},{"topic":"b","qos":1,"clientId":"example"},{"topic":"c","qos":1,"clientId":"example"}]'
{"code":1}
```
### POST /api/v1/mqtt/unsubscribe/batch
批量取消订阅。
**Parameters (json):**
| Name | Type | Required | Default | Description |
| ------------ | ------ | -------- | ------- | ------------ |
| [0].topic | String | Required | | 主题 |
| [0].clientId | String | Required | | 客户端标识符 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
一次性取消订阅 `a`, `b` 主题
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/mqtt/unsubscribe/batch" -d '[{"topic":"a","clientId":"example"},{"topic":"b","clientId":"example"}]'
{"code":1}
```
## 获取客户端详情
### GET /api/v1/clients/info
**Query Parameters:**
| Name | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| clientId | String | True | ClientID |
**Success Response Body (JSON):**
| Name | Type | Description |
|-----------|---------|-------------|
| code | Integer | 0 |
| clientId | String | clientId |
| username | String | 用户名 |
| connected | Boolen | 是否已经连接 |
| createdAt | Long | 连接的时间 |
| connectedAt | Long | 连接成功时间 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/clients/info?clientId=mqttx_5fe4cfcf"
{"code":1,"data":{"clientId":"mqttx_5fe4cfcf","connected":true,"connectedAt":1681792417835,"createdAt":1681792417835,"ipAddress":"127.0.0.1","port":11852,"protoName":"MQTT","protoVer":5}}
```
## 分页获取客户端
### GET /api/v1/clients
**Query Parameters:**
| Name | Type | Required | Description |
|--------|------|----------|--------------|
| _page | int | False | Page 默认1 |
| _limit | int | False | 分页大小 默认10000 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- |-------------|
| code | Integer | 0 |
| pageNumber | Integer | 当前页码 |
| pageSize | Integer | 分页大小 |
| totalRow | Integer | 分页数 |
| clientId | String | clientId |
| username | String | 用户名 |
| connected | Boolen | 是否已经连接 |
| createdAt | Long | 连接的时间 |
| connectedAt | Long | 连接成功时间 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/clients?_page=1&_limit=100"
{"data":{"list":[{"clientId":"mqttx_5fe4cfcf","connected":true,"protoName":"MQTT","protoVer":5,"ipAddress":"127.0.0.1","port":11852,"connectedAt":1681792417835,"createdAt":1681792417835}],"pageNumber":1,"pageSize":1,"totalRow":1},"code":1}
```
## 踢出指定客户端
### POST /api/v1/clients/delete
踢出指定客户端。注意踢出客户端操作会将连接与会话一并终结。
**Query Parameters:**
| Name | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| clientId | String | True | ClientID |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- | ------- | ----------- |
| code | Integer | 0 |
**Examples:**
由于客户端可能会重连,所以还会连上了。如果需要永久踢出需要自行开发黑名单。
```bash
$ curl -i --basic -u mica:mica -X POST "http://localhost:18083/api/v1/clients/delete?clientId=123"
{"code":1}
```
## 获取客户端订阅情况
### GET /api/v1/client/subscriptions
获取指定客户端订阅详情。
**Query Parameters:**
| Name | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| clientId | String | True | ClientID |
**Success Response Body (JSON):**
| Name | Type | Description |
| ---- |---------|-------------|
| code | Integer | 0 |
| data | Array | [] |
| topicFilter | String | |
| clientId | String | |
| mqttQoS | Integer | 0 |
**Examples:**
```bash
$ curl -i --basic -u mica:mica "http://127.0.0.1:8083/api/v1/client/subscriptions?clientId=123"
{
"code": 1,
"data": [
{
"clientId": "123",
"mqttQoS": 0,
"topicFilter": "#"
},
{
"clientId": "123",
"mqttQoS": 0,
"topicFilter": "testtopic/#"
}
]
}
```

View File

@@ -0,0 +1,7 @@
{
"local": {
"host": "127.0.0.1:18083",
"username": "mica",
"password": "mica"
}
}

View File

@@ -0,0 +1,132 @@
### mqtt endpoints
GET http://{{host}}/api/v1/endpoints
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
### mqtt clients
GET http://{{host}}/api/v1/clients
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
### mqtt client info
GET http://{{host}}/api/v1/clients/info?clientId=123
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
### mqtt stats
GET http://{{host}}/api/v1/stats
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
### mqtt stats sse
GET http://{{host}}/api/v1/stats/sse
Authorization: Basic {{username}} {{password}}
### mqtt publish
POST http://{{host}}/api/v1/mqtt/publish
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
{
"topic":"a/b/c",
"payload":"Hello World",
"qos":1,
"retain":false,
"clientId":"example"
}
### mqtt subscribe
POST http://{{host}}/api/v1/mqtt/subscribe
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
{
"topic":"a/b/c",
"qos":1,
"clientId":"example"
}
### mqtt unsubscribe
POST http://{{host}}/api/v1/mqtt/unsubscribe
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
{
"topic":"a/b/c",
"clientId":"example"
}
### mqtt publish batch
POST http://{{host}}/api/v1/mqtt/publish/batch
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
[
{
"topic":"a/b/c",
"payload":"Hello World",
"qos":1,
"retain":false,
"clientId":"example"
},
{
"topic":"a/b/c",
"payload":"Hello World Again",
"qos":0,
"retain":false,
"clientId":"example"
}
]
### mqtt subscribe batch
POST http://{{host}}/api/v1/mqtt/subscribe/batch
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
[
{
"topic":"a",
"qos":1,
"clientId":"example"
},
{
"topic":"b",
"qos":1,
"clientId":"example"
},
{
"topic":"c",
"qos":1,
"clientId":"example"
}
]
### mqtt unsubscribe batch
POST http://{{host}}/api/v1/mqtt/unsubscribe/batch
Content-Type: application/json
Authorization: Basic {{username}} {{password}}
[
{
"topic":"a",
"clientId":"example"
},
{
"topic":"b",
"clientId":"example"
}
]
### mqtt delete clients
POST http://{{host}}/api/v1/clients/delete
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {{username}} {{password}}
clientId=123
### mqtt client subscriptions
GET http://{{host}}/api/v1/client/subscriptions
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {{username}} {{password}}
clientId=123

BIN
docs/img/dreamlu-weixin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/img/mica-mqtt.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

91
docs/update.md Normal file
View File

@@ -0,0 +1,91 @@
# 升级指南
**mica-mqtt** 尽量减少对 api 的改动已保证老版本的平滑升级,但是有些大版本不得不改动。希望此文档对大家有所帮助。
## 迁移到 mica-mqtt 2.4.2
注意2.4.2 将 MqttServerCustomizer 和 MqttClientCustomizer 抽到 mica-mqtt-server、mica-mqtt-client。Spring Boot 和 Solon 插入如果有使用到请先将老的包导入删除idea 会自动引入新的包。
**客户端替换包导入:**
- 替换成 `import org.dromara.mica.mqtt.core.client.MqttClientCustomizer;`
**服务端替换包导入:**
- 替换成 `import org.dromara.mica.mqtt.core.server.MqttServerCustomizer;`
## 迁移到 mica-mqtt 2.4.x 以上版本
- :truck: 调整 maven groupId `net.dreamlu` 到新的 `org.dromara.mica-mqtt`
- :truck: 调整包名 `net.dreamlu.iot.mqtt` 到新的 `org.dromara.mica.mqtt`,其他均保持不变。
- :truck: 切换到 central sonatypecentral sonatype 不支持快照版mica-mqtt 不再发布快照版。
## 迁移到 mica-mqtt 2.1.x
- `mica-mqtt-core` 拆分成了 `mica-mqtt-client``mica-mqtt-server`,避免一些依赖引用问题。
- `ByteBufferUtil``org.dromara.mica.mqtt.codec.ByteBufferUtil` 移动到了 `org.tio.utils.buffer.ByteBufferUtil`
- `HexUtil``org.dromara.mica.mqtt.core.util.HexUtil` 移动到了 `org.tio.utils.mica.HexUtils`
### 1. 客户端
#### 1.1 订阅回调接口调整
注意:`mica-mqtt-client-spring-boot-starter` 使用注解订阅可以直升。
`IMqttClientMessageListener#onMessage(ChannelContext context, String topic, MqttPublishMessage message, ByteBuffer payload)` 方法统一添加 `context``message` 参数。
订阅系列方法需要调整:
```java
// 消息订阅,同类方法 subxxx
client.subQos0("/test/#", (context, topic, message, payload) -> {
logger.info(topic + '\t' + ByteBufferUtil.toString(payload));
});
```
#### 1.2 SSL 双向认证支持
```yaml
mica:
client:
ssl:
enabled: false # 是否开启 ssl 认证2.1.0 开始支持双向认证
keystore-path: # 可选参数ssl 双向认证 keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 可选参数ssl 双向认证 keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
```
注意: ssl 存在三种情况
| 服务端开启ssl | 客户端 |
| ---------------------------------------- | --------------------------------------------- |
| ClientAuth 为 NONE不需要客户端验证 | 仅仅需要开启 ssl 即可不用配置证书 |
| ClientAuth 为 OPTIONAL与客户端协商 | 需开启 ssl 并且配置 truststore 证书 |
| ClientAuth 为 REQUIRE (必须的客户端验证) | 需开启 ssl 并且配置 truststore、 keystore证书 |
### 2. 服务端
#### 2.1 IMqttMessageListener 调整
`IMqttMessageListener` onMessage 参数也做了调整,添加了 topic、qoSmessage 改为了原始 MqttPublishMessage方便自行获取 mqtt5.x 的属性。
```java
/**
* 监听到消息
*
* @param context ChannelContext
* @param clientId clientId
* @param topic topic
* @param qoS MqttQoS
* @param message Message
*/
void onMessage(ChannelContext context, String clientId, String topic, MqttQoS qoS, MqttPublishMessage message);
```
#### 2.2 ssl 双向认证支持
```yaml
mica:
server:
ssl: # mqtt tcp ssl 认证
enabled: false # 是否开启 ssl 认证2.1.0 开始支持双向认证
keystore-path: # 必须参数ssl keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 必选参数ssl keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
client-auth: none # 是否需要客户端认证双向认证默认NONE不需要
```

113
example/README.md Normal file
View File

@@ -0,0 +1,113 @@
## 快速开始
**example** 中有 `mqtt` 服务端和客户端演示代码。
也可以只启动**Server**端,**Client**端用客户端工具测试。
### 1. 启动 Server 端
运行 `example/mica-mqtt-server-spring-boot-example/src/main/java/net/dreamlu/iot/mqtt/server/MqttServerApplication.java``main` 方法
控制台打印如下内容:
```text
2021-07-05 20:42:36,869 INFO server.TioServer -
|----------------------------------------------------------------------------------------|
| t-io site | https://www.tiocloud.com |
| t-io on gitee | https://gitee.com/tywo45/t-io |
| t-io on github | https://github.com/tywo45/t-io |
| t-io version | 3.7.3.v20210706-RELEASE |
| ---------------------------------------------------------------------------------------|
| TioConfig name | Mica-Mqtt-Server |
| Started at | 2021-07-05 20:42:36 |
| Listen on | 127.0.0.1:1883 |
| Main Class | org.dromara.mica.mqtt.server.MqttServerTest |
| Jvm start time | 2715ms |
| Tio start time | 16ms |
| Pid | 3588 |
|----------------------------------------------------------------------------------------|
2021-07-05 20:42:37,884 WARN server.MqttServer - Mqtt publish to all ChannelContext is empty.
```
`Mqtt publish to all ChannelContext is empty.` 通道上下文为空,即没有客户端。
```text
Mica-Mqtt-Server
├ 当前时间:1625489086843
├ 连接统计
│ ├ 共接受过连接数 :0
│ ├ 当前连接数 :0
│ ├ 异IP连接数 :0
│ └ 关闭过的连接数 :0
├ 消息统计
│ ├ 已处理消息 :0
│ ├ 已接收消息(packet/byte):0/0
│ ├ 已发送消息(packet/byte):0/0b
│ ├ 平均每次TCP包接收的字节数 :0.0
│ └ 平均每次TCP包接收的业务包 :0.0
└ IP统计时段
└ 没有设置ip统计时间
├ 节点统计
│ ├ clientNodes :0
│ ├ 所有连接 :0
│ ├ 绑定user数 :0
│ ├ 绑定token数 :0
│ └ 等待同步消息响应 :0
├ 群组
│ └ groupmap:0
└ 拉黑IP
└ []
2021-07-05 20:44:46,925 WARN server.ServerTioConfig - Mica-Mqtt-Server, 检查心跳, 共0个连接, 取锁耗时0ms, 循环耗时71ms, 心跳超时时间:120000ms
```
### 2. 启动 Client 端
运行 `example/mica-mqtt-client-spring-boot-example/src/main/java/net/dreamlu/iot/mqtt/client/MqttClientApplication.java``main` 方法
控制台打印如下内容,表示客户端连接成功:
```text
2021-07-05 20:46:10,972 ERROR client.TioClient - closeds:0, connections:0
2021-07-05 20:46:10,972 INFO client.TioClient - [1]: curr:0, closed:0, received:(0p)(0b), handled:0, sent:(0p)(0b)
2021-07-05 20:46:12,566 INFO client.ConnectionCompletionHandler - connected to 127.0.0.1:1883
2021-07-05 20:46:12,586 INFO client.MqttClient - MqttClient reconnect send connect result:true
2021-07-05 20:46:12,630 INFO client.DefaultMqttClientProcessor - MqttClient connection succeeded!
2021-07-05 20:46:13,932 INFO client.MqttClientTest - /test/123 mica最牛皮
```
此时的 Server 端会打印出如下内容:
```text
2021-07-05 20:46:45,654 INFO server.MqttServerTest - subscribe: /test/client mica最牛皮
2021-07-05 20:46:46,926 WARN server.ServerTioConfig -
Mica-Mqtt-Server
├ 当前时间:1625489206923
├ 连接统计
│ ├ 共接受过连接数 :1
│ ├ 当前连接数 :1
│ ├ 异IP连接数 :1
│ └ 关闭过的连接数 :0
├ 消息统计
│ ├ 已处理消息 :20
│ ├ 已接收消息(packet/byte):20/584
│ ├ 已发送消息(packet/byte):37/935b
│ ├ 平均每次TCP包接收的字节数 :29.2
│ └ 平均每次TCP包接收的业务包 :1.0
└ IP统计时段
└ 没有设置ip统计时间
├ 节点统计
│ ├ clientNodes :1
│ ├ 所有连接 :1
│ ├ 绑定user数 :0
│ ├ 绑定token数 :0
│ └ 等待同步消息响应 :0
├ 群组
│ └ groupmap:0
└ 拉黑IP
└ []
2021-07-05 20:46:46,926 WARN server.ServerTioConfig - Mica-Mqtt-Server, 检查心跳, 共1个连接, 取锁耗时0ms, 循环耗时0ms, 心跳超时时间:120000ms
```
### 3. Client 接入 Aliyun MQTT 服务(示例)
详见 `example/mica-mqtt-example/src/main/java/net/dreamlu/iot/mqtt/aliyun/MqttClientTest.java`

View File

@@ -0,0 +1,33 @@
<?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>org.dromara.mica-mqtt</groupId>
<artifactId>example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>mica-mqtt-client-solon-plugin-example</artifactId>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-logging-simple</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,19 @@
package org.dromara.mica.mqtt.client.solon;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Configuration;
/**
* @author wsq
*/
@Configuration
public class MqttClientApplication {
/**
* 先启动 mica-mqtt-server-spring-boot-example 再启动本项目,进行测试
*/
public static void main(String[] args) {
Solon.start(MqttClientApplication.class, args);
}
}

View File

@@ -0,0 +1,26 @@
package org.dromara.mica.mqtt.client.solon.controller;
import org.dromara.mica.mqtt.client.solon.service.ClientService;
import org.noear.solon.annotation.*;
@Mapping("/mqtt/client")
@Controller
public class ClientController {
@Inject
private ClientService service;
@Post
@Mapping("/publish")
public boolean publish(String body) {
return service.publish(body);
}
@Get
@Mapping("/sub")
public boolean sub() {
return service.sub();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.client.solon.event.MqttConnectedEvent;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.EventListener;
/**
* 客户端连接状态监听
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttClientConnectedListener implements EventListener<MqttConnectedEvent> {
@Inject
private MqttClientCreator mqttClientCreator;
@Override
public void onEvent(MqttConnectedEvent mqttConnectedEvent) throws Throwable {
log.info("MqttConnectedEvent:{}", mqttConnectedEvent);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.client.solon.event.MqttDisconnectEvent;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.EventListener;
/**
* 客户端连接状态监听
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttClientDisconnectListener implements EventListener<MqttDisconnectEvent> {
@Inject
private MqttClientCreator mqttClientCreator;
@Override
public void onEvent(MqttDisconnectEvent mqttDisconnectEvent) throws Throwable {
log.info("MqttDisconnectEvent:{}", mqttDisconnectEvent);
// 在断线时更新 clientId、username、password只能改这 3 个,不可调用其他方法。
// mqttClientCreator.clientId("newClient" + System.currentTimeMillis())
// .username("newUserName")
// .password("newPassword");
}
}

View File

@@ -0,0 +1,25 @@
package org.dromara.mica.mqtt.client.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.client.IMqttClientMessageListener;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 客户端消息监听的另一种方式
*
* @author L.cm
*/
@Slf4j
@MqttClientSubscribe("${topic1}")
public class MqttClientMessageListener implements IMqttClientMessageListener {
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
log.info("MqttClientMessageListener,topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,68 @@
package org.dromara.mica.mqtt.client.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.dromara.mica.mqtt.client.solon.pojo.User;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.deserialize.MqttJsonDeserializer;
import org.noear.solon.annotation.Component;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 客户端消息监听
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttClientSubscribeListener {
@MqttClientSubscribe("/test/#")
public void subQos0(String topic, byte[] payload) {
log.info("MqttClientSubscribeListener.subQos0,topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(value = "/qos1/#", qos = MqttQoS.QOS1)
public void subQos1(String topic, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(
value = "/test/json",
deserialize = MqttJsonDeserializer.class // 2.4.5 开始支持 自定义序列化,默认 json 序列化
)
public void testJson(String topic, MqttPublishMessage message, Map<String, Object> data) {
// solon 插件为 2.4.6 开始支持,支持 2 到 3 个参数,字段类型映射规则(顺序)如下
// String 字符串会默认映射到 topic
// Map<String, String> topicVars 会默认映射到 topic 中的变量解析v2.5.4支持),注意:别跟消息序列化的冲突,消息反序列化不要用 Map<String, String>
// MqttPublishMessage 会默认映射到 原始的消息,可以拿到 mqtt5 的 props 参数
// byte[] 会映射到 mqtt 消息内容 payload
// ByteBuffer 会映射到 mqtt 消息内容 payload
// 其他类型会走序列化,确保消息能够序列化,默认为 json 序列化
log.info("topic:{} json data:{}", topic, data);
}
@MqttClientSubscribe(value = "/test/object")
public void testJson(String topic, MqttPublishMessage message, User<User> data) {
log.info("topic:{} json data:{}", topic, data);
}
/**
* 订阅,参数为可选,但是参数数量必须大于 2
*
* @param topic topic 参数,可选参数
* @param topicVars 订阅 topic 模板 ${productKey} 中的变量解析v2.5.4支持),可选参数,注意:类型必须为 Map<String, String>
* @param payload 消息内容
*/
@MqttClientSubscribe("/sys/${productKey}/${deviceName}/thing/sub/register")
public void thingSubRegister(String topic, Map<String, String> topicVars, byte[] payload) {
// 1.3.8 开始支持,@MqttClientSubscribe 注解支持 ${} 变量替换,会默认替换成 +
// 注意mica-mqtt 会先从 Spring boot 配置中替换参数 ${},如果存在配置会优先被替换。
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,9 @@
package org.dromara.mica.mqtt.client.solon.pojo;
import lombok.Data;
@Data
public class User<T> {
private String name;
private T girlfriend;
}

View File

@@ -0,0 +1,32 @@
package org.dromara.mica.mqtt.client.solon.service;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.client.solon.MqttClientTemplate;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.nio.charset.StandardCharsets;
/**
* @author wsq
*/
@Slf4j
@Component
public class ClientService {
@Inject
private MqttClientTemplate client;
public boolean publish(String body) {
client.publish("/test/client", body.getBytes(StandardCharsets.UTF_8));
return true;
}
public boolean sub() {
client.subQos0("/test/#", (context, topic, message, payload) -> {
log.info("{}\t{}", topic, new String(payload, StandardCharsets.UTF_8));
});
return true;
}
}

View File

@@ -0,0 +1,36 @@
server:
port: 30303
# solon 配置
solon:
logging:
appender:
console:
level: INFO
# mqtt-client 配置
mqtt:
client:
enabled: true # 是否开启客户端默认true
ip: 127.0.0.1 # 连接的服务端 ip 默认127.0.0.1
port: 1883 # 端口默认1883
name: Mica-Mqtt-Client # 名称默认Mica-Mqtt-Client
clientId: 000001 # 客户端Id非常重要一般为设备 sn不可重复
username: mica # 认证的用户名注意2.5.x 开始将 user-name 改成了 username
password: mica # 认证的密码
timeout: 5 # 超时时间单位默认5秒
reconnect: true # 是否重连默认true
re-interval: 5000 # 重连时间,默认 5000 毫秒
version: mqtt_3_1_1 # mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5默认mqtt_3_1_1
read-buffer-size: 8KB # 接收数据的 buffer size默认8k
max-bytes-in-message: 10MB # 消息解析最大 bytes 长度默认10M
keep-alive-secs: 60 # keep-alive 时间,单位:秒
heartbeat-mode: LAST_REQ # 心跳模式支持最后发送或接收心跳时间来计算心跳默认最后发送心跳的时间。2.4.3 开始支持)
heartbeat-timeout-strategy: PING # 心跳超时策略,支持发送 PING 和 CLOSE 断开连接,默认:最大努力发送 PING。2.4.3 开始支持)
clean-start: true # session 保留 2.5.x 使用 clean-start老版本用 clean-session默认true
ssl:
enabled: false # 是否开启 ssl 认证2.1.0 开始支持双向认证
keystore-path: # 可选参数ssl 双向认证 keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 可选参数ssl 双向认证 keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
topic1: /test/#

View File

@@ -0,0 +1,6 @@
## SpringBoot + mica-mqtt-client 应用演示
## 启动步骤
1. 先启动 mica-mqtt-server-spring-boot-example
2. 再启动 mica-mqtt-client-spring-boot-example

View File

@@ -0,0 +1,62 @@
<?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>
<artifactId>mica-mqtt-client-spring-boot-example</artifactId>
<name>${project.artifactId}</name>
<url>https://www.dreamlu.net</url>
<parent>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>example</artifactId>
<version>${revision}</version>
</parent>
<dependencies>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-lite</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-logging</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-openapi</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.spring.client.annotation.EnableMqttClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author wsq
*/
@SpringBootApplication
@EnableMqttClients
public class MqttClientApplication {
/**
* 先启动 mica-mqtt-server-spring-boot-example 再启动本项目,进行测试
*/
public static void main(String[] args) {
SpringApplication.run(MqttClientApplication.class, args);
}
}

View File

@@ -0,0 +1,31 @@
package org.dromara.mica.mqtt.client.config;
import org.dromara.mica.mqtt.core.client.DefaultMqttClientSession;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.dromara.mica.mqtt.spring.client.MqttClientTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 示例多个 mqtt client
*
* @author L.cm
*/
@Configuration
public class OtherMqttClientConfiguration {
@Bean("mqttClientTemplate1")
public MqttClientTemplate mqttClientTemplate1() {
// 基于 clientCreator 的配置构建一个新的
MqttClientCreator mqttClientCreator1 = new MqttClientCreator()
// 修改不同的配置
// .ip("mqtt.dreamlu.net")
.port(1884)
.username("mica")
.password("mica")
// 避免 client session 冲突
.clientSession(new DefaultMqttClientSession());
return new MqttClientTemplate(mqttClientCreator1);
}
}

View File

@@ -0,0 +1,32 @@
package org.dromara.mica.mqtt.client.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dromara.mica.mqtt.client.service.ClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Tag(name = "Mqtt::客户端")
@RequestMapping("/mqtt/client")
@RestController
public class ClientController {
@Autowired
private ClientService service;
@Operation(summary = "publish")
@PostMapping("/publish")
public boolean publish(@RequestBody String body) {
service.publish(body);
service.publishHelloInterfaceA(body);
service.publishHelloInterfaceB(body);
return true;
}
@Operation(summary = "sub")
@GetMapping("/sub")
public boolean sub() {
return service.sub();
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.dromara.mica.mqtt.spring.client.event.MqttConnectedEvent;
import org.dromara.mica.mqtt.spring.client.event.MqttDisconnectEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
/**
* 客户端连接状态监听
*
* @author L.cm
*/
@Slf4j
@Service
public class MqttClientConnectListener {
@Autowired
private MqttClientCreator mqttClientCreator;
@EventListener
public void onConnected(MqttConnectedEvent event) {
log.info("MqttConnectedEvent:{}", event);
}
@EventListener
public void onDisconnect(MqttDisconnectEvent event) {
log.info("MqttDisconnectEvent:{}", event);
// 在断线时更新 clientId、username、password只能改这 3 个,不可调用其他方法。
// mqttClientCreator.clientId("newClient" + System.currentTimeMillis())
// .username("newUserName")
// .password("newPassword");
}
}

View File

@@ -0,0 +1,27 @@
package org.dromara.mica.mqtt.client.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.client.IMqttClientMessageListener;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.springframework.stereotype.Service;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 客户端消息监听的另一种方式
*
* @author L.cm
*/
@Slf4j
@Service
@MqttClientSubscribe("${topic1}")
public class MqttClientMessageListener implements IMqttClientMessageListener {
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,70 @@
package org.dromara.mica.mqtt.client.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.client.pojo.User;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.deserialize.MqttJsonDeserializer;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 客户端消息监听
*
* @author L.cm
* @author ChangJin Wei(魏昌进)
*/
@Slf4j
@Service
public class MqttClientSubscribeListener {
@MqttClientSubscribe("/test/#")
public void subQos0(String topic, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(value = "/qos1/#", qos = MqttQoS.QOS1)
public void subQos1(String topic, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(
value = "/test/json",
deserialize = MqttJsonDeserializer.class // 2.4.5 开始支持 自定义序列化,默认 json 序列化
)
public void testJson(String topic, MqttPublishMessage message, Map<String, Object> data) {
// 2.4.5 开始支持,支持 2 到 3 个参数,字段类型映射规则(顺序)如下:
// String 字符串会默认映射到 topic
// Map<String, String> topicVars 会默认映射到 topic 中的变量解析v2.5.4支持),注意:别跟消息序列化的冲突,消息反序列化不要用 Map<String, String>
// MqttPublishMessage 会默认映射到 原始的消息,可以拿到 mqtt5 的 props 参数
// byte[] 会映射到 mqtt 消息内容 payload
// ByteBuffer 会映射到 mqtt 消息内容 payload
// 其他类型会走序列化,确保消息能够序列化,默认为 json 序列化
log.info("topic:{} json data:{}", topic, data);
}
@MqttClientSubscribe(value = "/test/object")
public void testJson(String topic, MqttPublishMessage message, User<User> data) {
log.info("topic:{} json data:{}", topic, data);
}
/**
* 订阅,参数为可选,但是参数数量必须大于 2
*
* @param topic topic 参数,可选参数
* @param topicVars 订阅 topic 模板 ${productKey} 中的变量解析v2.5.4支持),可选参数,注意:类型必须为 Map<String, String>
* @param payload 消息内容,以字节数组形式提供,可选参数,也可支持对象形式,默认 json 序列化
*/
@MqttClientSubscribe("/sys/${productKey}/${deviceName}/thing/sub/register")
public void thingSubRegister(String topic, Map<String, String> topicVars, byte[] payload) {
// 1.3.8 开始支持,@MqttClientSubscribe 注解支持 ${} 变量替换,会默认替换成 +
// 注意mica-mqtt 会先从 Spring boot 配置中替换参数 ${},如果存在配置会优先被替换。
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,29 @@
package org.dromara.mica.mqtt.client.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.springframework.stereotype.Service;
/**
* 客户端消息监听,注解在方法上
*
* @author L.cm
*/
@Slf4j
@Service
public class OtherMqttClientSubscribeListener {
@MqttClientSubscribe(
value = {
"$share/iothub/test/${a}",
"/test/${arg1}/${arg2}/${arg3}/${arg4}"
},
clientTemplateBean = "mqttClientTemplate1"
)
public void sub(String topic, MqttPublishMessage message, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload));
}
}

View File

@@ -0,0 +1,9 @@
package org.dromara.mica.mqtt.client.pojo;
import lombok.Data;
@Data
public class User<T> {
private String name;
private T girlfriend;
}

View File

@@ -0,0 +1,52 @@
package org.dromara.mica.mqtt.client.service;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.spring.client.MqttClientTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
/**
* @author wsq
*/
@Slf4j
@Service
public class ClientService {
/**
* 使用 默认的 mqtt client
*/
@Autowired
@Qualifier(MqttClientTemplate.DEFAULT_CLIENT_TEMPLATE_BEAN)
private MqttClientTemplate client;
@Autowired
private HelloInterfaceA helloInterfaceA;
@Autowired
private HelloInterfaceB helloInterfaceB;
public boolean publish(String body) {
client.publish("/test/client", body.getBytes(StandardCharsets.UTF_8));
return true;
}
public boolean publishHelloInterfaceA(String body) {
helloInterfaceA.sayHello(body.getBytes(StandardCharsets.UTF_8));
return true;
}
public boolean publishHelloInterfaceB(String body) {
helloInterfaceB.sayHello(body.getBytes(StandardCharsets.UTF_8));
return true;
}
public boolean sub() {
client.subQos0("/test/#", (context, topic, message, payload) -> {
log.info("{}\t{}", topic, new String(payload, StandardCharsets.UTF_8));
});
return true;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client.service;
import org.dromara.mica.mqtt.core.annotation.MqttClientPublish;
import org.dromara.mica.mqtt.core.annotation.MqttPayload;
import org.dromara.mica.mqtt.spring.client.annotation.MqttClient;
/**
* @author ChangJin Wei (魏昌进)
*/
@MqttClient
public interface HelloInterfaceA {
@MqttClientPublish("/test/HelloInterfaceA")
void sayHello(@MqttPayload Object payload);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client.service;
import org.dromara.mica.mqtt.core.annotation.MqttClientPublish;
import org.dromara.mica.mqtt.core.annotation.MqttPayload;
import org.dromara.mica.mqtt.spring.client.annotation.MqttClient;
/**
* @author ChangJin Wei (魏昌进)
*/
@MqttClient
public interface HelloInterfaceB {
@MqttClientPublish("/test/HelloInterfaceB")
void sayHello(@MqttPayload Object payload);
}

View File

@@ -0,0 +1,46 @@
server:
port: 30012
spring:
application:
name: mica-mqtt-client
# mqtt-client 配置
mqtt:
client:
enabled: true # 是否开启客户端默认true
ip: 127.0.0.1 # 连接的服务端 ip 默认127.0.0.1
port: 1883 # 端口默认1883
name: Mica-Mqtt-Client # 名称默认Mica-Mqtt-Client
clientId: "000001" # 客户端Id非常重要一般为设备 sn不可重复
username: mica # 认证的用户名注意2.5.x 开始将 user-name 改成了 username
password: 123456 # 认证的密码
timeout: 5 # 超时时间单位默认5秒
reconnect: true # 是否重连默认true
re-interval: 5000 # 重连时间,默认 5000 毫秒
version: mqtt_3_1_1 # mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5默认mqtt_3_1_1
read-buffer-size: 8KB # 接收数据的 buffer size默认8k
max-bytes-in-message: 10MB # 消息解析最大 bytes 长度默认10M
keep-alive-secs: 60 # keep-alive 时间,单位:秒
heartbeat-mode: LAST_REQ # 心跳模式支持最后发送或接收心跳时间来计算心跳默认最后发送心跳的时间。2.4.3 开始支持)
heartbeat-timeout-strategy: PING # 心跳超时策略,支持发送 PING 和 CLOSE 断开连接,默认:最大努力发送 PING。2.4.3 开始支持)
clean-start: true # session 保留 2.5.x 使用 clean-start老版本用 clean-session默认true
will-message:
topic: /test/offline
message: down
ssl:
enabled: false # 是否开启 ssl 认证2.1.0 开始支持双向认证
keystore-path: # 可选参数ssl 双向认证 keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 可选参数ssl 双向认证 keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
topic1: /test2/#
springdoc:
swagger-ui:
urls:
- name: swagger
url: /v3/api-docs
logging:
level:
root: info
server: info # t-io 服务端默认日志
org.tio: info # t-io 服务端默认日志
org.dromara.mica.mqtt: info # mica-mqtt 日志

View File

@@ -0,0 +1,12 @@
${AnsiColor.BRIGHT_BLUE}## ## #### ###### ### ${AnsiColor.RED} ## ## ####### ######## ########
${AnsiColor.BRIGHT_BLUE}### ### ## ## ## ## ## ${AnsiColor.RED} ### ### ## ## ## ##
${AnsiColor.BRIGHT_BLUE}#### #### ## ## ## ## ${AnsiColor.RED} #### #### ## ## ## ##
${AnsiColor.BRIGHT_BLUE}## ### ## ## ## ## ##${AnsiColor.RED} ## ### ## ## ## ## ##
${AnsiColor.BRIGHT_BLUE}## ## ## ## #########${AnsiColor.RED} ## ## ## ## ## ## ##
${AnsiColor.BRIGHT_BLUE}## ## ## ## ## ## ##${AnsiColor.RED} ## ## ## ## ## ##
${AnsiColor.BRIGHT_BLUE}## ## #### ###### ## ##${AnsiColor.RED} ## ## ##### ## ## ##
https://www.dreamlu.net
${AnsiColor.BRIGHT_BLUE}:: ${spring.application.name} :: Running Spring Boot ${spring-boot.version} 🏃🏃🏃 ${AnsiColor.DEFAULT}

View File

@@ -0,0 +1,183 @@
<?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>
<artifactId>mica-mqtt-example</artifactId>
<name>${project.artifactId}</name>
<parent>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>example</artifactId>
<version>${revision}</version>
</parent>
<properties>
<graalvm.version>25.0.1</graalvm.version>
<mainClass.server>org.dromara.mica.mqtt.server.MqttServerTest</mainClass.server>
<mainClass.client>org.dromara.mica.mqtt.client.MqttClientTest</mainClass.client>
</properties>
<dependencies>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
</build>
<profiles>
<profile>
<id>jar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<!-- 非 GraalVM 环境用 tinylog -->
<dependency>
<groupId>org.tinylog</groupId>
<artifactId>slf4j-tinylog</artifactId>
</dependency>
<dependency>
<groupId>org.tinylog</groupId>
<artifactId>tinylog-impl</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<archive>
<manifest>
<mainClass>${mainClass.server}</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mqtt-server-graal</id>
<dependencies>
<!-- GraalVM 环境使用 jdk log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
<!-- GraalVM -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>mqtt-server-graal</finalName>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>21.2.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>false</skip>
<imageName>mqtt-server-graalvm</imageName>
<mainClass>${mainClass.server}</mainClass>
<buildArgs>
-H:+RemoveSaturatedTypeFlows
--allow-incomplete-classpath
--no-fallback
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mqtt-client-graal</id>
<dependencies>
<!-- GraalVM 环境使用 jdk log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
<!-- GraalVM -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>mqtt-client-graal</finalName>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>21.2.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>false</skip>
<imageName>mqtt-client-graalvm</imageName>
<mainClass>${mainClass.client}</mainClass>
<buildArgs>
-H:+RemoveSaturatedTypeFlows
--allow-incomplete-classpath
--no-fallback
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.aliyun;
import org.dromara.mica.mqtt.core.client.MqttClient;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
/**
* 客户端测试
*
* @author L.cm
*/
public class MqttClientTest {
public static void main(String[] args) {
String productKey = "g27jB42P9hm";
String deviceName = "3dbc1cb4";
String deviceSecret = "";
// 计算MQTT连接参数。
MqttSign sign = new MqttSign(productKey, deviceName, deviceSecret);
String username = sign.getUsername();
String password = sign.getPassword();
String clientId = sign.getClientId();
System.out.println("username: " + username);
System.out.println("password: " + password);
System.out.println("clientid: " + clientId);
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip(productKey + ".iot-as-mqtt.cn-shanghai.aliyuncs.com")
.port(443)
.username(username)
.password(password)
.clientId(clientId)
.connectSync();
client.subQos0("/sys/" + productKey + '/' + deviceName + "/thing/event/property/post_reply", (context, topic, message, payload) -> {
System.out.println(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
});
client.schedule(() -> {
int LightSwitch = ThreadLocalRandom.current().nextBoolean() ? 0 : 1;
String content = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":" + LightSwitch + "}}";
client.publish("/sys/" + productKey + "/" + deviceName + "/thing/event/property/post", content.getBytes(StandardCharsets.UTF_8));
}, 3000);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.aliyun;
import org.tio.utils.mica.DigestUtils;
import java.util.Objects;
/**
* 阿里云 mqtt 签名方式
*
* @author L.cm
*/
public class MqttSign {
/**
* 用户名
*/
private final String username;
/**
* 密码
*/
private final String password;
/**
* 客户端id
*/
private final String clientId;
public MqttSign(String productKey, String deviceName, String deviceSecret) {
Objects.requireNonNull(productKey, "productKey is null");
Objects.requireNonNull(deviceName, "deviceName is null");
Objects.requireNonNull(deviceSecret, "deviceSecret is null");
this.username = deviceName + '&' + productKey;
String timestamp = Long.toString(System.currentTimeMillis());
this.password = getPassword(productKey, deviceName, deviceSecret, timestamp);
this.clientId = getClientId(productKey, deviceName, timestamp);
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getClientId() {
return clientId;
}
private static String getPassword(String productKey, String deviceName, String deviceSecret, String timestamp) {
String plainPwd = "clientId" + productKey + '.' + deviceName + "deviceName" +
deviceName + "productKey" + productKey + "timestamp" + timestamp;
return hmacSha256(plainPwd, deviceSecret);
}
private static String getClientId(String productKey, String deviceName, String timestamp) {
return productKey + '.' + deviceName + "|timestamp=" + timestamp + ",_v=paho-java-1.0.0,securemode=2,signmethod=hmacsha256|";
}
private static String hmacSha256(String plainText, String key) {
if (plainText == null || key == null) {
return null;
}
return DigestUtils.hmacSha256Hex(plainText, key);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.benchmark;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.tio.utils.hutool.StrUtil;
import org.tio.utils.thread.ThreadUtils;
import org.tio.utils.thread.pool.SynThreadPoolExecutor;
import org.tio.utils.timer.DefaultTimerTaskService;
import org.tio.utils.timer.TimerTaskService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
/**
* mqtt 压力测试
*
* @author L.cm
*/
public class MqttBenchmark {
public static void main(String[] args) {
// 注意: windows 上需要修改最大的 Tcp 连接数,不然超不过 2W。
// 《修改Windows服务器最大的Tcp连接数》https://www.jianshu.com/p/00136a97d2d8
int connCount = 5_0000;
String ip = "127.0.0.1";
// 优化使用ArrayList+预分配容量性能比CopyOnWriteArrayList好很多
// 对于压测场景,不需要线程安全的写操作(因为是单线程创建客户端)
final List<MqttClient> clientList = new ArrayList<>(connCount);
SynThreadPoolExecutor tioExecutor = ThreadUtils.getTioExecutor();
ExecutorService groupExecutor = ThreadUtils.getGroupExecutor();
// 自定义全局 taskService避免每个 client new创建过多线程
TimerTaskService taskService = new DefaultTimerTaskService(200L, 60);
for (int i = 0; i < connCount; i++) {
newClient(ip, i, clientList, tioExecutor, groupExecutor, taskService);
}
}
private static void newClient(String ip, int i, final List<MqttClient> clientList,
SynThreadPoolExecutor tioExecutor,
ExecutorService groupExecutor,
TimerTaskService taskService) {
MqttClient client = MqttClient.create()
.ip(ip)
.clientId(StrUtil.getNanoId() + i)
.readBufferSize(128)
// 取消自动重连
.reconnect(false)
.tioExecutor(tioExecutor)
.groupExecutor(groupExecutor)
.mqttExecutor(tioExecutor)
.taskService(taskService)
.connect();
clientList.add(client);
}
}

View File

@@ -0,0 +1,59 @@
package org.dromara.mica.mqtt.benchmark;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.tio.utils.hutool.StrUtil;
import org.tio.utils.thread.ThreadUtils;
import org.tio.utils.thread.pool.SynThreadPoolExecutor;
import org.tio.utils.timer.DefaultTimerTaskService;
import org.tio.utils.timer.TimerTaskService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* mqtt 发布端测试
*
* @author L.cm
*/
public class MqttPublishBench {
public static void main(String[] args) {
int clientCount = 10;
int publishCount = 10000;
MqttQoS qos = MqttQoS.QOS0;
List<MqttClient> clients = getClient(clientCount);
Executors.newScheduledThreadPool(ThreadUtils.AVAILABLE_PROCESSORS).scheduleWithFixedDelay(() -> {
for (MqttClient mqttClient : clients) {
for (int j = 0; j < publishCount; j++) {
byte[] payload = new byte[1024 + j];
Arrays.fill(payload, (byte) -1);
mqttClient.publish("/topic/" + j, payload, qos);
}
}
}, 1L, 1L, TimeUnit.SECONDS);
}
public static List<MqttClient> getClient(int clientCount) {
SynThreadPoolExecutor tioExecutor = ThreadUtils.getTioExecutor();
ExecutorService groupExecutor = ThreadUtils.getGroupExecutor();
TimerTaskService taskService = new DefaultTimerTaskService();
List<MqttClient> clients = new ArrayList<>();
for (int i = 0; i < clientCount; i++) {
MqttClient client = MqttClient.create()
.clientId(StrUtil.getNanoId())
.tioExecutor(tioExecutor)
.groupExecutor(groupExecutor)
.mqttExecutor(groupExecutor)
.taskService(taskService)
.connectSync();
clients.add(client);
}
return clients;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.benchmark;
import org.dromara.mica.mqtt.core.server.MqttServer;
/**
* mqtt 服务端测试
*
* @author L.cm
*/
public class MqttServerBench {
public static void main(String[] args) {
// 设定日志级别为 error
System.setProperty("tinylog.writer.level", "error");
// 启动 mqtt 服务
MqttServer.create()
.enableMqtt()
.statEnable(false)
.start();
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.broker;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
/**
* 设备 A这里默认 APP 应用端
*
* @author L.cm
*/
public class DeviceA {
private static final Logger logger = LoggerFactory.getLogger(DeviceA.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("admin")
.password("123456")
.connectSync();
client.subQos0("/a/door/open", (context, topic, message, payload) -> {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
});
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.broker;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
/**
* 设备 B这里默认 web 端
*
* @author L.cm
*/
public class DeviceB {
private static final Logger logger = LoggerFactory.getLogger(DeviceB.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("admin")
.password("123456")
.connectSync();
client.subQos0("/a/door/open", (context, topic, message, payload) -> {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
});
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.broker;
import org.dromara.mica.mqtt.core.client.MqttClient;
/**
* 设备 C每 5 秒上报一个数据
*
* @author L.cm
*/
public class DeviceC {
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("admin")
.password("123456")
.connectSync();
client.schedule(() -> {
client.publish("/a/door/open", null);
}, 5000);
}
}

View File

@@ -0,0 +1,36 @@
package org.dromara.mica.mqtt.broker;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.annotation.MqttRetain;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.dromara.mica.mqtt.core.annotation.MqttClientPublish;
import org.dromara.mica.mqtt.core.annotation.MqttPayload;
/**
* @author ChangJin Wei (魏昌进)
*/
public class DeviceD {
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("admin")
.password("123456")
.connectSync();
DoorClient doorClient = client.getInterface(DoorClient.class);
client.schedule(() -> {
doorClient.sendMessage("open", false);
}, 1000);
}
public interface DoorClient {
@MqttClientPublish(value = "/a/door/open", qos = MqttQoS.QOS0)
void sendMessage(@MqttPayload String message, @MqttRetain boolean retain);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.broker;
import org.dromara.mica.mqtt.core.server.MqttServer;
/**
* 服务端,单纯的做消息转发
*
* @author L.cm
*/
public class Server {
/**
* 客户端 A 模拟 APP 端订阅 `/a/door/open`
* 客户端 B 模拟 web 网页端 mqtt.js 订阅 `/a/door/open`
* Mqtt 服务端实现 `IMqttMessageListener`,将消息转交给 `AbstractMqttMessageDispatcher`(自定义实现)处理。
* 客户端 C 定时上报转态给 `/a/door/open`
* 结果A 和 B 将收到 C 或 D 发布的消息,并完成相应的效果展示。
*/
public static void main(String[] args) {
// 启动服务mica-mqtt 1.3.x 已经默认为 broker 模式
MqttServer.create()
.enableMqtt()
.debug()
.start();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.codec.MqttVersion;
import org.dromara.mica.mqtt.core.client.IMqttClientMessageListener;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 客户端测试
*
* @author L.cm
*/
public class Mqtt5ClientTest {
private static final Logger logger = LoggerFactory.getLogger(Mqtt5ClientTest.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("mica")
.password("mica")
.version(MqttVersion.MQTT_5)
.cleanStart(false)
.sessionExpiryIntervalSecs(7200)
.connectListener(new MqttClientConnectListener())
.willMessage(builder -> {
builder.topic("/test/offline")
.messageText("down")
.retain(false)
.qos(MqttQoS.QOS0); // 遗嘱消息
})
// 同步连接,也可以使用 connect() 异步(可以避免 broker 没启动照成启动卡住),但是下面的订阅和发布可能还没连接成功。
.connectSync();
client.subQos0("/test/123", new IMqttClientMessageListener() {
@Override
public void onSubscribed(ChannelContext context, String topicFilter, MqttQoS mqttQoS) {
// 订阅成功之后触发,可在此处做一些业务逻辑
logger.info("topicFilter:{} MqttQoS:{} 订阅成功!!!", topicFilter, mqttQoS);
}
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
}
});
client.publish("/test/client", "mica最牛皮1".getBytes(StandardCharsets.UTF_8));
client.publish("/test/client", "mica最牛皮2".getBytes(StandardCharsets.UTF_8));
client.publish("/test/client", "mica最牛皮3".getBytes(StandardCharsets.UTF_8));
client.schedule(() -> {
client.publish("/test/client", "mica最牛皮".getBytes(StandardCharsets.UTF_8));
}, 1000);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.codec.codes.MqttConnectReasonCode;
import org.dromara.mica.mqtt.core.client.MqttClient;
/**
* 客户端测试
*
* @author L.cm
*/
public class MqttClientConnTest {
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttConnectReasonCode reasonCode = MqttClient.create()
// .ip("127.0.0.1")
.ip("mqtt.dreamlu.net")
.port(1883)
.username("mica")
.password("mica1")
.connectTest();
System.out.println(reasonCode);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.core.client.IMqttClientConnectListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
/**
* 客户端连接状态监听
*
* @author L.cm
*/
public class MqttClientConnectListener implements IMqttClientConnectListener {
private static final Logger logger = LoggerFactory.getLogger(MqttClientConnectListener.class);
@Override
public void onConnected(ChannelContext context, boolean isReconnect) {
String clientId = context.getId();
if (isReconnect) {
logger.info("重连 mqtt 服务器重连成功... clientId:{}", clientId);
} else {
logger.info("连接 mqtt 服务器成功... clientId:{}", clientId);
}
}
@Override
public void onDisconnect(ChannelContext context, Throwable throwable, String remark, boolean isRemove) {
String clientId = context.getId();
logger.error("mqtt 链接断开 remark:{} isRemove:{} clientId:{}", remark, isRemove, clientId, throwable);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.tio.utils.buffer.ByteBufferUtil;
/**
* 客户端全局订阅测试
*
* @author L.cm
*/
public class MqttClientGlobalTest {
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("admin")
.password("123456")
// 采用 globalSubscribe保留 session 停机重启后可以接受到离线消息注意clientId 要不能变化。
.clientId("globalTest")
.cleanStart(false)
// 全局订阅的 topic
.globalSubscribe("/test", "/test/123", "/debug/#")
// 全局监听,也会监听到服务端 http api 订阅的数据
.globalMessageListener((context, topic, message, payload) -> {
System.out.println("topic:\t" + topic);
System.out.println("payload:\t" + ByteBufferUtil.toString(payload));
})
// .debug()
.connectSync();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
/**
* 客户端测试
*
* @author L.cm
*/
public class MqttClientSyncTest {
private static final Logger logger = LoggerFactory.getLogger(MqttClientSyncTest.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("mica")
.password("mica")
.connectListener(new MqttClientConnectListener())
// 同步连接,注意:连接会阻塞
.connectSync();
client.subQos0("/test/#", (context, topic, message, payload) -> {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
});
client.unSubscribe("/test/#", "/test/123");
// 连接上之后发送消息,注意:连接时出现异常等就不会发出
client.publish("/test/client", "mica最牛皮".getBytes(StandardCharsets.UTF_8));
// 2.3.0 开始支持,可停止
// client.stop();
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.client;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.client.IMqttClientMessageListener;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 客户端测试
*
* @author L.cm
*/
public class MqttClientTest {
private static final Logger logger = LoggerFactory.getLogger(MqttClientTest.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(1883)
.username("mica")
.password("mica")
// 绑定网卡 v2.5.1 添加,支持 bindIp网卡ip和 bindNetworkInterface网卡名取一即可
// .bindIp("127.0.0.1")
// .bindNetworkInterface("lo")
// 如果包体过大,建议将此参数设置和 maxBytesInMessage 一样大
// .readBufferSize(1024 * 10)
// 最大包体长度,如果包体过大需要设置此参数
// .maxBytesInMessage(1024 * 10)
// .version(MqttVersion.MQTT_5)
// 连接监听
.connectListener(new MqttClientConnectListener())
// 遗嘱消息
.willMessage(builder -> {
builder.topic("/test/offline")
.messageText("down")
.retain(false)
.qos(MqttQoS.QOS0)
// mqtt5 遗嘱消息属性
.willProperties(props -> {
props.setWillDelayInterval(1000);
props.setContentType("text/plain");
});
})
// 同步连接,也可以使用 connect() 异步(可以避免 broker 没启动照成启动卡住),但是下面的订阅和发布可能还没连接成功。
.connectSync();
client.subQos0("/test/#", new IMqttClientMessageListener() {
@Override
public void onSubscribed(ChannelContext context, String topicFilter, MqttQoS mqttQoS) {
// 订阅成功之后触发,可在此处做一些业务逻辑
logger.info("topicFilter:{} MqttQoS:{} 订阅成功!!!", topicFilter, mqttQoS);
}
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
}
});
client.subQos0("/test/1", new IMqttClientMessageListener() {
@Override
public void onSubscribed(ChannelContext context, String topicFilter, MqttQoS mqttQoS) {
// 订阅成功之后触发,可在此处做一些业务逻辑
logger.info("topicFilter:{} MqttQoS:{} 订阅成功!!!", topicFilter, mqttQoS);
}
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
}
});
client.subQos0("/test/2", new IMqttClientMessageListener() {
@Override
public void onSubscribed(ChannelContext context, String topicFilter, MqttQoS mqttQoS) {
// 订阅成功之后触发,可在此处做一些业务逻辑
logger.info("topicFilter:{} MqttQoS:{} 订阅成功!!!", topicFilter, mqttQoS);
}
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
}
});
client.subQos0("/test/3", new IMqttClientMessageListener() {
@Override
public void onSubscribed(ChannelContext context, String topicFilter, MqttQoS mqttQoS) {
// 订阅成功之后触发,可在此处做一些业务逻辑
logger.info("topicFilter:{} MqttQoS:{} 订阅成功!!!", topicFilter, mqttQoS);
}
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
logger.info(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
}
});
client.publish("/test/client", "mica最牛皮1".getBytes(StandardCharsets.UTF_8));
client.publish("/test/client", "mica最牛皮2".getBytes(StandardCharsets.UTF_8));
client.publish("/test/client", "mica最牛皮3".getBytes(StandardCharsets.UTF_8));
client.schedule(() -> {
client.publish("/test/client", "mica最牛皮".getBytes(StandardCharsets.UTF_8));
}, 2000);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.huawei;
import org.dromara.mica.mqtt.core.client.MqttClient;
import java.nio.charset.StandardCharsets;
/**
* 客户端测试
*
* @author L.cm
*/
public class MqttClientTest {
public static void main(String[] args) {
// 设备id和密钥请从华为云iot获取
String deviceId = "630eb6f8664c6f7938db6ef0_test";
String deviceSecret = "";
// 计算MQTT连接参数。
MqttSign sign = new MqttSign(deviceId, deviceSecret);
String username = sign.getUsername();
String password = sign.getPassword();
String clientId = sign.getClientId();
System.out.println("username: " + username);
System.out.println("password: " + password);
System.out.println("clientid: " + clientId);
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("iot-mqtts.cn-north-4.myhuaweicloud.com")
.port(8883)
.username(username)
.password(password)
.clientId(clientId)
.useSsl()
.connectSync();
// 订阅命令下发topic
String cmdRequestTopic = "$oc/devices/" + deviceId + "/sys/commands/#";
client.subQos0(cmdRequestTopic, (context, topic, message, payload) -> {
System.out.println(topic + '\t' + new String(payload, StandardCharsets.UTF_8));
});
// 属性上报消息
String reportTopic = "$oc/devices/" + deviceId + "/sys/properties/report";
String jsonMsg = "{\"services\":[{\"service_id\":\"Temperature\", \"properties\":{\"value\":57}},{\"service_id\":\"Battery\",\"properties\":{\"level\":88}}]}";
client.schedule(() -> {
client.publish(reportTopic, jsonMsg.getBytes(StandardCharsets.UTF_8));
}, 3000);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.huawei;
import org.tio.utils.mica.DigestUtils;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
/**
* 华为云 mqtt 签名方式
*
* @author L.cm
*/
public class MqttSign {
/**
* 用户名
*/
private final String username;
/**
* 密码
*/
private final String password;
/**
* 客户端id
*/
private final String clientId;
public MqttSign(String deviceId, String deviceSecret) {
Objects.requireNonNull(deviceId, "deviceId is null");
Objects.requireNonNull(deviceSecret, "deviceSecret is null");
this.username = deviceId;
String timestamp = getTimeStamp();
this.password = getPassword(deviceSecret, timestamp);
this.clientId = getClientId(deviceId, timestamp);
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getClientId() {
return clientId;
}
private static String getPassword(String deviceSecret, String timestamp) {
return hmacSha256(deviceSecret, timestamp);
}
private static String getClientId(String deviceId, String timestamp) {
return deviceId + "_0_0_" + timestamp;
}
/***
* 要求10位数字
*/
private static String getTimeStamp() {
return ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
}
/***
* 调用sha256算法进行哈希
*/
private static String hmacSha256(String message, String tStamp) {
return DigestUtils.hmacSha256Hex(message, tStamp);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.nginx;
import org.dromara.mica.mqtt.core.server.MqttServer;
/**
* mqtt 服务端测试
*
* @author L.cm
*/
public class MqttServerProxyProtocol {
public static void main(String[] args) {
MqttServer.create()
.enableMqtt()
.enableMqttWs()
// 开启代理协议
.proxyProtocolEnable()
.statEnable(false)
.debug()
.start();
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.proxy;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.client.MqttClient;
/**
* 2 个 mqtt 服务间,使用 2 个 client 做数据传输
*
* @author L.cm
*/
public class MqttClientProxy {
public static void main(String[] args) {
MqttClient client1 = MqttClient.create()
.ip("ip1")
.port(1883)
.clientId("clientI")
.username("mica")
.password("mica")
.debug()
.connectSync();
MqttClient client2 = MqttClient.create()
.ip("ip2")
.port(1883)
.clientId("client2")
.username("mica")
.password("mica")
.debug()
.connectSync();
String[] topics = new String[]{
"$share/test/link/product1/+/event/+/post",
"$share/test/link/product2/+/event/+/post",
"$share/test/link/product3/+/event/+/post"
};
client1.subscribe(topics, MqttQoS.QOS0, (context, topic, message, payload) -> {
client2.publish(topic, payload);
});
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.proxy;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.dromara.mica.mqtt.core.server.MqttServer;
import org.dromara.mica.mqtt.server.MqttConnectStatusListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.utils.buffer.ByteBufferUtil;
/**
* mqtt 服务端代理到另外一个服务端
*
* @author L.cm
*/
public class MqttServerProxy {
private static final Logger logger = LoggerFactory.getLogger(MqttServerProxy.class);
public static void main(String[] args) {
// 需要将数据发往的服务端
MqttClient client = MqttClient.create()
.ip("ip")
.port(1883)
.clientId("proxy")
.username("mica")
.password("mcia")
.debug()
.connectSync();
// 接受数据的服务端
MqttServer.create()
.messageListener((context, clientId, topic, qoS, message) -> {
byte[] payload = message.payload();
logger.info("clientId:{} topic:{} payload:\n{}", clientId, topic, ByteBufferUtil.toString(payload));
// 转发数据
client.publish(topic, payload);
})
// 开启 mqtt tcp 协议
.enableMqtt()
// 开启 mqtt websocket
.enableMqttWs()
// 开启 http api 接口
.enableMqttHttpApi()
// 客户端连接状态监听
.connectStatusListener(new MqttConnectStatusListener())
// 开始 stat 监控
.statEnable()
// 开启 debug 信息日志
.debug()
.start();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server;
import org.dromara.mica.mqtt.core.server.event.IMqttConnectStatusListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
/**
* mqtt 连接状态
*
* @author L.cm
*/
public class MqttConnectStatusListener implements IMqttConnectStatusListener {
private static final Logger logger = LoggerFactory.getLogger(MqttConnectStatusListener.class);
@Override
public void online(ChannelContext context, String clientId, String username) {
logger.info("Mqtt clientId:{} username:{} online.", clientId, username);
}
@Override
public void offline(ChannelContext context, String clientId, String username, String reason) {
logger.info("Mqtt clientId:{} username:{} offline reason:{}.", clientId, username, reason);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server;
import org.dromara.mica.mqtt.codec.message.MqttMessage;
import org.dromara.mica.mqtt.core.server.interceptor.IMqttMessageInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.Node;
/**
* mqtt 消息拦截器
*
* @author L.cm
*/
public class MqttMessageInterceptor implements IMqttMessageInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MqttMessageInterceptor.class);
@Override
public void onAfterReceivedBytes(ChannelContext context, int receivedBytes) throws Exception {
// 注意:此时 clientId 可能为空
String clientId = context.getBsId();
Node clientNode = context.getClientNode();
// ChannelStat channelStat = context.stat;
// 自定义规则,超限是可用 Tio.remove(context, "xxx超限"); 断开连接。
logger.info("===接收 client:{} clientId:{} data:{}b", clientNode, clientId, receivedBytes);
}
@Override
public void onAfterDecoded(ChannelContext context, MqttMessage message, int packetSize) {
// 注意:此时 clientId 可能为空
String clientId = context.getBsId();
Node clientNode = context.getClientNode();
logger.info("===解码 client:{} clientId:{} message:{}", clientNode, clientId, message);
}
@Override
public void onAfterHandled(ChannelContext context, MqttMessage message, long cost) throws Exception {
String clientId = context.getBsId();
Node clientNode = context.getClientNode();
logger.info("===处理完成 ip:{} clientId:{} message:{} 耗时:{}", clientNode, clientId, message, cost);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server;
import org.dromara.mica.mqtt.core.server.MqttServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
/**
* mqtt 服务端测试
*
* @author L.cm
*/
public class MqttServerTest {
private static final Logger logger = LoggerFactory.getLogger(MqttServerTest.class);
public static void main(String[] args) {
// 注意:为了能接受更多链接(降低内存),请添加 jvm 参数 -Xss129k
MqttServer mqttServer = MqttServer.create()
// 服务端 ip 默认为空0.0.0.0,建议不要设置,端口 默认1883
.enableMqtt(1883)
// 默认为: 8192mqtt 默认最大消息大小),为了降低内存可以减小小此参数,如果消息过大 t-io 会尝试解析多次(建议根据实际业务情况而定)
.readBufferSize(8192)
// 最大包体长度
// .maxBytesInMessage(1024 * 100)
// mqtt 3.1 协议会校验 clientId 长度。
// .maxClientIdLength(64)
.messageListener((context, clientId, topic, qos, message) -> {
logger.info("clientId:{} payload:{}", clientId, new String(message.payload(), StandardCharsets.UTF_8));
})
// 客户端连接状态监听
.connectStatusListener(new MqttConnectStatusListener())
// 自定义消息拦截器
.addInterceptor(new MqttMessageInterceptor())
// 开启 websocket
.enableMqttWs()
// 开启 mqtt http 接口
.enableMqttHttpApi(builder ->
builder
.basicAuth("mica", "mica") // http basic 认证
.mcpServer() // 开启 mcp 服务
.build()
)
// 开始 stat 监控
.statEnable()
// 开启 debug 信息日志
.debug()
.start();
mqttServer.schedule(() -> {
String message = "mica最牛皮 " + System.currentTimeMillis();
mqttServer.publishAll("/test/123", message.getBytes(StandardCharsets.UTF_8));
}, 2000);
// 2.3.2 开始支持 stop 关闭
// mqttServer.stop();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.ssl;
import org.dromara.mica.mqtt.core.client.MqttClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.utils.buffer.ByteBufferUtil;
import java.nio.charset.StandardCharsets;
/**
* 客户端测试
*
* @author L.cm
*/
public class SslMqttClientTest {
private static final Logger logger = LoggerFactory.getLogger(SslMqttClientTest.class);
public static void main(String[] args) {
// 初始化 mqtt 客户端
MqttClient client = MqttClient.create()
.ip("127.0.0.1")
.port(8883)
.username("mica")
.password("mica")
.useSsl("classpath:ssl/dreamlu.net.jks", "123456")
.connectSync();
client.subQos0("/test/#", (context, topic, message, payload) -> {
logger.info(topic + '\t' + ByteBufferUtil.toString(payload));
});
// 定时发送数据
client.schedule(() -> {
String message = "mica最牛皮 " + System.currentTimeMillis();
client.publish("/test/123", message.getBytes(StandardCharsets.UTF_8));
}, 5000);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.ssl;
import org.dromara.mica.mqtt.core.server.MqttServer;
import org.dromara.mica.mqtt.server.MqttConnectStatusListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.utils.buffer.ByteBufferUtil;
import java.nio.charset.StandardCharsets;
/**
* mqtt 服务端测试
*
* @author L.cm
*/
public class SslMqttServerTest {
private static final Logger logger = LoggerFactory.getLogger(SslMqttServerTest.class);
public static void main(String[] args) {
MqttServer mqttServer = MqttServer.create()
.enableMqttSsl(builder ->
builder.useSsl("classpath:ssl/dreamlu.net.jks", "123456")
.build()
)
.messageListener((context, clientId, topic, qoS, message) -> {
logger.info("clientId:{} message:{} payload:{}", clientId, message, ByteBufferUtil.toString(message.payload()));
})
.connectStatusListener(new MqttConnectStatusListener())
.debug()
.start();
// 定时发送数据
mqttServer.schedule(() -> {
String message = "mica最牛皮 " + System.currentTimeMillis();
mqttServer.publishAll("/test/123", message.getBytes(StandardCharsets.UTF_8));
}, 5000);
}
}

View File

@@ -0,0 +1 @@
ssl 自签双向证书详见https://gitee.com/596392912/mica-mqtt/issues/I45GO7

View File

@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICejCCAeMCFGkzqgl8ilICj+PRCwIiUy3dPNuWMA0GCSqGSIb3DQEBCwUAMHwx
CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJITjELMAkGA1UEBwwCQ1MxDTALBgNVBAoM
BFJNSlMxDTALBgNVBAsMBFJNSlMxFDASBgNVBAMMC2RyZWFtbHUubmV0MR8wHQYJ
KoZIhvcNAQkBFhA1OTYzOTI5MTJAcXEuY29tMB4XDTIyMDkwMzA4MjExNVoXDTMy
MDgzMTA4MjExNVowfDELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkhOMQswCQYDVQQH
DAJDUzENMAsGA1UECgwEUk1KUzENMAsGA1UECwwEUk1KUzEUMBIGA1UEAwwLZHJl
YW1sdS5uZXQxHzAdBgkqhkiG9w0BCQEWEDU5NjM5MjkxMkBxcS5jb20wgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAJxanDadRHd+D9jH5/mq7Pn3Fl915CiOaaJr
QS0ZxCqRtTaS9+JkFQJ4TnAezlKx/xDZu7G9pk7CJ6w+JQwfI+AwsAkOlrynyFbe
Hc9s6DZyZHXxkpgeQUqpnrkXkWG+jbh2aulWB4smQE/vPnpkjVGEe86+/JsYW3Sm
rFhL2xddAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAP39PM0XpNzg3Bne63oXHRWDJ
bafiCwloO/SaxH7JtCBj1I05W3owwHSqWYadK+lg//tKf6TL+94GtW8s2VtpLGu7
Y5R2aakhywzbWVfrEK+kyvTG/4nP9tvcKwh8Iqr2XlllDLfsQeyLacb4+pcfDY3G
8nrbe0lBKepae8D0SvQ=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICejCCAeMCFBebamYN4nfF0CrZWuN68SXxTH/GMA0GCSqGSIb3DQEBCwUAMHwx
CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJITjELMAkGA1UEBwwCQ1MxDTALBgNVBAoM
BFJNSlMxDTALBgNVBAsMBFJNSlMxFDASBgNVBAMMC2RyZWFtbHUubmV0MR8wHQYJ
KoZIhvcNAQkBFhA1OTYzOTI5MTJAcXEuY29tMB4XDTIyMDkwMzA4MjM1N1oXDTMy
MDgzMTA4MjM1N1owfDELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkhOMQswCQYDVQQH
DAJDUzENMAsGA1UECgwEUk1KUzENMAsGA1UECwwEUk1KUzEUMBIGA1UEAwwLZHJl
YW1sdS5uZXQxHzAdBgkqhkiG9w0BCQEWEDU5NjM5MjkxMkBxcS5jb20wgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAL3hH0vRYCsqs79ghUPskgr+bQyVkO/6U1H1
4yHJWyWeXT2tl+VTvXhou0Zk3yYtd6O3l241PgmAjwBDPyfmM2UU/zluXQzELEuf
AnC4Q2wl0vpoj7mGQeMuRpjZN3FblS0bRifvctx3ubWVvSzCFIaKLZgdrnyWm5Lm
kJQjRaEDAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAkm/Mv9nEGE4xy3L4NmwgyZMo
j5CuarbCEv/MgXST8B/dJvtC7PbCzrb9BbUkiIUjCOnRC+2U0DbSAkHy7CCwHTLx
g9Syn1SMWn9SF74l74qVtuaRjMKCGwSlqBLH5/t1FdPoevY2a+zi5dAqwIhkjY84
uYOgxODe+kFBUbT6W3A=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC94R9L0WArKrO/YIVD7JIK/m0MlZDv+lNR9eMhyVslnl09rZfl
U714aLtGZN8mLXejt5duNT4JgI8AQz8n5jNlFP85bl0MxCxLnwJwuENsJdL6aI+5
hkHjLkaY2TdxW5UtG0Yn73Lcd7m1lb0swhSGii2YHa58lpuS5pCUI0WhAwIDAQAB
AoGAR/T8azsZesJgA/KMDkWkws3QfahgmNEAqlrIjJFGHWd6ZllW6u1lLDBkaDTp
7AnnAQAePwGmVOuHRc42LOSsLMX/D2wYsTGjLTT1w+fEDkQCVnDKV8ZYWA5fN7Zh
m5cLB5IB23L/Xvs8UMYQ8qWufv6BxVPr+cawtXOK3O91AoECQQD5PwGSrMpsNkVG
Ox1t1A9wVgelbq+9qANPl8TvdaZKvNsbWwI0PRqK7D1SJXJiMX4m/hc8YkIXJWsO
Wd1yLg6TAkEAwwZLncJYlYOp0v8PXk75rDR7KV9fdBPPOdnZ1XU/yFW3anwOt0OX
LvGL3X0A2f/tqOX9v+3LENCICF4gAv850QJBALo7Yal+giEoy8oWEX8mnAKLxVrO
wXEsQI0QEY36ki31vqFJ9vOhVFvI+GiQok7MPD5WTHZJ1KgGxV8LtnLCBxECQCPD
WcZ6RyhT1qaco0LWFK7hiNxTYvu0TkH7kxizwZiJL3NVgJVWzbiMDuv06l0Ps5NP
abLydlSFCQ0PxasHBqECQQC0lhmqkNbaN3lJyGDoEWm6Kb9z3eh3+9Fk4j328aYW
gQcBeRhKU8kdDTg2flOWS3sxrEysJYv8i9DPbX9RsRFd
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
writer = console
writer.format = {date: HH:mm:ss.SSS} [{thread}] {level} {class-name}.{method} : {message}
writer.level = info
# level
level@org.tio = warn
level@org.tio.client.TioClient = off
level@org.tio.server = info
level@org.dromara.mica.mqtt = info

View File

@@ -0,0 +1 @@
package net.dreamlu.iot;

View File

@@ -0,0 +1,49 @@
<?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>org.dromara.mica-mqtt</groupId>
<artifactId>example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>mica-mqtt-server-solon-plugin-example</artifactId>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-solon-plugin</artifactId>
</dependency>
<!-- 简单的本地定时任务调度 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-scheduling-simple</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-logging-simple</artifactId>
</dependency>
<!-- metrics 指标 开始 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-cloud-metrics</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- metrics 指标 结束 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,19 @@
package org.dromara.mica.mqtt.server.solon;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.scheduling.annotation.EnableScheduling;
/**
* @author wsq
*/
@Configuration
@EnableScheduling
public class MqttServerApplication {
public static void main(String[] args) {
Solon.start(MqttServerApplication.class, args);
}
}

View File

@@ -0,0 +1,22 @@
package org.dromara.mica.mqtt.server.solon.controller;
import org.dromara.mica.mqtt.server.solon.service.ServerService;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Post;
@Mapping("/mqtt/server")
@Controller
public class ServerController {
@Inject
private ServerService service;
@Mapping("publish")
@Post
public boolean publish(String body) {
return service.publish(body);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.server.solon.event.MqttClientOfflineEvent;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.event.EventListener;
/**
* mqtt 连接状态,使用 solon event 方式,性能有损耗
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttConnectOfflineListener implements EventListener<MqttClientOfflineEvent> {
@Override
public void onEvent(MqttClientOfflineEvent mqttClientOfflineEvent) throws Throwable {
log.info("MqttClientOnlineEvent:{}", mqttClientOfflineEvent);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.server.solon.event.MqttClientOnlineEvent;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.event.EventListener;
/**
* mqtt 连接状态,使用 solon event 方式,性能有损耗
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttConnectOnlineListener implements EventListener<MqttClientOnlineEvent> {
@Override
public void onEvent(MqttClientOnlineEvent mqttClientOnlineEvent) throws Throwable {
log.info("MqttClientOnlineEvent:{}", mqttClientOnlineEvent);
}
}

View File

@@ -0,0 +1,25 @@
package org.dromara.mica.mqtt.server.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.server.event.IMqttMessageListener;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 消息监听器示例1直接实现 IMqttMessageListener注意如果实现了 IMqttMessageListenerMqttServerFunction 注解就不生效了。
*
* @author wsq
*/
@Slf4j
//@Component
public class MqttServerMessageListener1 implements IMqttMessageListener {
@Override
public void onMessage(ChannelContext context, String clientId, String topic, MqttQoS qos, MqttPublishMessage message) {
log.info("context:{} clientId:{} message:{} payload:{}", context, clientId, message, new String(message.payload(), StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,48 @@
package org.dromara.mica.mqtt.server.solon.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.annotation.MqttServerFunction;
import org.dromara.mica.mqtt.server.solon.pojo.User;
import org.noear.solon.annotation.Component;
import org.tio.core.ChannelContext;
import org.tio.core.Node;
import java.util.Map;
/**
* 消息监听器示例2MqttServerFunction 注解订阅,注意:如果自行实现了 IMqttMessageListenerMqttServerFunction 注解就不生效了。
*
* @author L.cm
*/
@Slf4j
@Component
public class MqttServerMessageListener2 {
@MqttServerFunction("/test/object")
public void func1(String topic, User<?> user) {
log.info("topic:{} user:{}", topic, user);
}
@MqttServerFunction("/test/client")
public void func1(String topic, byte[] message) {
log.info("topic:{} message:{}", topic, new String(message));
}
/**
* MQTT消息处理函数匹配 mqtt Topic /test/+,如何需要匹配所以消息,请使用通配符 #
*
* @param context ChannelContext可选参数
* @param topic 实际接收到消息的主题名称,可选参数
* @param topicVars topic 中的 ${xxxx} 变量解析v2.5.4支持),可选参数,注意:类型必须为 Map<String, String>
* @param publishMessage 完整的MQTT发布消息对象包含消息头和负载可选参数
* @param message 消息负载内容,以字节数组形式提供,可选参数,也可支持对象形式,默认 json 序列化
*/
@MqttServerFunction("/test/${xxxx}")
public void func3(ChannelContext context, String topic, Map<String, String> topicVars, MqttPublishMessage publishMessage, byte[] message) {
// 获取客户端节点信息
Node clientNode = context.getClientNode();
// 记录接收到的MQTT消息信息
log.info("clientNode:{} topic:{} topicVars:{} publishMessage:{} message:{}", clientNode, topic, topicVars, publishMessage, new String(message));
}
}

View File

@@ -0,0 +1,19 @@
package org.dromara.mica.mqtt.server.solon.pojo;
import lombok.Data;
@Data
public class User<T> {
private String name;
private T girlfriend;
public static User newUser(){
User<User> user1 = new User();
user1.setName("name1");
User<User> user2 = new User();
user2.setName("name2");
user2.setGirlfriend(user1);
return user2;
}
}

View File

@@ -0,0 +1,24 @@
package org.dromara.mica.mqtt.server.solon.service;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.server.solon.MqttServerTemplate;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.nio.charset.StandardCharsets;
/**
* @author wsq
*/
@Slf4j
@Component
public class ServerService {
@Inject
private MqttServerTemplate server;
public boolean publish(String body) {
boolean result = server.publishAll("/test/123", body.getBytes(StandardCharsets.UTF_8));
log.info("Mqtt publishAll result:{}", result);
return result;
}
}

View File

@@ -0,0 +1,25 @@
package org.dromara.mica.mqtt.server.solon.task;
import org.dromara.mica.mqtt.server.solon.MqttServerTemplate;
import org.dromara.mica.mqtt.server.solon.pojo.User;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.scheduling.annotation.Scheduled;
import java.nio.charset.StandardCharsets;
/**
* @author wsq
*/
@Component
public class PublishTask {
@Inject
private MqttServerTemplate mqttServerTemplate;
@Scheduled(fixedDelay = 1000)
public void publish() {
mqttServerTemplate.publishAll("/test/123", "mica最牛皮".getBytes(StandardCharsets.UTF_8));
mqttServerTemplate.publishAll("/test/object", User.newUser());
}
}

View File

@@ -0,0 +1,54 @@
# solon 配置
solon:
logging:
appender:
console:
level: INFO
# mqtt 服务端配置
mqtt:
server:
enabled: true # 是否开启服务端默认true
name: Mica-Mqtt-Server # 名称默认Mica-Mqtt-Server
heartbeat-timeout: 120000 # 心跳超时,单位毫秒,默认: 1000 * 120
read-buffer-size: 8KB # 接收数据的 buffer size默认8k
max-bytes-in-message: 10MB # 消息解析最大 bytes 长度默认10M
auth:
enable: false # 是否开启 mqtt 认证
username: mica # mqtt 认证用户名
password: mica # mqtt 认证密码
debug: true # 如果开启 prometheus 指标收集建议关闭
stat-enable: true # 开启指标收集debug 和 prometheus 开启时需要打开,默认开启,关闭节省内存
mqtt-listener: # mqtt 监听器
enable: true # 是否开启默认false
# ip: "0.0.0.0" # 服务端 ip 默认为空0.0.0.0,建议不要设置
port: 1883 # 端口默认1883
mqtt-ssl-listener: # mqtt ssl 监听器
enable: false # 是否开启默认false
port: 8883 # 端口默认8883
ssl: # ssl 配置,必须
keystore-path: # 必须参数ssl keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 必选参数ssl keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
client-auth: none # 是否需要客户端认证双向认证默认NONE不需要
ws-listener: # websocket mqtt 监听器
enable: true # 是否开启默认false
port: 8083 # websocket 端口默认8083
wss-listener: # websocket ssl mqtt 监听器
enable: false # 是否开启默认false
port: 8084 # 端口默认8084
ssl: # ssl 配置,必须
keystore-path: # 必须参数ssl keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 必选参数ssl keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
client-auth: none # 是否需要客户端认证双向认证默认NONE不需要
http-listener:
enable: true
port: 18083
basic-auth: # 基础认证
enable: true
username: mica
password: mica
mcp-server: # 大模型 mcp
enable: true

View File

@@ -0,0 +1,14 @@
## SpringBoot + mica-mqtt-server 应用演示
## 启动步骤
1. 先启动 mica-mqtt-server-spring-boot-example
2. 再启动 mica-mqtt-client-spring-boot-example
3. 查看控制器 swagger 地址http://localhost:30012/doc.html
4. 可开启 prometheus 指标收集,详见: http://localhost:30012/actuator/prometheus
## 连接
mica Spring boot 开发组件集文档https://www.dreamlu.net/components/mica-swagger.html

View File

@@ -0,0 +1,92 @@
<?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>
<artifactId>mica-mqtt-server-spring-boot-example</artifactId>
<parent>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>example</artifactId>
<version>${revision}</version>
</parent>
<dependencies>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-lite</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-logging</artifactId>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-openapi</artifactId>
</dependency>
<!-- 开启 prometheus 指标收集,详见: http://localhost:30012/actuator/prometheus -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>3.5.14</version>
</dependency>
<!-- &lt;!&ndash; MySQL Connector &ndash;&gt;-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- &lt;!&ndash; Druid Connection Pool &ndash;&gt;-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package org.dromara.mica.mqtt.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author wsq
*/
@SpringBootApplication
@EnableScheduling
public class MqttServerApplication {
/**
* 先启动本项目,再启动 mica-mqtt-client-spring-boot-example 进行测试
*/
public static void main(String[] args) {
SpringApplication.run(MqttServerApplication.class, args);
}
}

View File

@@ -0,0 +1,21 @@
package org.dromara.mica.mqtt.server.auth;
import org.dromara.mica.mqtt.core.server.auth.IMqttServerAuthHandler;
import org.springframework.context.annotation.Configuration;
import org.tio.core.ChannelContext;
/**
* 示例 mqtt tcp、websocket 认证,请按照自己的需求和业务进行扩展
*
* @author L.cm
*/
@Configuration(proxyBeanMethods = false)
public class MqttAuthHandler implements IMqttServerAuthHandler {
@Override
public boolean authenticate(ChannelContext context, String uniqueId, String clientId, String username, String password) {
// 客户端认证逻辑实现
return true;
}
}

View File

@@ -0,0 +1,36 @@
package org.dromara.mica.mqtt.server.auth;
import org.dromara.mica.mqtt.core.server.http.api.code.ResultCode;
import org.dromara.mica.mqtt.core.server.http.api.result.Result;
import org.dromara.mica.mqtt.core.server.http.handler.HttpFilter;
import org.dromara.mica.mqtt.core.server.http.handler.MqttHttpRoutes;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
/**
* 示例自定义 mqtt http 接口认证,请按照自己的需求和业务进行扩展
*
* @author L.cm
*/
@Configuration(proxyBeanMethods = false)
public class MqttHttpAuthFilter implements HttpFilter, InitializingBean {
@Override
public boolean filter(HttpRequest request) throws Exception {
// 自行实现逻辑
return true;
}
@Override
public HttpResponse response(HttpRequest request) {
// 认证不通过时的响应
return Result.fail(request, ResultCode.E103);
}
@Override
public void afterPropertiesSet() throws Exception {
MqttHttpRoutes.addFilter(this);
}
}

View File

@@ -0,0 +1,22 @@
package org.dromara.mica.mqtt.server.auth;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.server.auth.IMqttServerSubscribeValidator;
import org.springframework.context.annotation.Configuration;
import org.tio.core.ChannelContext;
/**
* 示例自定义订阅校验,请按照自己的需求和业务进行扩展
*
* @author L.cm
*/
@Configuration(proxyBeanMethods = false)
public class MqttSubscribeValidator implements IMqttServerSubscribeValidator {
@Override
public boolean isValid(ChannelContext context, String clientId, String topicFilter, MqttQoS qoS) {
// 校验客户端订阅的 topic校验成功返回 true失败返回 false
return true;
}
}

View File

@@ -0,0 +1,21 @@
package org.dromara.mica.mqtt.server.auth;
import org.dromara.mica.mqtt.core.server.auth.IMqttServerUniqueIdService;
import org.springframework.context.annotation.Configuration;
import org.tio.core.ChannelContext;
/**
* 示例自定义 clientId请按照自己的需求和业务进行扩展
*
* @author L.cm
*/
@Configuration(proxyBeanMethods = false)
public class MqttUniqueIdService implements IMqttServerUniqueIdService {
@Override
public String getUniqueId(ChannelContext context, String clientId, String userName, String password) {
// 返回的 uniqueId 会替代 mqtt client 传过来的 clientId请保证返回的 uniqueId 唯一。
return clientId;
}
}

View File

@@ -0,0 +1,26 @@
package org.dromara.mica.mqtt.server.controller;
import lombok.AllArgsConstructor;
import org.dromara.mica.mqtt.server.entity.CarInfo;
import org.dromara.mica.mqtt.server.service.ICarInfoService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/car")
@AllArgsConstructor
public class CarInfoController {
private final ICarInfoService carInfoService;
@GetMapping("/list")
public List<CarInfo> list() {
return carInfoService.list();
}
@PostMapping
public boolean save(@RequestBody CarInfo carInfo) {
return carInfoService.save(carInfo);
}
}

View File

@@ -0,0 +1,25 @@
package org.dromara.mica.mqtt.server.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dromara.mica.mqtt.server.service.ServerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "Mqtt::服务端")
@RequestMapping("/mqtt/server")
@RestController
public class ServerController {
@Autowired
private ServerService service;
@Operation(summary = "publish")
@PostMapping("publish")
public boolean publish(@RequestBody String body) {
return service.publish(body);
}
}

View File

@@ -0,0 +1,12 @@
package org.dromara.mica.mqtt.server.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("car_info")
public class CarInfo {
private Long customerId;
private String plate;
private String enable;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & dreamlu.net).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.mica.mqtt.server.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.spring.server.event.MqttClientOfflineEvent;
import org.dromara.mica.mqtt.spring.server.event.MqttClientOnlineEvent;
import org.springframework.context.event.EventListener;
/**
* mqtt 连接状态,使用 spring boot event 方式,性能有损耗
*
* @author L.cm
*/
@Slf4j
//@Service
public class MqttConnectStatusListener1 {
@EventListener
public void online(MqttClientOnlineEvent event) {
log.info("MqttClientOnlineEvent:{}", event);
}
@EventListener
public void offline(MqttClientOfflineEvent event) {
log.info("MqttClientOfflineEvent:{}", event);
}
}

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