一分钟在SpringBoot项目中使用EMQ
先展示最终的结果:
生产者端:
@RestController
@RequiredArgsConstructor
public class TestController {private final MqttProducer mqttProducer;@GetMapping("/test")public String test() {User build = User.builder().age(100).sex(1).address("世界潍坊渤海之眼").build();// 延时发布mqttProducer.send("$delayed/10/cookie", 2, JSON.toJSONString(build));return "ok";}}
消费者端
/*** @author : Cookie* date : 2024/1/30*/
@Component
@Topic("cookie")
public class TestTopicHandler implements MsgHandler {@Overridepublic void process(String jsonMsg) {User user = JSON.parseObject(jsonMsg, User.class);System.out.println(user);}}
控制台结果:
具体解释在之前的笔记中, 需要的话可以查看 EMQ的介绍及整合SpringBoot的使用-CSDN博客
OK, 下面我们就开始实现上面的逻辑, 你要做的就是把 1-9 复制到项目即可
1. 依赖导入
<dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.5</version>
</dependency>
2. yml 配置
# 顶格
mqtt:client:username: adminpassword: publicserverURI: tcp://192.168.200.128:1883clientId: monitor.task.${random.int[10000,99999]} # 注意: emq的客户端id 不能重复keepAliveInterval: 10 #连接保持检查周期 秒connectionTimeout: 30 #连接超时时间 秒producer:defaultQos: 2defaultRetained: falsedefaultTopic: topic/test1consumer:consumerTopics: $queue/cookie/#, $share/group1/yfs1024 #不带群组的共享订阅 多个主题逗号隔开# $queue/cookie/## 以$queue开头,不带群组的共享订阅 多个客户端只能有一个消费者消费# $share/group1/yfs1024# 以$share开头,群组的共享订阅 多个客户端订阅# 如果在一个组 只能有一个消费者消费# 如果不在一个组 都可以消费
3. 属性配置
@Data
@Configuration
@ConfigurationProperties(prefix = "mqtt.client")
public class MqttConfigProperties {private int defaultProducerQos;private boolean defaultRetained;private String defaultTopic;private String username;private String password;private String serverURI;private String clientId;private int keepAliveInterval;private int connectionTimeout;}
4. 定义主题注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Topic {String value();
}
5.Mqtt配置类
@Data
@Slf4j
@Configuration
@RequiredArgsConstructor
public class MqttConfig {private final MqttConfigProperties configProperties;private final MqttCallback mqttCallback;@Beanpublic MqttClient mqttClient() {try {MqttClient client = new MqttClient(configProperties.getServerURI(), configProperties.getClientId(), mqttClientPersistence());client.setManualAcks(true); //设置手动消息接收确认mqttCallback.setMqttClient(client);client.setCallback(mqttCallback);client.connect(mqttConnectOptions());return client;} catch (MqttException e) {log.error("emq connect error", e);return null;}}@Beanpublic MqttConnectOptions mqttConnectOptions() {MqttConnectOptions options = new MqttConnectOptions();options.setUserName(configProperties.getUsername());options.setPassword(configProperties.getPassword().toCharArray());options.setAutomaticReconnect(true);//是否自动重新连接options.setCleanSession(true);//是否清除之前的连接信息options.setConnectionTimeout(configProperties.getConnectionTimeout());//连接超时时间options.setKeepAliveInterval(configProperties.getKeepAliveInterval());//心跳options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);//设置mqtt版本return options;}public MqttClientPersistence mqttClientPersistence() {return new MemoryPersistence();}}
6. 定义消息处理接口
/*** 消息处理接口*/
public interface MsgHandler {void process(String jsonMsg) throws IOException;}
7.定义消息上下文
/*** 消息处理上下文, 通过主题拿到topic*/
public interface MsgHandlerContext{MsgHandler getMsgHandler(String topic);}
8. 定义回调类
@Component
@Slf4j
public class MqttCallback implements MqttCallbackExtended {// 需要订阅的topic配置@Value("${mqtt.consumer.consumerTopics}")private List<String> consumerTopics;@Autowiredprivate MsgHandlerContext msgHandlerContext;@Overridepublic void connectionLost(Throwable throwable) {log.error("emq error.", throwable);}@Overridepublic void messageArrived(String topic, MqttMessage message) throws Exception {log.info("topic:" + topic + " message:" + new String(message.getPayload()));//处理消息String msgContent = new String(message.getPayload());log.info("接收到消息:" + msgContent);try {// 根据主题名称 获取 该主题对应的处理器对象// 多态 父类的引用指向子类的对象MsgHandler msgHandler = msgHandlerContext.getMsgHandler(topic);if (msgHandler == null) {return;}msgHandler.process(msgContent); //执行} catch (IOException e) {log.error("process msg error,msg is: " + msgContent, e);}//处理成功后确认消息mqttClient.messageArrivedComplete(message.getId(), message.getQos());}@Overridepublic void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {log.info("deliveryComplete---------" + iMqttDeliveryToken.isComplete());}@Overridepublic void connectComplete(boolean b, String s) {log.info("连接成功");//和EMQ连接成功后根据配置自动订阅topicif (consumerTopics != null && consumerTopics.size() > 0) {// 循环遍历当前项目中配置的所有的主题.consumerTopics.forEach(t -> {try {log.info(">>>>>>>>>>>>>>subscribe topic:" + t);// 订阅当前集群中所有的主题 消息服务质量 2 -> 至少收到一个mqttClient.subscribe(t, 2);} catch (MqttException e) {log.error("emq connect error", e);}});}}private MqttClient mqttClient;// 在配置类中调用传入连接public void setMqttClient(MqttClient mqttClient) {this.mqttClient = mqttClient;}
}
8. 消息处理类加载器
作用: 将Topic跟处理类对应 通过 handlerMap
/*** 消息处理类加载器* 作用:* 1. 因为实现了Spring 的 ApplicationContextAware 接口, 项目启动后就会运行实现的方法* 2. 获取MsgHandler接口的所有的实现类* 3. 将实现类上的Topic注解的值,作为handlerMap的键,实现类(处理器)作为对应的值*/
@Component
public class MsgHandlerContextImp implements ApplicationContextAware, MsgHandlerContext {private final Map<String, MsgHandler> handlerMap = new HashMap<>();@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {// 从spring容器中获取 <所有> 实现了MsgHandler接口的对象// key 默认类名首字母小写 value 当前对象Map<String, MsgHandler> map = applicationContext.getBeansOfType(MsgHandler.class);map.values().forEach(obj -> {// 通过反射拿到注解中的值 即 当前类处理的 topicString topic = obj.getClass().getAnnotation(Topic.class).value();// 将主题和当前主题的处理类建立映射handlerMap.put(topic,obj);});}@Overridepublic MsgHandler getMsgHandler(String topic) {return handlerMap.get(topic);}
}
9. 封装消息生产者
@Slf4j
@Component
public class MqttProducer {// @Value() 读取配置 当然也可以批量读取配置,这里就一个一个了@Value("${mqtt.producer.defaultQos}")private int defaultProducerQos;@Value("${mqtt.producer.defaultRetained}")private boolean defaultRetained;@Value("${mqtt.producer.defaultTopic}")private String defaultTopic;@Autowiredprivate MqttClient mqttClient;public void send(String payload) {this.send(defaultTopic, payload);}public void send(String topic, String payload) {this.send(topic, defaultProducerQos, payload);}public void send(String topic, int qos, String payload) {this.send(topic, qos, defaultRetained, payload);}public void send(String topic, int qos, boolean retained, String payload) {try {mqttClient.publish(topic, payload.getBytes(), qos, retained);} catch (MqttException e) {log.error("publish msg error.",e);}}public <T> void send(String topic, int qos, T msg) throws JsonProcessingException {String payload = JsonUtil.serialize(msg);this.send(topic,qos,payload);}
}
最终的实现的结果
- 生产者端: 在需要发送消息的地方注入 MqttProducer 发送消息
- 消费者端: 在需要处理对应主题的类上 实现 MsgHandler接口
代码示例
生产者端
@RestController
@RequiredArgsConstructor
public class TestController {private final MqttProducer mqttProducer;@GetMapping("/test")public String test() {User build = User.builder().age(100).sex(1).address("世界潍坊渤海之眼").build();// 延时发布mqttProducer.send("$delayed/10/cookie", 2, JSON.toJSONString(build));return "ok";}
}
消费者端
@Component
@Topic("cookie")
public class TestTopicHandler implements MsgHandler {@Overridepublic void process(String jsonMsg) {User user = JSON.parseObject(jsonMsg, User.class);System.out.println(user);}}
控制台结果展示:
补充JsonUtil
public class JsonUtil {/*** 从json字符串中根据nodeName获取值* @param nodeName* @param json* @return* @throws IOException*/public static String getValueByNodeName(String nodeName, String json) throws IOException {ObjectMapper objectMapper = new ObjectMapper();JsonNode jsonNode = objectMapper.readTree(json);JsonNode node = jsonNode.findPath(nodeName);if(node == null) return null;return node.asText();}/*** 根据nodeName获取节点内容* @param nodeName* @param json* @return* @throws IOException*/public static JsonNode getNodeByName(String nodeName, String json) throws IOException {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.readTree(json).findPath(nodeName);}/*** 反序列化* @param json* @param clazz* @param <T>* @return* @throws IOException*/public static <T> T getByJson(String json, Class<T> clazz) throws IOException {ObjectMapper mapper = new ObjectMapper();// 在反序列化时忽略在 json 中存在但 Java 对象不存在的属性mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZmapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);return mapper.readValue(json, clazz);}/*** 反序列化(驼峰转换)* @param json* @param clazz* @param <T>* @return* @throws IOException*/public static <T> T getByJsonSNAKE(String json, Class<T> clazz) throws IOException {ObjectMapper mapper = new ObjectMapper();// 在反序列化时忽略在 json 中存在但 Java 对象不存在的属性mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZmapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);// 设置驼峰和下划线之间的映射mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);return mapper.readValue(json, clazz);}/*** 序列化* @param object* @return* @throws JsonProcessingException*/public static String serialize(Object object) throws JsonProcessingException {ObjectMapper mapper = new ObjectMapper();return mapper.writeValueAsString(object);}/*** 序列化(驼峰转换)* @param object* @return* @throws JsonProcessingException*/public static String serializeSNAKE(Object object) throws JsonProcessingException {ObjectMapper mapper = new ObjectMapper();// 设置驼峰和下划线之间的映射mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);return mapper.writeValueAsString(object);}public static JsonNode getTreeNode(String json) throws JsonProcessingException {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.readTree(json);}/*** 将对象转map* @param obj* @return* @throws IOException*/public static Map<String,Object> convertToMap(Object obj) throws IOException {ObjectMapper mapper = new ObjectMapper();return mapper.readValue(serialize(obj),Map.class);}
}
相关文章:

一分钟在SpringBoot项目中使用EMQ
先展示最终的结果: 生产者端: RestController RequiredArgsConstructor public class TestController {private final MqttProducer mqttProducer;GetMapping("/test")public String test() {User build User.builder().age(100).sex(1).address("世界潍坊渤…...

SOME/IP 协议介绍(七)传输 CAN 和 FlexRay 帧
SOME/IP 不应仅用于传输 CAN 或 FlexRay 帧。但是,消息 ID 空间需要在两种用例之间进行协调。 传输 CAN/FlexRay 应使用完整的 SOME/IP 标头。 AUTOSAR Socket-Adapter 使用消息 ID 和长度来构建所需的内部 PDU,但不会查看其他字段。因此,必…...

与数组相关经典面试题
𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - :来于“云”的“羽球人”。…...
数据结构与算法面试系列-02
1. 一个整数,它加上100后是一个完全平方数,加上168又是一个完全平方数,请问该数是多少? 程序分析:在10万以内判断,先将该数加上100后再开方,再将该数加上168后再开方,如果开方后的结果满足如下条件,即是结果。请看具体分析: 程序代码如下: package com.yoodb.uti…...

CMake 完整入门教程(五)
CMake 使用实例 13.1 例子一 一个经典的 C 程序,如何用 cmake 来进行构建程序呢? //main.c #include <stdio.h> int main() { printf("Hello World!/n"); return 0; } 编写一个 CMakeList.txt 文件 ( 可看做 cmake 的…...
pgsql中with子句和直接查询差别
1、代码的可读性和维护性: 当查询较为复杂时,WITH子句可以将复杂的查询分解成多个简单的步骤,每个步骤都可以有一个易于理解的名字。这样做提高了代码的可读性,也便于后期维护。 2、代码的重用性: 在WITH子句中定义…...
Day 31 | 贪心算法 理论基础 、455.分发饼干 、 376. 摆动序列 、 53. 最大子序和
理论基础 文章讲解 455.分发饼干 题目 文章讲解 视频讲解 思路:从小饼干开始喂小胃口 class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int start 0;int count 0;for (int i 0; i < s.length &&a…...

vue3使用is动态切换组件报错Vue received a Component which was made a reactive object.
vue3使用is动态切换组件,activeComponent用ref定义报错 Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef ins…...

React16源码: React中LegacyContext的源码实现
LegacyContext 老的 contextAPI 也就是我们使用 childContextTypes 这种声明方式来从父节点为它的子树提供 context 内容的这么一种方式遗留的contextAPI 在 react 17 被彻底移除了,就无法使用了那么为什么要彻底移除这个contextAPI的使用方式呢?因为它…...

Gin 框架之jwt 介绍与基本使用
文章目录 一.JWT 介绍二.JWT认证与session认证的区别2.1 基于session认证流程图2.2 基于jwt认证流程图 三. JWT 的构成3.1 header : 头部3.2 payload : 负载3.2.1 标准中注册的声明 (建议但不强制使用)3.2.2 公共的声明3.2.3 私有的声明3.2.4 定义一个payload 3.3 signatrue : …...

从[redis:LinkedList]中学习链表
文章目录 adlistlistNodelistmacros[宏定义]listCreatelistInitNodelistEmptylistReleaselistAddNodeHeadlistLinkNodeHeadlistAddNodeTaillistLinkNodeTaillistInsertNodelistDelNodelistUlinkNodelistIndexredis3.2.100quicklistredis7.2.2quicklist redis的基本数据类型之一…...

Prometheus+grafana配置监控系统
使用docker compose安装 方便拓展, 配置信息都放在在 /docker/prometheus 目录下 1.目录结构如下 . ├── conf │ └── prometheus.yml ├── grafana_data ├── prometheus_data └── prometheus_grafana.yaml2.创建目录文件 mkdir /docker/prometheus &&am…...

Linux之安装配置CentOS 7
一、CentOS简介 CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,…...
神经网络与深度学习Pytorch版 Softmax回归 笔记
Softmax回归 目录 Softmax回归 1. 独热编码 2. Softmax回归的网络架构是一个单层的全连接神经网络。 3. Softmax回归模型概述及其在多分类问题中的应用 4. Softmax运算在多分类问题中的应用及其数学原理 5. 小批量样本分类的矢量计算表达式 6. 交叉熵损失函数 7. 模型预…...
git学习及简单maven打包
前提: 已经有远程仓库地址 和账号密码了 已经安装git了 1.本地新建文件夹A用作本地仓库 2.在A文件夹下右键打开GIT BASH HERE 3.创建用户和密码,方便追踪提交记录 git config --global user.email “caoqingqing0108” //创建邮箱 git config --global …...

如何用MapTalks IDE来发布网站?
简介 MapTalks IDE 全称 MapTalks集成设计环境(Integrated Design Environment),是由MapTalks技术团队开发的新一代web地图设计软件。 通过MapTalks IDE,您可以自由的创建二维和三维地图,在其中载入或创建地理数据&a…...
我用selenium开发了一个自动创建任务,解放重复性工作
我用selenium开发了一个自动创建任务,大大解放了我做重复性工作带来的疲惫感,收获了更多的乐趣。 我司有100多个服务,运维忙不过来的时候,就会让我们自己创建云负载,你首先需要在云服务上创建负载,再创建容…...
安卓11修改HDMI自适应分辨率
客户需要hdmi自适应屏幕分辨率,没发现有相关的指令,我发现设置中有个hdmi的Auto选项,于是就试试选中这个选项,试下了可以自适应,于是就找到相关代码,在开机完成后执行这个代码,基本满足需求&…...

Linux实验记录:使用Apache的虚拟主机功能
前言: 本文是一篇关于Linux系统初学者的实验记录。 参考书籍:《Linux就该这么学》 实验环境: VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 正文: 目录 前言: 正文&…...

分布式空间索引了解与扩展
目录 一、空间索引快速理解 (一)区域编码 (二)区域编码检索 (三)Geohash 编码 (四)RTree及其变体 二、业内方案选取 三、分布式空间索引架构 (一)PG数…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...