EMQX构建简易的云服务

基本思路:

  1. 使用EMQX作为Mqtt broker
  2. mqtt-receive-server服务,用于接收设备上报的数据
  3. mqtt-sender-service服务,用于下发数据给设备
  4. KafKa实现数据解耦,mqtt-receive-server服务接收的数据简单处理下直接扔到Kafka中
  5. 云服务各业务系统从KafKa中消费数据,各业务需要下发数据的话,调用mqtt-sender-service接口下发数据给设备

基本流程

DashBoard 定义认证用户

定义Mqtt协议主题

bash 复制代码
// 设备激活
public final static String ACTIVATE = "mqtt/0/1";
// 设备重置
public final static String RESET = "mqtt/0/0";
// 上线
public final static String ONLINE = "mqtt/1/1";
// 下线
public final static String OFFLINE = "mqtt/1/0";
// 上行-设备上报数据到平台
public final static String REPORT = "mqtt/2/1";
// 下行-平台下发数据给设备
public final static String ISSUED = "%s/2/0";

设备认证流程

首先在云平台创建产品,生成PK/PS,用于Mqtt Broker的连接认证

将PK/PS烧录到设备中

设备开机启动,首次连接平台携带PK/PS/DK,mqtt连接成功后,云服务端会下发DS给到设备,并标识设备已激活

设备再次连接云服务,mqtt连接成功后,会校验DK/DS是否合法,不合法将设备踢下线。

设备订阅${clientId}/2/0主题

bash 复制代码
@PostConstruct
 public void init() throws MqttException {
     client.setCallback(new MqttCallbackHandler());
     client.subscribe(String.format(MqttTopicConstant.ISSUED, client.getClientId()));
 }

mqtt-receive-server服务

使用EMQX内置的用户,连接Mqtt Broker,clientId=mqtt_receive_server

订阅ACTIVATE 、RESET 、ONLINE 、OFFLINE 、REPORT 等主题

将接收的数据简单处理,转发到KafKa

bash 复制代码
mqtt:
    broker-url: tcp://42.194.132.44:1883
    client-id: mqtt_receive_server
    username: mqtt_server
    password: 9b31fa798e16532b0285e130b004836d33391f908f043f2ce0897eea0a669fa0
bash 复制代码
@PostConstruct
public void init() throws MqttException {
    client.setCallback(new MqttCallbackHandler(kafkaService));
    subscribe(MqttTopicConstant.ACTIVATE);
    subscribe(MqttTopicConstant.RESET);
    subscribe(MqttTopicConstant.ONLINE);
    subscribe(MqttTopicConstant.OFFLINE);
    subscribe(MqttTopicConstant.REPORT);
}
java 复制代码
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
    String data = new String(message.getPayload());
    log.info("接收消息主题:{}, Qos:{}, 消息内容:{}", topic, message.getQos(), data);
    UpData upData = JSONObject.parseObject(data, UpData.class);
    UpKafKaData upKafKaData = new UpKafKaData(topic, data);
    log.info("upKafKaData: {}", JSON.toJSONString(upKafKaData));
    kafkaService.sendData(UP_DATA_TOPIC, upData.getClientId(), JSON.toJSONString(upKafKaData));
}

mqtt-sender-service服务

使用EMQX内置的用户,连接Mqtt Broker,clientId=mqtt_sender_server

不订阅主题,只下发数据,下发数据主题为${clientId}/2/0

提供API给给业务子系统使用,用于下发数据给设备

bash 复制代码
mqtt:
    broker-url: tcp://42.194.132.44:1883
    client-id: mqtt_sender_server
    username: mqtt_server
    password: 9b31fa798e16532b0285e130b004836d33391f908f043f2ce0897eea0a669fa0
java 复制代码
package com.angel.ocean.listener;

import com.alibaba.fastjson2.JSONObject;
import com.angel.ocean.contants.MqttTopicConstant;
import com.angel.ocean.domain.UpKafKaData;
import com.angel.ocean.domain.client.ActivateData;
import com.angel.ocean.mqtt.MqttService;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static com.angel.ocean.contants.KafkaTopicConstant.UP_DATA_TOPIC;

@Slf4j
@Component
public class UpDataConsumerListener {

    @Resource
    private MqttService mqttService;

    /**
     * 批量消费
     */
    @KafkaListener(topics = UP_DATA_TOPIC, containerFactory = "batchFactory")
    public void batchListen(List<ConsumerRecord<String, String>> records, Acknowledgment ack) {
        try {
            log.info("UpDataConsumerListener.batchListen(), records.size: {}", records.size());
            for (ConsumerRecord<String, String> record : records) {
                UpKafKaData data = JSONObject.parseObject(record.value(), UpKafKaData.class);
                log.info("{}", record.value());
                handler(data.getTopic(), data.getData());
            }
        } catch (Exception e) {
            log.error("UpDataConsumerListener.batchListen() Exception:{}", e.getMessage(), e);
        } finally {
            // 手动确认
            ack.acknowledge();
        }
    }

    private void handler(String topic, String data) {
        switch (topic) {
            case MqttTopicConstant.ACTIVATE:
                activateHandler(data);
                break;
            case MqttTopicConstant.RESET:
                otherHandler(data);
                break;
            case MqttTopicConstant.OFFLINE:
                otherHandler(data);
                break;
            case MqttTopicConstant.ONLINE:
                otherHandler(data);
                break;
            case MqttTopicConstant.REPORT:
                otherHandler(data);
                break;
            default:
                otherHandler(data);
        }
    }

    private void activateHandler(String data) {
        ActivateData activateData = JSONObject.parseObject(data, ActivateData.class);
        String clientId = activateData.getClientId();
        mqttService.publish(String.format(MqttTopicConstant.ISSUED, clientId), "200");
    }

    private void otherHandler(String data) {
        log.info("{}", data);
    }

}
java 复制代码
package com.angel.ocean.controller;

import com.angel.ocean.common.ApiResult;
import com.angel.ocean.contants.MqttTopicConstant;
import com.angel.ocean.mqtt.MqttService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@Slf4j
@RestController
@RequestMapping("/mqtt/server")
public class MqttController {

    @Resource
    private MqttClient server;

    @Resource
    private MqttService mqttService;

    /**
     * 数据下发接口
     * @param clientId
     * @param data
     * @return
     */
    @RequestMapping("/sender")
    public ApiResult<?> publish(String clientId, String data) {

        String topic = String.format(MqttTopicConstant.ISSUED, clientId);

        mqttService.publish(topic, data);

        if(server.isConnected()) {
            MqttMessage message = new MqttMessage(data.getBytes());
            message.setQos(0);
            try {
                server.publish(topic, message);
                log.info("Message published, topic:{}, data:{}", topic, data);
            } catch (MqttException e) {
                log.error("Message publish failed, topic:{}", topic, e);
                return ApiResult.error();
            }
            return ApiResult.success();
        }

        log.info("Message publish failed, not online.");

        return ApiResult.error();
    }
}

代码验证

场景:设备上报消息,云服务端回复消息给设备; 云服务主动下发数据给设备。

模拟设备上报消息, 接收云平台回复

发了两次:
mqtt-client 本地客户端日志:

mqtt-receive-server云服务日志:

mqtt-sender-server云服务日志:

模拟云平台主动下发数据

mqtt-sender-server云服务主动下发的日志:

mqtt-client数据接收日志:

相关推荐
eternal__day30 分钟前
数据结构十大排序之(冒泡,快排,并归)
java·数据结构·算法
Theodore_10221 小时前
3 需求分析
java·开发语言·算法·java-ee·软件工程·需求分析·需求
小笨猪-1 小时前
统⼀服务⼊⼝-Gateway
java·spring cloud·微服务·gateway
深耕AI1 小时前
在Excel中绘制ActiveX控件:解决文本编辑框定位问题
java·前端·excel
我叫吴桂鑫2 小时前
myexcel的使用
java·myexcel
小小unicorn2 小时前
【C++初阶】STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set
java·c++·散列表
l_lOct2 小时前
为什么要写单元测试呢?
java
CodeClimb2 小时前
【华为OD-E卷-开心消消乐 100分(python、java、c++、js、c)】
java·python·华为od
神的孩子都在歌唱3 小时前
Java 和 J2EE 有什么不同?
java·开发语言·java-ee
向宇it3 小时前
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
java·开发语言·unity·c#·游戏引擎·里氏替换原则