物联网-AMQP协议

一、消息队列

消息队列:

  • 生产者:负责将消息发送到消息队列中。
  • 消费者:负责从消息队列中获取消息并进行处理。
  • 队列:存储消息。
  • broker:负责接收、存储和分发消息的中间件组件,实现了发送者和接收者之间的解耦和异步通信。
  • topic:消息的分类。

IOT中数据流转是这样的:

  • 生产者:设备负责将消息发送到IOT中(队列)。
  • 每个产品可以绑定不同的topic来进行消息分类,比如有手表topic、烟雾topic。
  • IOT本身相当于是一个队列。
  • 消费者可以从指定的topic中获取数据。
  • 如果有多个消费者都要接收同一类消息,可以设置多个消费者,称为消费者组。

二、AMQP

AMQP全称Advanced Message Queuing Protocol,是一种网络协议,用于在应用程序之间传递消息。它是一种开放标准的消息传递协议,可以在不同的系统之间实现可靠、安全、高效的消息传递。

AMQP协议的实现包括多种消息队列软件,例如RabbitMQ、Apache ActiveMQ、Apache Qpid等。这些软件提供了可靠、高效的消息传递服务,广泛应用于分布式系统、云计算、物联网等领域。

三、环境集成

IOT平台提供了完整的SDK,我们可以快速集成到项目进行接口的调用,集成方式:官方说明

step1.导入坐标。

xml 复制代码
<dependency>
    <groupId>com.huaweicloud.sdk</groupId>
    <artifactId>huaweicloud-sdk-core</artifactId>
    <version>3.1.76</version>
</dependency>
<dependency>
    <groupId>com.huaweicloud.sdk</groupId>
    <artifactId>huaweicloud-sdk-iotda</artifactId>
    <version>3.1.76</version>
</dependency>

step2.在application-dev.yml文件中添加IOT的配置。

yml 复制代码
huaweicloud:
  ak: UTVLYVJKVGYVEFFWG
  sk: WkEWqfwZoFlLwKq5NmWTLmj71WhRXe
  #如果是上海一,请填写"cn-east-3";如果是北京四,请填写"cn-north-4";
  regionId: cn-east-3
  endpoint: 38e7abf.st1.iotda-app.cn-east-3.myhuaweicloud.com
  projectId: 57ee9b4827a44c19a0770fe7cb
  #amqp相关配置 接收设备数据使用
  host: 38e7abedbf.st1.iotda-app.cn-east-3.myhuaweicloud.com
  accessKey: S5ZTC5
  accessCode: a4fKpE5k0nbGNJU0d1bKkJNRQzlp
  queueName: DefaultQueue   #默认无需改动

step3.新增HuaWeiIotConfigProperties来读取配置文件。

java 复制代码
package com.yyyl.framework.config.properties;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;


@Data
@NoArgsConstructor
@Configuration
@ConfigurationProperties(prefix = "huaweicloud")
public class HuaWeiIotConfigProperties {

    /**
     * 访问Key
     */
    private String ak;

    /**
     * 访问秘钥
     */
    private String sk;

    /**
     * 区域id
     */
    private String regionId;

    /**
     * 应用侧https接入地址
     */
    private String endpoint;

    /**
     * 项目id
     */
    private String projectId;

    /**
     * 应用侧amqp接入地址
     */
    private String host;

    /**
     * amqp连接端口
     */
    private int port = 5671;

    /**
     * amqp接入凭证键值
     */
    private String accessKey;

    /**
     * amqp接入凭证密钥
     */
    private String accessCode;

    // 指定单个进程启动的连接数
    // 单个连接消费速率有限,请参考使用限制,最大64个连接
    // 连接数和消费速率及rebalance相关,建议每500QPS增加一个连接
    //可根据实际情况自由调节,目前测试和正式环境资源有限,限制更改为4
    private int connectionCount = 4;

    /**
     * 队列名称
     */
    private String queueName;

    /**
     * 开门命令所属服务id
     */
    private String smartDoorServiceId;

    /**
     * 开门记录属性
     */
    private String doorOpenPropertyName;

    /**
     * 开门命令
     */
    private String doorOpenCommandName;

    /**
     * 设置临时密码命令
     */
    private String passwordSetCommandName;

    /**
     * 仅支持true
     */
    private boolean useSsl = true;

    /**
     * IoTDA仅支持default
     */
    private String vhost = "default";

    /**
     * IoTDA仅支持PLAIN
     */
    private String saslMechanisms = "PLAIN";

    /**
     * true: SDK自动ACK(默认)
     * false:收到消息后,需要手动调用message.acknowledge()
     */
    private boolean isAutoAcknowledge = true;

    /**
     * 重连时延(ms)
     */
    private long reconnectDelay = 3000L;

    /**
     * 最大重连时延(ms),随着重连次数增加重连时延逐渐增加
     */
    private long maxReconnectDelay = 30 * 1000L;

    /**
     * 最大重连次数,默认值-1,代表没有限制
     */
    private long maxReconnectAttempts = -1;

    /**
     * 空闲超时,对端在这个时间段内没有发送AMQP帧则会导致连接断开。默认值为30000。单位:毫秒。
     */
    private long idleTimeout = 30 * 1000L;

    /**
     * The values below control how many messages the remote peer can send to the client and be held in a pre-fetch buffer for each consumer instance.
     */
    private int queuePrefetch = 1000;

    /**
     * 扩展参数
     */
    private Map<String, String> extendedOptions;
}

step4.添加配置。

java 复制代码
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.core.auth.ICredential;
import com.huaweicloud.sdk.core.region.Region;
import com.huaweicloud.sdk.iotda.v5.IoTDAClient;
import com.yyyl.framework.config.properties.HuaWeiIotConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class IotClientConfig {

    @Autowired
    private HuaWeiIotConfigProperties huaWeiIotConfigProperties;

    @Bean
    public IoTDAClient huaWeiIotInstance() {
        ICredential auth = new BasicCredentials()
                .withAk(huaWeiIotConfigProperties.getAk())
                .withSk(huaWeiIotConfigProperties.getSk())
                // 标准版/企业版需要使用衍生算法,基础版请删除配置"withDerivedPredicate"
                .withDerivedPredicate(BasicCredentials.DEFAULT_DERIVED_PREDICATE)
                .withProjectId(huaWeiIotConfigProperties.getProjectId());

        return IoTDAClient.newBuilder()
                .withCredential(auth)
                // 标准版/企业版:需自行创建Region对象,基础版:请使用IoTDARegion的region对象,如"withRegion(IoTDARegion.CN_NORTH_4)"
                .withRegion(new Region(huaWeiIotConfigProperties.getRegionId(), huaWeiIotConfigProperties.getEndpoint()))
                // .withRegion(IoTDARegion.CN_NORTH_4)
                .build();
    }
}

setp5.创建单元测试,查询产品列表。

java 复制代码
import com.huaweicloud.sdk.iotda.v5.IoTDAClient;
import com.huaweicloud.sdk.iotda.v5.model.ListProductsRequest;
import com.huaweicloud.sdk.iotda.v5.model.ListProductsResponse;
import com.huaweicloud.sdk.iotda.v5.model.Page;
import com.huaweicloud.sdk.iotda.v5.model.ProductSummary;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class IoTDeviceTest {


    @Autowired
    private IoTDAClient client;
    
    /**
     * 查询公共实例下的所有产品
     * @throws Exception
     */
    @Test
    public void selectProduceList() throws Exception {

        ListProductsRequest listProductsRequest = new ListProductsRequest();
        listProductsRequest.setLimit(50);
        ListProductsResponse response = client.listProducts(listProductsRequest);
        List<ProductSummary> products = response.getProducts();
        System.out.println(products);
    }
    
}

四、AMQP设备数据转发

修改链接参数:

java 复制代码
package com.iot.amqp;


public interface AmqpConstants {
    /**
     * AMQP接入域名
     * 参考:https://support.huaweicloud.com/usermanual-iothub/iot_01_00100_2.html#section2
     */
    String HOST = "38e7abedbf.st1.iotda-app.cn-east-3.myhuaweicloud.com";   // eg: "****.iot-amqps.cn-north-4.myhuaweicloud.com";

    /**
     * AMQP接入端口
     * 参考:https://support.huaweicloud.com/usermanual-iothub/iot_01_00100_2.html#section2
     */
    int PORT = 5671;

    /**
     * 接入凭证键值
     * 参考:https://support.huaweicloud.com/usermanual-iothub/iot_01_00100_2.html#section3
     */
    String ACCESS_KEY = "S2DSSeTC5";

    /**
     * 接入凭证密钥
     * 参考:https://support.huaweicloud.com/usermanual-iothub/iot_01_00100_2.html#section3
     */
    String ACCESS_CODE = "61xLTVTzppmBi62h5my2TT2LIXpU";

    /**
     * 默认队列,无需改动
     */
    String DEFAULT_QUEUE = "DefaultQueue";
}

实现思路:

  • 1.导入对应的依赖。
  • 2.所有的可变参数,如HOST、ACCESS_KEY、ACCESS_CODE、DEFAULT_QUEUE这些统一在配置文件中维护。
  • 3.在项目中根据项目需求配置线程池。
  • 1.让spring进行管理和监听,一旦有数据变化之后,就可以马上消费,可以让这个类实现ApplicationRunner接口,重新run方法。

step1.导入依赖

xml 复制代码
<!-- amqp 1.0 qpid client -->
<dependency>
    <groupId>org.apache.qpid</groupId>
    <artifactId>qpid-jms-client</artifactId>
    <version>0.61.0</version>
</dependency>

step2. 在application.yml文件添加iot配置。

step3. 实现ApplicationRunner接口之后的AmqpClient.

让AmqpClient 实现ApplicationRunner接口,重写run方法,然后调用处理数据方法。

java 复制代码
package com.zzyl.nursing.job;

import cn.hutool.core.text.CharSequenceUtil;
import com.zzyl.framework.config.properties.HuaWeiIotConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.qpid.jms.*;
import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
import org.apache.qpid.jms.transports.TransportOptions;
import org.apache.qpid.jms.transports.TransportSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.jms.*;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;


@Slf4j
@Component
public class AmqpClient implements ApplicationRunner {

    @Autowired
    private HuaWeiIotConfigProperties huaWeiIotConfigProperties;

    //业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
    //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
    private static String clientId;

    static {
        try {
            clientId = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        start();
    }

    public void start() throws Exception {
        //参数说明,请参见AMQP客户端接入说明文档。
        for (int i = 0; i < huaWeiIotConfigProperties.getConnectionCount(); i++) {
            //创建amqp连接
            Connection connection = getConnection();

            //加入监听者
            ((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);
            // 创建会话。
            // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
            // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            connection.start();

            // 创建Receiver连接。
            MessageConsumer consumer = newConsumer(session, connection, huaWeiIotConfigProperties.getQueueName());
            consumer.setMessageListener(messageListener);
        }

        log.info("amqp  is started successfully, and will exit after server shutdown ");
    }

    /**
     * 创建amqp连接
     *
     * @return amqp连接
     */
    private Connection getConnection() throws Exception {
        String connectionUrl = generateConnectUrl();
        JmsConnectionFactory cf = new JmsConnectionFactory(connectionUrl);
        // 信任服务端
        TransportOptions to = new TransportOptions();
        to.setTrustAll(true);
        cf.setSslContext(TransportSupport.createJdkSslContext(to));
        String userName = "accessKey=" + huaWeiIotConfigProperties.getAccessKey();
        cf.setExtension(JmsConnectionExtensions.USERNAME_OVERRIDE.toString(), (connection, uri) -> {
            // IoTDA的userName组成格式如下:"accessKey=${accessKey}|timestamp=${timestamp}"
            String newUserName = userName;
            if (connection instanceof JmsConnection) {
                newUserName = ((JmsConnection) connection).getUsername();
            }
            return newUserName + "|timestamp=" + System.currentTimeMillis();
        });

        // 创建连接。
        return cf.createConnection(userName, huaWeiIotConfigProperties.getAccessCode());
    }

    /**
     * 生成amqp连接地址
     *
     * @return amqp连接地址
     */
    public String generateConnectUrl() {
        String uri = MessageFormat.format("{0}://{1}:{2}",
                (huaWeiIotConfigProperties.isUseSsl() ? "amqps" : "amqp"),
                huaWeiIotConfigProperties.getHost(),
                String.valueOf(huaWeiIotConfigProperties.getPort()));
        Map<String, String> uriOptions = new HashMap<>();
        uriOptions.put("amqp.vhost", huaWeiIotConfigProperties.getVhost());
        uriOptions.put("amqp.idleTimeout", String.valueOf(huaWeiIotConfigProperties.getIdleTimeout()));
        uriOptions.put("amqp.saslMechanisms", huaWeiIotConfigProperties.getSaslMechanisms());

        Map<String, String> jmsOptions = new HashMap<>();
        jmsOptions.put("jms.prefetchPolicy.queuePrefetch", String.valueOf(huaWeiIotConfigProperties.getQueuePrefetch()));
        if (CharSequenceUtil.isNotBlank(clientId)) {
            jmsOptions.put("jms.clientID", clientId);
        } else {
            jmsOptions.put("jms.clientID", UUID.randomUUID().toString());
        }
        jmsOptions.put("failover.reconnectDelay", String.valueOf(huaWeiIotConfigProperties.getReconnectDelay()));
        jmsOptions.put("failover.maxReconnectDelay", String.valueOf(huaWeiIotConfigProperties.getMaxReconnectDelay()));
        if (huaWeiIotConfigProperties.getMaxReconnectAttempts() > 0) {
            jmsOptions.put("failover.maxReconnectAttempts", String.valueOf(huaWeiIotConfigProperties.getMaxReconnectAttempts()));
        }
        if (huaWeiIotConfigProperties.getExtendedOptions() != null) {
            for (Map.Entry<String, String> option : huaWeiIotConfigProperties.getExtendedOptions().entrySet()) {
                if (option.getKey().startsWith("amqp.") || option.getKey().startsWith("transport.")) {
                    uriOptions.put(option.getKey(), option.getValue());
                } else {
                    jmsOptions.put(option.getKey(), option.getValue());
                }
            }
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(uriOptions.entrySet().stream()
                .map(option -> MessageFormat.format("{0}={1}", option.getKey(), option.getValue()))
                .collect(Collectors.joining("&", "failover:(" + uri + "?", ")")));
        stringBuilder.append(jmsOptions.entrySet().stream()
                .map(option -> MessageFormat.format("{0}={1}", option.getKey(), option.getValue()))
                .collect(Collectors.joining("&", "?", "")));
        return stringBuilder.toString();
    }

    /**
     * 创建消费者
     *
     * @param session    session
     * @param connection amqp连接
     * @param queueName  队列名称
     * @return 消费者
     */
    public MessageConsumer newConsumer(Session session, Connection connection, String queueName) throws Exception {
        if (connection == null || !(connection instanceof JmsConnection) || ((JmsConnection) connection).isClosed()) {
            throw new Exception("create consumer failed,the connection is disconnected.");
        }

        return session.createConsumer(new JmsQueue(queueName));
    }

    private final MessageListener messageListener = message -> {
        try {
            //异步处理收到的消息,确保onMessage函数里没有耗时逻辑
            threadPoolTaskExecutor.submit(() -> processMessage(message));
        } catch (Exception e) {
            log.error("submit task occurs exception ", e);
        }
    };

    /**
     * 在这里处理您收到消息后的具体业务逻辑。
     */
    private void processMessage(Message message) {
        String contentStr;
        try {
            contentStr = message.getBody(String.class);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            log.info("receive message,\n topic = {},\n messageId = {},\n content = {}", topic, messageId, contentStr);
        } catch (JMSException e) {
            throw new RuntimeException("服务器错误");
        }

    }

    private final JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            log.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }

        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            log.error("onConnectionFailure, {}", error.getMessage());
        }

        /**
         * 连接中断。
         */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            log.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }

        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            log.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }

        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {
        }

        @Override
        public void onSessionClosed(Session session, Throwable cause) {
        }

        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {
        }

        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {
        }
    };
}

五、接收设备数据

接收到的数据是一个json字符串,我们可以定义一个类来转换接收数据:

json 复制代码
{
  "resource": "device.property",
  "event": "report",
  "event_time": "20250217T083248Z",
  "event_time_ms": "2026-02-17T08:32:48.938Z",
  "request_id": "57f53c8e-9f20-4ce8-aceb-83953391c6c2",
  "notify_data": {
    "header": {
      "app_id": "d51bfac701644b9daa4363cf76c661af",
      "device_id": "67ad95860c504e29c72b2436_watch88",
      "node_id": "watch88",
      "product_id": "67ad95860c504e29c72b2436",
      "gateway_id": "67ad95860c504e29c72b2436_watch88"
    },
    "body": {
      "services": [
        {
          "service_id": "watch_services",
          "properties": {
            "BodyTemp": 1,
            "HeartRate": 87.6276,
            "xueyang": 93.12909,
            "BatteryPercentage": 6.9553375
          },
          "event_time": "20260217T083248Z"
        }
      ]
    }
  }
}

修改AmqpClient类,解析json数据并调用设备数据的业务层,批量添加数据。

1)定义接口:

java 复制代码
/**
 * 批量保存数据
 * @param iotMsgNotifyData
 */
void batchInsertDeviceData(IotMsgNotifyData iotMsgNotifyData);

2)实现方法:

bash 复制代码
@Autowired
private DeviceMapper deviceMapper;

/**
 * 批量保存数据
 * @param iotMsgNotifyData
 */
@Override
public void batchInsertDeviceData(IotMsgNotifyData iotMsgNotifyData) {
    //判断设备是否存在
    String iotId = iotMsgNotifyData.getHeader().getDeviceId();
    Device device = deviceMapper.selectOne(Wrappers.<Device>lambdaQuery().eq(Device::getIotId, iotId));
    if(device == null){
        //日志的记录,方便后期查找问题
        log.error("设备不存在,iotId:{}",iotId);
        return;
    }
    //获取到设备上报的数据,一个设备可以有多个service
    iotMsgNotifyData.getBody().getServices().forEach(s->{

        //所有数据都已经装入到map中
        Map<String, Object> properties = s.getProperties();
        if (ObjectUtil.isEmpty(properties)){
            return;
        }
        //处理上报时间日期
        LocalDateTime eventTime =  LocalDateTimeUtil.parse(s.getEventTime(), "yyyyMMdd'T'HHmmss'Z'");
        //日期时区转换
        LocalDateTime alarmTime = eventTime.atZone(ZoneId.from(ZoneOffset.UTC))
                .withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
                .toLocalDateTime();

        //转入多个设备数据
        List<DeviceData> deviceDataList = new ArrayList<>();

        //遍历map集合,批量新增数据
        properties.forEach((key, value)->{
            DeviceData deviceData = DeviceData.builder()
                    .iotId(iotId)
                    .deviceName(device.getDeviceName())
                    .productKey(device.getProductKey())
                    .productName(device.getProductName())
                    .functionId(key)
                    .accessLocation(device.getRemark())
                    .locationType(device.getLocationType())
                    .physicalLocationType(device.getPhysicalLocationType())
                    .deviceDescription(device.getDeviceDescription())
                    .alarmTime(alarmTime)
                    .dataValue(value + "")
                    .build();
            deviceDataList.add(deviceData);

        });

        //批量保存
        saveBatch(deviceDataList);

    });

}

3)修改AmqpClient类,解析json数据并调用设备数据的业务层,批量添加数据。

相关推荐
一只鹿鹿鹿13 小时前
智慧水利一体化建设方案
大数据·运维·开发语言·数据库·物联网
良许Linux17 小时前
物联网安全和认证技术
物联网·struts·安全
国科安芯1 天前
实战验证:ASM1042S2S CANFD收发器的质子单粒子效应试验与在轨性能
网络·人工智能·单片机·嵌入式硬件·物联网·fpga开发
Zevalin爱灰灰1 天前
基于STM32实现OTA&BootLoader 第二章——外设功能开发
stm32·单片机·物联网·嵌入式
Zevalin爱灰灰1 天前
基于STM32实现OTA&BootLoader 第一章——概述
stm32·单片机·物联网·嵌入式
TDengine (老段)1 天前
TDengine IDMP 高级功能——计量单位
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
会周易的程序员2 天前
cNetgate物联网网关内存数据表和数据视图模块架构
c语言·c++·物联网·架构·lua·iot
The_Uniform_C@t22 天前
论文浅读(第三期)|摘自《UAV Resilience Against Stealthy Attacks》(第一节)
网络·物联网·学习·网络安全
LCG元2 天前
智能农业灌溉:STM32+NB-IoT+土壤湿度传感器,自动控制实战
stm32·物联网·mongodb