华为 IODT 设备接入

70 阅读12分钟

注册使用

image.png

按需付费,其他可调整,点击立即创建即可。

image.png

查看接入地址

image.png

开发过程

  1. 创建产品

image.png 2. 添加服务

image.png

  1. 新增属性

image.png

  1. 设备绑定到产品上

image.png

image.png

设备影子,查看数据

image.png

模拟数据上报

拉取华为开源工具SDK

GitHub 地址

修改图片下的类

  1. 修改接入信息ssl后面的
  2. 修改设备ID和密钥
  3. 修改属性和物理模型id

image.png

image.png

贴一下示例

public static void main(String[] args) throws InterruptedException, IOException {

    // 加载iot平台的ca证书,进行服务端校验
    File tmpCAFile = new File(IOT_ROOT_CA_TMP_PATH);
    try (InputStream resource = CommandSample.class.getClassLoader().getResourceAsStream(IOT_ROOT_CA_RES_PATH)) {
        Files.copy(resource, tmpCAFile.toPath(), REPLACE_EXISTING);
    }

    // 创建设备并初始化. 用户请替换为自己的接入地址。
    IoTDevice device = new IoTDevice("ssl://自己的.com:8883",
        "69ccb5c509b482_watch01", "75fdd81437aca8391", tmpCAFile);
    if (device.init() != 0) {
        return;
    }

    // 接收平台下发的属性读写
//        device.getClient().setPropertyListener(new PropertyListener() {
//            // 处理写属性
//            @Override
//            public void onPropertiesSet(String requestId, List<ServiceProperty> services) {
//                // 遍历service
//                for (ServiceProperty serviceProperty : services) {
//                    log.info("OnPropertiesSet, serviceId is {}", serviceProperty.getServiceId());
//                    // 遍历属性
//                    for (String name : serviceProperty.getProperties().keySet()) {
//                        log.info("property name is {}", name);
//                        log.info("set property value is {}", serviceProperty.getProperties().get(name));
//                    }
//                }
//                // 修改本地的属性值
//                device.getClient().respondPropsSet(requestId, IotResult.SUCCESS);
//            }
//
//            /**
//             * 处理读属性。多数场景下,用户可以直接从平台读设备影子,此接口不用实现。
//             * 但如果需要支持从设备实时读属性,则需要实现此接口。
//             */
//            @Override
//            public void onPropertiesGet(String requestId, String serviceId) {
//                log.info("OnPropertiesGet, the serviceId is {}", serviceId);
//                Map<String, Object> json = new HashMap<>();
//                Random rand = new SecureRandom();
//                json.put("alarm", 1);
//                json.put("temperature", rand.nextFloat() * 100.0f);
//                json.put("humidity", rand.nextFloat() * 100.0f);
//                json.put("smokeConcentration", rand.nextFloat() * 100.0f);
//
//                ServiceProperty serviceProperty = new ServiceProperty();
//                serviceProperty.setProperties(json);
//                serviceProperty.setServiceId("smokeDetector");
//
//                device.getClient().respondPropsGet(requestId, Arrays.asList(serviceProperty));
//            }
//        });

    // 定时上报属性
    while (true) {

        Map<String, Object> json = new HashMap<>();
        Random rand = new SecureRandom();

        // 按照物模型设置属性
        json.put("BodyTemp", 36.5f);
        json.put("xueyang", rand.nextFloat() * 100.0f);
        json.put("HeartRate", rand.nextFloat() * 100.0f);
        json.put("BatteryPercentage", rand.nextFloat() * 100.0f);

        ServiceProperty serviceProperty = new ServiceProperty();
        serviceProperty.setProperties(json);
        serviceProperty.setServiceId("watch-services"); // serviceId要和物模型一致

        device.getClient().reportProperties(Arrays.asList(serviceProperty), new ActionListener() {
            @Override
            public void onSuccess(Object context) {
                log.info("pubMessage success");
            }

            @Override
            public void onFailure(Object context, Throwable var2) {
                log.error("reportProperties failed" + var2.toString());
            }
        });
        Thread.sleep(10000);
    }
}

集成SpringBoot

<dependency>
    <groupId>com.huaweicloud.sdk</groupId>
    <artifactId>huaweicloud-sdk-core</artifactId>
    <version>3.1.92</version>
</dependency>
<dependency>
    <groupId>com.huaweicloud.sdk</groupId>
    <artifactId>huaweicloud-sdk-iotda</artifactId>
    <version>3.1.92</version>
</dependency>

登录华为云,点击我的,点击我的凭证

image.png

点击访问密钥,点击新增

image.png

image.png

复制下载文件的,Access Key Id 和 Secret Access Key(配置文件中的ak和sk)

复制 HTTP 443 的地址 到配置文件的endpoint字段

image.png

去我的api 凭证里面复制ID到配置文件的 projectId 字段

image.png

配置 AMAP 字段

host

image.png

获取凭证

accessKey 和 accessCode

image.png

image.png

配置yml文件

huaweicloud:
  ak: HPUA1VDUYFE9TQNPSWHG
  sk: VbDCnhyvLYw9xzuCDlAmACJOx9cBIpXk4bcSbcGv
  #如果是上海一,请填写"cn-east-3";如果是北京四,请填写"cn-north-4";
  regionId: cn-east-3
  endpoint: ce251462e1.st1.iotda-app.cn-east-3.myhuaweicloud.com
  projectId: 9dda13117bf54ad2962e7017fbb04c73
  #amqp相关配置 下一章课程接收设备数据使用
  host: ce251462e1.st1.iotda-app.cn-east-3.myhuaweicloud.com
  accessKey: fMPMPDCg
  accessCode: XP8qnXDzYCfCNmRC8rukwHsQfPzAT8P9
  queueName: DefaultQueue   #默认无需改动

配置类

/**
 * @author 31094
 */
@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;

}

配置对象

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.kzzyl.framework.config.properties.HuaWeiIotConfigProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 31094
 */
@Configuration
public class IotClientConfig {

    @Bean
    public IoTDAClient iotClient(HuaWeiIotConfigProperties config) {
        // 创建认证
        ICredential auth = new BasicCredentials()
                .withAk(config.getAk())
                .withSk(config.getSk())
                // 标准版/企业版需要使用衍生算法,基础版请删除配置"withDerivedPredicate"
                .withDerivedPredicate(BasicCredentials.DEFAULT_DERIVED_PREDICATE)
                .withProjectId(config.getProjectId());
        // 创建IoTDAClient实例并初始化
        return IoTDAClient.newBuilder()
                .withCredential(auth)
                // 标准版/企业版:需自行创建Region对象,基础版:请使用IoTDARegion的region对象,如"withRegion(IoTDARegion.CN_NORTH_4)"
                // import com.huaweicloud.sdk.core.region.Region;
                .withRegion(new Region(config.getRegionId(), config.getEndpoint()))
                // .withRegion(IoTDARegion.CN_NORTH_4)
                // 配置是否忽略SSL证书校验, 默认不忽略
                // .withHttpConfig(new HttpConfig().withIgnoreSSLVerification(true))
                .build();
    }

}

具体使用

注册设备

// 从华为IOT创建设备
AddDeviceRequest request = new AddDeviceRequest();
AddDevice body = new AddDevice();

// 自定义密钥
String secret = UUID.randomUUID().toString().replaceAll("-", "");

// 配置信息
body.withProductId(device.getProductKey()).withNodeId(device.getNodeId())
        .withDeviceName(device.getDeviceName())
        // 设置密钥
        .withAuthInfo(new AuthInfo().withSecret(secret));
        
// 发送请求
AddDeviceResponse response = ioTDAClient.addDevice(request.withBody(body));

// 获取设备ID
String deviceId = response.getDeviceId();

实现

import com.huaweicloud.sdk.iotda.v5.IoTDAClient;
import com.huaweicloud.sdk.iotda.v5.model.*;

@Service
@AllArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device>
        implements IDeviceService {

    private final IoTDAClient ioTDAClient;

    /**
     * 设备注册
     */
    @Override
    public boolean insertDevice(DeviceDto device) {
        Long count = lambdaQuery().eq(Device::getDeviceName, device.getDeviceName()).count();
        Assert.isTrue(count == 0, "设备名称重复,请重新输入");
        count = lambdaQuery().eq(Device::getNodeId, device.getNodeId()).count();
        Assert.isTrue(count == 0, "设备标识码重复,请重新输入");

        // 如果是随身设备,物理位置类型设置为-1
        if (device.getLocationType() == 0) {
            device.setPhysicalLocationType(-1);
        }

        count = lambdaQuery().eq(Device::getBindingLocation, device.getBindingLocation())
                .eq(!ObjectUtils.isEmpty(device.getPhysicalLocationType()),
                        Device::getPhysicalLocationType, device.getPhysicalLocationType())
                .eq(Device::getProductKey, device.getProductKey()).count();
        Assert.isTrue(count == 0, "该老人/位置已经绑定了该产品,请重新选择");


        // 从华为IOT创建设备
        AddDeviceRequest request = new AddDeviceRequest();
        AddDevice body = new AddDevice();
        String secret = UUID.randomUUID().toString().replaceAll("-", "");
        body.withProductId(device.getProductKey()).withNodeId(device.getNodeId())
                .withDeviceName(device.getDeviceName())
                // 设置密钥
                .withAuthInfo(new AuthInfo().withSecret(secret));
        AddDeviceResponse response = ioTDAClient.addDevice(request.withBody(body));

        // 获取设备ID
        String deviceId = response.getDeviceId();
        Device device1 = BeanUtil.copyProperties(device, Device.class);
        device1.setIotId(deviceId);
        device1.setSecret(secret);
        Assert.isTrue(save(device1), "设备新增失败");
        return true;
    }

    /**
     * 修改设备管理
     */
    @Override
    public boolean updateDevice(DeviceDto device) {
        UpdateDeviceRequest request = new UpdateDeviceRequest().withDeviceId(device.getIotId())
                .withBody(new UpdateDevice().withDeviceName(device.getDeviceName()));
        // 调用华为云修改设备名称
        UpdateDeviceResponse deviceResponse = ioTDAClient.updateDevice(request);

        Device device1 = BeanUtil.toBean(device, Device.class);
        updateById(device1);
        return true;
    }

    /**
     * 删除设备管理
     */
    @Override
    public void deleteDevice(List<Long> ids) {
        // 这里应该是IOT的ID,懒得改了
        ids.forEach(id -> {
            DeleteDeviceRequest request = new DeleteDeviceRequest().withDeviceId(String.valueOf(id));
            ioTDAClient.deleteDevice(request);
        });
        boolean remove = remove(Wrappers.<Device>lambdaQuery());
        if (!remove) {
            throw new RuntimeException("Device 删除失败");
        }
    }
    
    // 获取产品列表
    @Override
    public void syncProductList() {
        ListProductsRequest request = new ListProductsRequest();
        request.setLimit(50);
        ListProductsResponse response = ioTDAClient.listProducts(request);
        // 判断相应是否成功
        if (response.getHttpStatusCode() != 200) {
            throw new BaseException("从IOT平台同步产品列表失败");
        }
        // 存储到Redis
        RedisUtil.setJSONList(CacheConstants.IOT_ALL_PRODUCT_LIST, response.getProducts());
    }


    // 查询详情
    @Override
    public DeviceDetailVo queryDeviceDetail(String id) {
        Device device = lambdaQuery().eq(Device::getIotId, id).one();
        Assert.notNull(device, "设备不存在");
        // 调用华为云查询详情
        ShowDeviceResponse response = ioTDAClient
                .showDevice(new ShowDeviceRequest().withDeviceId(device.getIotId()));
        DeviceDetailVo deviceDetailVo = BeanUtil.copyProperties(device, DeviceDetailVo.class);
        deviceDetailVo.setDeviceStatus(response.getStatus());
        // 时间转换
        LocalDateTime dateTime = CommonUtils.utcToShanghaiTime(response.getActiveTime());
        deviceDetailVo.setActiveTime(dateTime);
        return deviceDetailVo;
    }
    
    // 查询数据
    @Override
    public List<DeviceDataVo> queryServiceProperties(String iotId) {
        ShowDeviceShadowRequest request = new ShowDeviceShadowRequest()
                .withDeviceId(iotId);
        // 调用华为云查询数据
        ShowDeviceShadowResponse response = ioTDAClient.showDeviceShadow(request);
        // 获取数据
        List<DeviceShadowData> shadow = response.getShadow();
        if (CollectionUtils.isEmpty(shadow) || shadow.size() != 1) {
            return List.of();
        }
        DeviceShadowData deviceShadowData = shadow.get(0);
        DeviceShadowProperties reported = deviceShadowData.getReported();

        String eventTime = reported.getEventTime();

        List<DeviceDataVo> dataVos = CommonUtils.mapToObjects(reported.getProperties(),
                "functionId", DeviceDataVo.class);
        dataVos.forEach(deviceDataVo -> deviceDataVo.setEventTime(eventTime));
        return dataVos;
    }
}

数据转发

AMQP 华为官网配置数据转发

image.png

创建规则

image.png

image.png

完成

image.png

Java Amqt 对接

地址

下载所需要的demo

image.png

image.png

打开日志

image.png

在启动之前的模拟属性上报的程序,此时就可以监听到上报的数据。

[JmsSession [ID:d0ce6fc6-320b-4ec0-b9c3-f70a0e41c1ee:1:1] delivery dispatcher] INFO com.iot.amqp.examples.AbstractAmqpExample - receive a message,content={"resource":"device.property","event":"report","event_time":"20260505T153616Z","event_time_ms":"2026-05-05T15:36:16.414Z","request_id":"feb77a07-3d61-4772-9b3e-146195c8f1cc","notify_data":{"header":{"app_id":"f3d7eeaa7d324ec3899e8814b7dff5d6","device_id":"69ccb55618855b39c509b482_watch04","node_id":"watch04","product_id":"69ccb55618855b39c509b482","gateway_id":"69ccb55618855b39c509b482_watch04"},"body":{"services":[{"service_id":"watch-services","properties":{"BodyTemp":1,"HeartRate":80.98859,"xueyang":4.7724667,"BatteryPercentage":25.743711},"event_time":"20260505T153616Z"}]}}}
[pool-1-thread-1] INFO com.iot.amqp.examples.AmqpClientTest - Receive message speed

Java SDK 对接

<!-- amqp 1.0 qpid client -->
<dependency>
    <groupId>org.apache.qpid</groupId>
    <artifactId>qpid-jms-client</artifactId>
    <version>2.10.0</version>
</dependency>

配置参数和配置类

huaweicloud:
  ak: HPUA1LDUY...QNPSWHG
  sk: VbDCnhyvLYw9xzu.....9cBIpXk4bcSbcGv
  #如果是上海一,请填写"cn-east-3";如果是北京四,请填写"cn-north-4";
  regionId: cn-east-3
  endpoint: ce2...1e1.st1.iotda-app.cn-east-3.myhuaweicloud.com
  projectId: 9dda2....2962e7017fbb04c73
  #amqp相关配置 下一章课程接收设备数据使用
  host: ce...1e1.st1.iotda-app.cn-east-3.myhuaweicloud.com
  accessKey: fMPMP...
  accessCode: XP8qnXD...8rukwHsQfPzAT8P9
  queueName: DefaultQueue   #默认无需改动
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * @author 31094
 */
@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;

}

具体实现

具体关注:processMessage 方法用于接收数据。

import cn.hutool.core.text.CharSequenceUtil;
import com.kzzyl.framework.config.properties.HuaWeiIotConfigProperties;
import jakarta.jms.*;
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 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;

/**
 * @author itcast
 */
@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) {
            log.info("找不到地址");
        }
    }

    @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());
                }
            }
        }
        return uriOptions.entrySet().stream()
                .map(option -> MessageFormat.format("{0}={1}", option.getKey(), option.getValue()))
                .collect(Collectors.joining("&", "failover:(" + uri + "?", ")")) +
                jmsOptions.entrySet().stream()
                        .map(option -> MessageFormat.format("{0}={1}", option.getKey(), option.getValue()))
                        .collect(Collectors.joining("&", "?", ""));
    }

    /**
     * 创建消费者
     *
     * @param session    session
     * @param connection amqp连接
     * @param queueName  队列名称
     * @return 消费者
     */
    public MessageConsumer newConsumer(Session session, Connection connection, String queueName) throws Exception {
        if (!(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) {
        }
    };
}

多线程改造

在 framework.config.ThreadPoolConfig 模块下有这么个类

线程池核心参数和原理

  • corePoolSize 核心线程数目
    • 到底多少合适?
      • 对于IO密集型的项目,一般设置核心线程数为:CPU核数 * 2
      • 对于计算密集型的项目,一般设置核心线程数为: CPU核数 + 1
  • maximumPoolSize 最大线程数目 = (核心线程+临时线程的最大数目)
  • keepAliveTime 生存时间 - 临时线程的生存时间,生存时间内没有新任务,此线程资源会释放
  • unit 时间单位 - 临时线程的生存时间单位,如秒、毫秒等
  • workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建临时线程执行任务
  • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
    • AbortPolicy:直接抛出异常,默认策略;
    • CallerRunsPolicy:用调用者所在的线程来执行任务;
    • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
    • DiscardPolicy:直接丢弃任务;

改造,CPU数 * 2

@Configuration
public class ThreadPoolConfig
{
    // 核心线程池大小
    private int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;

image.png

SpringBoot3 必须使用@RequestParam 注解标注参数问题

在聚合pom里

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <!-- 解决传参校验报错,需要使用maven打包 -->
                <parameters>true</parameters>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <encoding>${project.build.sourceEncoding}</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>