feat:【IOT】添加 IoT 脚本模块及相关配置,删除不再使用的插件模块

This commit is contained in:
安浩浩
2025-05-22 22:23:08 +08:00
parent f124584d06
commit ab4b148df3
92 changed files with 654 additions and 5070 deletions

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.iot.protocol.config;
import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser;
import cn.iocoder.yudao.module.iot.protocol.message.impl.IotAlinkMessageParser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* IoT 协议模块自动配置类
*
* @author haohao
*/
@Configuration(proxyBeanMethods = false)
public class IotProtocolAutoConfiguration {
/**
* 注册 Alink 协议消息解析器
*
* @return Alink 协议消息解析器
*/
@Bean
public IotMessageParser iotAlinkMessageParser() {
return new IotAlinkMessageParser();
}
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.iot.protocol.constants;
/**
* IoT 设备主题常量类
* <p>
* 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
*
* @author haohao
*/
public class IotTopicConstants {
/**
* 系统主题前缀
*/
public static final String SYS_TOPIC_PREFIX = "/sys/";
/**
* 服务调用主题前缀
*/
public static final String SERVICE_TOPIC_PREFIX = "/thing/service/";
/**
* 设备属性设置主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/service/property/set
* 响应Topic/sys/${productKey}/${deviceName}/thing/service/property/set_reply
*/
public static final String PROPERTY_SET_TOPIC = "/thing/service/property/set";
/**
* 设备属性获取主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/service/property/get
* 响应Topic/sys/${productKey}/${deviceName}/thing/service/property/get_reply
*/
public static final String PROPERTY_GET_TOPIC = "/thing/service/property/get";
/**
* 设备配置设置主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/service/config/set
* 响应Topic/sys/${productKey}/${deviceName}/thing/service/config/set_reply
*/
public static final String CONFIG_SET_TOPIC = "/thing/service/config/set";
/**
* 设备OTA升级主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/service/ota/upgrade
* 响应Topic/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply
*/
public static final String OTA_UPGRADE_TOPIC = "/thing/service/ota/upgrade";
/**
* 设备属性上报主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/event/property/post
* 响应Topic/sys/${productKey}/${deviceName}/thing/event/property/post_reply
*/
public static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
/**
* 设备事件上报主题前缀
*/
public static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
/**
* 设备事件上报主题后缀
*/
public static final String EVENT_POST_TOPIC_SUFFIX = "/post";
/**
* 响应主题后缀
*/
public static final String REPLY_SUFFIX = "_reply";
}

View File

@@ -0,0 +1,154 @@
package cn.iocoder.yudao.module.iot.protocol.message;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
/**
* IoT Alink 消息模型
* <p>
* 基于阿里云 Alink 协议规范实现的标准消息格式
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云物联网 —— Alink 协议</a>
*
* @author haohao
*/
@Data
@Builder
public class IotAlinkMessage {
/**
* 消息 ID
*/
private String id;
/**
* 协议版本
*/
@Builder.Default
private String version = "1.0";
/**
* 消息方法
*/
private String method;
/**
* 消息参数
*/
private Map<String, Object> params;
/**
* 转换为 JSONObject
*
* @return JSONObject 对象
*/
public JSONObject toJsonObject() {
JSONObject json = new JSONObject();
json.set("id", id);
json.set("version", version);
json.set("method", method);
json.set("params", params != null ? params : new JSONObject());
return json;
}
/**
* 转换为 JSON 字符串
*
* @return JSON 字符串
*/
public String toJsonString() {
return toJsonObject().toString();
}
/**
* 创建设备服务调用消息
*
* @param requestId 请求 ID为空时自动生成
* @param serviceIdentifier 服务标识符
* @param params 服务参数
* @return Alink 消息对象
*/
public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier,
Map<String, Object> params) {
return IotAlinkMessage.builder()
.id(requestId != null ? requestId : generateRequestId())
.method("thing.service." + serviceIdentifier)
.params(params)
.build();
}
/**
* 创建设备属性设置消息
*
* @param requestId 请求 ID为空时自动生成
* @param properties 设备属性
* @return Alink 消息对象
*/
public static IotAlinkMessage createPropertySetMessage(String requestId, Map<String, Object> properties) {
return IotAlinkMessage.builder()
.id(requestId != null ? requestId : generateRequestId())
.method("thing.service.property.set")
.params(properties)
.build();
}
/**
* 创建设备属性获取消息
*
* @param requestId 请求 ID为空时自动生成
* @param identifiers 要获取的属性标识符列表
* @return Alink 消息对象
*/
public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) {
JSONObject params = new JSONObject();
params.set("identifiers", identifiers);
return IotAlinkMessage.builder()
.id(requestId != null ? requestId : generateRequestId())
.method("thing.service.property.get")
.params(params)
.build();
}
/**
* 创建设备配置设置消息
*
* @param requestId 请求 ID为空时自动生成
* @param configs 设备配置
* @return Alink 消息对象
*/
public static IotAlinkMessage createConfigSetMessage(String requestId, Map<String, Object> configs) {
return IotAlinkMessage.builder()
.id(requestId != null ? requestId : generateRequestId())
.method("thing.service.config.set")
.params(configs)
.build();
}
/**
* 创建设备 OTA 升级消息
*
* @param requestId 请求 ID为空时自动生成
* @param otaInfo OTA 升级信息
* @return Alink 消息对象
*/
public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) {
return IotAlinkMessage.builder()
.id(requestId != null ? requestId : generateRequestId())
.method("thing.service.ota.upgrade")
.params(otaInfo)
.build();
}
/**
* 生成请求 ID
*
* @return 请求 ID
*/
public static String generateRequestId() {
return IdUtil.fastSimpleUUID();
}
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.iot.protocol.message;
/**
* IoT 消息解析器接口
* <p>
* 用于解析不同协议的消息内容
*
* @author haohao
*/
public interface IotMessageParser {
/**
* 解析消息
*
* @param topic 主题
* @param payload 消息负载
* @return 解析后的标准消息,如果解析失败返回 null
*/
IotAlinkMessage parse(String topic, byte[] payload);
/**
* 格式化响应消息
*
* @param response 标准响应
* @return 格式化后的响应字节数组
*/
byte[] formatResponse(IotStandardResponse response);
/**
* 检查是否能够处理指定主题的消息
*
* @param topic 主题
* @return 如果能处理返回 true否则返回 false
*/
boolean canHandle(String topic);
}

View File

@@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.iot.protocol.message;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* IoT 标准协议响应实体类
* <p>
* 用于统一 MQTT 和 HTTP 的响应格式
*
* @author haohao
*/
@Data
@Accessors(chain = true)
public class IotStandardResponse {
/**
* 消息 ID
*/
private String id;
/**
* 状态码
*/
private Integer code;
/**
* 响应数据
*/
private Object data;
/**
* 响应消息
*/
private String message;
/**
* 方法名
*/
private String method;
/**
* 协议版本
*/
private String version;
/**
* 创建成功响应
*
* @param id 消息 ID
* @param method 方法名
* @return 成功响应
*/
public static IotStandardResponse success(String id, String method) {
return success(id, method, null);
}
/**
* 创建成功响应
*
* @param id 消息 ID
* @param method 方法名
* @param data 响应数据
* @return 成功响应
*/
public static IotStandardResponse success(String id, String method, Object data) {
return new IotStandardResponse()
.setId(id)
.setCode(200)
.setData(data)
.setMessage("success")
.setMethod(method)
.setVersion("1.0");
}
/**
* 创建错误响应
*
* @param id 消息 ID
* @param method 方法名
* @param code 错误码
* @param message 错误消息
* @return 错误响应
*/
public static IotStandardResponse error(String id, String method, Integer code, String message) {
return new IotStandardResponse()
.setId(id)
.setCode(code)
.setData(null)
.setMessage(StrUtil.blankToDefault(message, "error"))
.setMethod(method)
.setVersion("1.0");
}
}

View File

@@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.iot.protocol.message.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage;
import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser;
import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse;
import cn.iocoder.yudao.module.iot.protocol.util.IotTopicUtils;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* IoT Alink 协议消息解析器实现
* <p>
* 基于阿里云 Alink 协议规范实现的消息解析器
*
* @author haohao
*/
@Slf4j
public class IotAlinkMessageParser implements IotMessageParser {
@Override
public IotAlinkMessage parse(String topic, byte[] payload) {
if (payload == null || payload.length == 0) {
log.warn("[Alink] 收到空消息内容, topic={}", topic);
return null;
}
try {
String message = new String(payload, StandardCharsets.UTF_8);
if (!JSONUtil.isTypeJSON(message)) {
log.warn("[Alink] 收到非JSON格式消息, topic={}, message={}", topic, message);
return null;
}
JSONObject json = JSONUtil.parseObj(message);
String id = json.getStr("id");
String method = json.getStr("method");
if (StrUtil.isBlank(method)) {
// 尝试从 topic 中解析方法
method = IotTopicUtils.parseMethodFromTopic(topic);
if (StrUtil.isBlank(method)) {
log.warn("[Alink] 无法确定消息方法, topic={}, message={}", topic, message);
return null;
}
}
Map<String, Object> params = (Map<String, Object>) json.getObj("params", Map.class);
return IotAlinkMessage.builder()
.id(id)
.method(method)
.version(json.getStr("version", "1.0"))
.params(params)
.build();
} catch (Exception e) {
log.error("[Alink] 解析消息失败, topic={}", topic, e);
return null;
}
}
@Override
public byte[] formatResponse(IotStandardResponse response) {
try {
String json = JsonUtils.toJsonString(response);
return json.getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("[Alink] 格式化响应失败", e);
return new byte[0];
}
}
@Override
public boolean canHandle(String topic) {
// Alink 协议处理所有系统主题
return topic != null && topic.startsWith("/sys/");
}
}

View File

@@ -0,0 +1,184 @@
package cn.iocoder.yudao.module.iot.protocol.util;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants;
/**
* IoT 主题工具类
* <p>
* 用于构建和解析设备主题
*
* @author haohao
*/
public class IotTopicUtils {
/**
* 构建设备服务调用主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @param serviceIdentifier 服务标识符
* @return 完整的主题路径
*/
public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
return buildDeviceBaseTopic(productKey, deviceName) +
IotTopicConstants.SERVICE_TOPIC_PREFIX + serviceIdentifier;
}
/**
* 构建设备属性设置主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 完整的主题路径
*/
public static String buildPropertySetTopic(String productKey, String deviceName) {
return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_SET_TOPIC;
}
/**
* 构建设备属性获取主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 完整的主题路径
*/
public static String buildPropertyGetTopic(String productKey, String deviceName) {
return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_GET_TOPIC;
}
/**
* 构建设备配置设置主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 完整的主题路径
*/
public static String buildConfigSetTopic(String productKey, String deviceName) {
return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.CONFIG_SET_TOPIC;
}
/**
* 构建设备 OTA 升级主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 完整的主题路径
*/
public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.OTA_UPGRADE_TOPIC;
}
/**
* 构建设备属性上报主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 完整的主题路径
*/
public static String buildPropertyPostTopic(String productKey, String deviceName) {
return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_POST_TOPIC;
}
/**
* 构建设备事件上报主题
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @param eventIdentifier 事件标识符
* @return 完整的主题路径
*/
public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
return buildDeviceBaseTopic(productKey, deviceName) +
IotTopicConstants.EVENT_POST_TOPIC_PREFIX + eventIdentifier + IotTopicConstants.EVENT_POST_TOPIC_SUFFIX;
}
/**
* 获取响应主题
*
* @param requestTopic 请求主题
* @return 响应主题
*/
public static String getReplyTopic(String requestTopic) {
return requestTopic + IotTopicConstants.REPLY_SUFFIX;
}
/**
* 构建设备基础主题
* 格式: /sys/${productKey}/${deviceName}
*
* @param productKey 产品Key
* @param deviceName 设备名称
* @return 设备基础主题
*/
public static String buildDeviceBaseTopic(String productKey, String deviceName) {
return IotTopicConstants.SYS_TOPIC_PREFIX + productKey + "/" + deviceName;
}
/**
* 从主题中解析产品Key
* 格式: /sys/${productKey}/${deviceName}/...
*
* @param topic 主题
* @return 产品Key如果无法解析则返回null
*/
public static String parseProductKeyFromTopic(String topic) {
if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
return null;
}
String[] parts = topic.split("/");
if (parts.length < 4) {
return null;
}
return parts[2];
}
/**
* 从主题中解析设备名称
* 格式: /sys/${productKey}/${deviceName}/...
*
* @param topic 主题
* @return 设备名称如果无法解析则返回null
*/
public static String parseDeviceNameFromTopic(String topic) {
if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
return null;
}
String[] parts = topic.split("/");
if (parts.length < 4) {
return null;
}
return parts[3];
}
/**
* 从主题中解析方法名
* 例如:从 /sys/pk/dn/thing/service/property/set 解析出 property.set
*
* @param topic 主题
* @return 方法名如果无法解析则返回null
*/
public static String parseMethodFromTopic(String topic) {
if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
return null;
}
// 服务调用主题
if (topic.contains("/thing/service/")) {
String servicePart = topic.substring(topic.indexOf("/thing/service/") + "/thing/service/".length());
return servicePart.replace("/", ".");
}
// 事件上报主题
if (topic.contains("/thing/event/")) {
String eventPart = topic.substring(topic.indexOf("/thing/event/") + "/thing/event/".length());
return "event." + eventPart.replace("/", ".");
}
return null;
}
}

View File

@@ -0,0 +1 @@
cn.iocoder.yudao.module.iot.protocol.config.IotProtocolAutoConfiguration