reactor:【IoT 物联网】将 http component 合并到 gateway 里
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class IotGatewayServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(IotGatewayServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
/**
|
||||
* 提供设备接入的各种数据(请求、响应)的编解码
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.gateway.codec;
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.config;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(IotGatewayProperties.class)
|
||||
@Slf4j
|
||||
public class IotGatewayConfiguration {
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议配置类
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.http", name = "enabled", havingValue = "true")
|
||||
@Slf4j
|
||||
public static class HttpProtocolConfiguration {
|
||||
|
||||
@Bean
|
||||
public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties,
|
||||
IotDeviceMessageProducer deviceMessageProducer) {
|
||||
return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp(), deviceMessageProducer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol,
|
||||
IotMessageBus messageBus) {
|
||||
return new IotHttpDownstreamSubscriber(httpUpstreamProtocol,messageBus);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "yudao.iot.gateway")
|
||||
@Validated
|
||||
@Data
|
||||
public class IotGatewayProperties {
|
||||
|
||||
/**
|
||||
* 设备 RPC 服务配置
|
||||
*/
|
||||
private RpcProperties rpc;
|
||||
|
||||
/**
|
||||
* 协议配置
|
||||
*/
|
||||
private ProtocolProperties protocol;
|
||||
|
||||
@Data
|
||||
public static class RpcProperties {
|
||||
|
||||
/**
|
||||
* 主程序 API 地址
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 连接超时时间
|
||||
*/
|
||||
private String connectTimeout;
|
||||
/**
|
||||
* 读取超时时间
|
||||
*/
|
||||
private String readTimeout;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ProtocolProperties {
|
||||
|
||||
/**
|
||||
* HTTP 组件配置
|
||||
*/
|
||||
private HttpProperties http;
|
||||
|
||||
/**
|
||||
* EMQX 组件配置
|
||||
*/
|
||||
private EmqxProperties emqx;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class HttpProperties {
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 服务端口
|
||||
*/
|
||||
private Integer serverPort;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class EmqxProperties {
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* MQTT 服务器地址
|
||||
*/
|
||||
private String mqttHost;
|
||||
/**
|
||||
* MQTT 服务器端口
|
||||
*/
|
||||
private Integer mqttPort;
|
||||
/**
|
||||
* MQTT 用户名
|
||||
*/
|
||||
private String mqttUsername;
|
||||
/**
|
||||
* MQTT 密码
|
||||
*/
|
||||
private String mqttPassword;
|
||||
/**
|
||||
* MQTT 是否开启 SSL
|
||||
*/
|
||||
private Boolean mqttSsl;
|
||||
/**
|
||||
* MQTT 主题
|
||||
*/
|
||||
private List<String> mqttTopics;
|
||||
/**
|
||||
* 认证端口
|
||||
*/
|
||||
private Integer authPort;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备主题枚举
|
||||
* <p>
|
||||
* 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotDeviceTopicEnum {
|
||||
|
||||
// TODO @haohao:SYS_TOPIC_PREFIX、SERVICE_TOPIC_PREFIX、REPLY_SUFFIX 类似这种,要不搞成这个里面的静态变量?不是枚举值
|
||||
/**
|
||||
* 系统主题前缀
|
||||
*/
|
||||
SYS_TOPIC_PREFIX("/sys/", "系统主题前缀"),
|
||||
|
||||
/**
|
||||
* 服务调用主题前缀
|
||||
*/
|
||||
SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"),
|
||||
|
||||
// TODO @haohao:注释时,中英文之间,有个空格;
|
||||
/**
|
||||
* 设备属性设置主题
|
||||
* 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
|
||||
* 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
|
||||
*/
|
||||
PROPERTY_SET_TOPIC("/thing/service/property/set", "设备属性设置主题"),
|
||||
|
||||
/**
|
||||
* 设备属性获取主题
|
||||
* 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/property/get
|
||||
* 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply
|
||||
*/
|
||||
PROPERTY_GET_TOPIC("/thing/service/property/get", "设备属性获取主题"),
|
||||
|
||||
/**
|
||||
* 设备配置设置主题
|
||||
* 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/config/set
|
||||
* 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply
|
||||
*/
|
||||
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
|
||||
*/
|
||||
OTA_UPGRADE_TOPIC("/thing/service/ota/upgrade", "设备OTA升级主题"),
|
||||
|
||||
/**
|
||||
* 设备属性上报主题
|
||||
* 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
|
||||
* 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
|
||||
*/
|
||||
PROPERTY_POST_TOPIC("/thing/event/property/post", "设备属性上报主题"),
|
||||
|
||||
/**
|
||||
* 设备事件上报主题前缀
|
||||
*/
|
||||
EVENT_POST_TOPIC_PREFIX("/thing/event/", "设备事件上报主题前缀"),
|
||||
|
||||
/**
|
||||
* 设备事件上报主题后缀
|
||||
*/
|
||||
EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀"),
|
||||
|
||||
/**
|
||||
* 响应主题后缀
|
||||
*/
|
||||
REPLY_SUFFIX("_reply", "响应主题后缀");
|
||||
|
||||
private final String topic;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 构建设备服务调用主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @param serviceIdentifier 服务标识符
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
|
||||
// TODO @haohao:貌似 SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName 是统一的;
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
|
||||
SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备属性设置主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildPropertySetTopic(String productKey, String deviceName) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_SET_TOPIC.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备属性获取主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildPropertyGetTopic(String productKey, String deviceName) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_GET_TOPIC.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备配置设置主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildConfigSetTopic(String productKey, String deviceName) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + CONFIG_SET_TOPIC.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备 OTA 升级主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + OTA_UPGRADE_TOPIC.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备属性上报主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildPropertyPostTopic(String productKey, String deviceName) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_POST_TOPIC.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建设备事件上报主题
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @param eventIdentifier 事件标识符
|
||||
* @return 完整的主题路径
|
||||
*/
|
||||
public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
|
||||
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
|
||||
EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应主题
|
||||
*
|
||||
* @param requestTopic 请求主题
|
||||
* @return 响应主题
|
||||
*/
|
||||
public static String getReplyTopic(String requestTopic) {
|
||||
return requestTopic + REPLY_SUFFIX.getTopic();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway;
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 订阅者:接收下行给设备的消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class IotHttpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
|
||||
private final IotHttpUpstreamProtocol protocol;
|
||||
|
||||
private final IotMessageBus messageBus;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
messageBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTopic() {
|
||||
return IotDeviceMessage.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
// 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
|
||||
return getTopic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
log.error("[onMessage][IoT 网关 HTTP 协议不支持下行消息,忽略消息:{}]", message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotCoreUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议:接收设备上行消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class IotHttpUpstreamProtocol extends AbstractVerticle {
|
||||
|
||||
private final IotGatewayProperties.HttpProperties httpProperties;
|
||||
|
||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
private HttpServer httpServer;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
// 创建路由
|
||||
Vertx vertx = Vertx.vertx();
|
||||
Router router = Router.router(vertx);
|
||||
router.route().handler(BodyHandler.create());
|
||||
|
||||
// 创建处理器,添加路由处理器
|
||||
IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(
|
||||
this, deviceMessageProducer);
|
||||
router.post(IotHttpUpstreamHandler.PROPERTY_PATH).handler(upstreamHandler);
|
||||
router.post(IotHttpUpstreamHandler.EVENT_PATH).handler(upstreamHandler);
|
||||
|
||||
// 启动 HTTP 服务器
|
||||
try {
|
||||
httpServer = vertx.createHttpServer()
|
||||
.requestHandler(router)
|
||||
.listen(httpProperties.getServerPort())
|
||||
.result();
|
||||
log.info("[start][IoT 网关 HTTP 协议启动成功,端口:{}]", httpProperties.getServerPort());
|
||||
} catch (Exception e) {
|
||||
log.error("[start][IoT 网关 HTTP 协议启动失败]", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
if (httpServer != null) {
|
||||
try {
|
||||
httpServer.close().result();
|
||||
log.info("[stop][IoT 网关 HTTP 协议已停止]");
|
||||
} catch (Exception e) {
|
||||
log.error("[stop][IoT 网关 HTTP 协议停止失败]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerId() {
|
||||
return IotCoreUtils.generateServerId(httpProperties.getServerPort());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http;
|
||||
@@ -0,0 +1,322 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||
import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议的处理器
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class IotHttpUpstreamHandler implements Handler<RoutingContext> {
|
||||
|
||||
// TODO @haohao:你说,咱要不要把 "/sys/:productKey/:deviceName"
|
||||
// + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic(),也抽到 IotDeviceTopicEnum 的 build 这种?尽量都收敛掉?
|
||||
/**
|
||||
* 属性上报路径
|
||||
*/
|
||||
public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName"
|
||||
+ IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic();
|
||||
|
||||
/**
|
||||
* 事件上报路径
|
||||
*/
|
||||
public static final String EVENT_PATH = "/sys/:productKey/:deviceName"
|
||||
+ IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic() + ":identifier"
|
||||
+ IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic();
|
||||
|
||||
/**
|
||||
* 事件上报方法前缀
|
||||
*/
|
||||
private static final String EVENT_METHOD_PREFIX = "thing.event.";
|
||||
|
||||
/**
|
||||
* 事件上报方法后缀
|
||||
*/
|
||||
private static final String EVENT_METHOD_SUFFIX = ".post";
|
||||
|
||||
private final IotHttpUpstreamProtocol protocol;
|
||||
// /**
|
||||
// * 设备上行 API
|
||||
// */
|
||||
// private final IotDeviceUpstreamApi deviceUpstreamApi;
|
||||
/**
|
||||
* 设备消息生产者
|
||||
*/
|
||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext routingContext) {
|
||||
String path = routingContext.request().path();
|
||||
String requestId = IdUtil.fastSimpleUUID();
|
||||
|
||||
try {
|
||||
// 1. 解析通用参数
|
||||
Map<String, String> params = parseCommonParams(routingContext, requestId);
|
||||
String productKey = params.get("productKey");
|
||||
String deviceName = params.get("deviceName");
|
||||
JsonObject body = routingContext.body().asJsonObject();
|
||||
requestId = params.get("requestId");
|
||||
|
||||
// 2. 根据路径模式处理不同类型的请求
|
||||
if (isPropertyPostPath(path)) {
|
||||
// 处理属性上报
|
||||
handlePropertyPost(routingContext, productKey, deviceName, requestId, body);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEventPostPath(path)) {
|
||||
// 处理事件上报
|
||||
String identifier = routingContext.pathParam("identifier");
|
||||
handleEventPost(routingContext, productKey, deviceName, identifier, requestId, body);
|
||||
return;
|
||||
}
|
||||
|
||||
// 不支持的请求路径
|
||||
sendErrorResponse(routingContext, requestId, "unknown", BAD_REQUEST.getCode(), "不支持的请求路径");
|
||||
} catch (Exception e) {
|
||||
log.error("[handle][处理上行请求异常] path={}", path, e);
|
||||
String method = determineMethodFromPath(path, routingContext);
|
||||
sendErrorResponse(routingContext, requestId, method, INTERNAL_SERVER_ERROR.getCode(),
|
||||
INTERNAL_SERVER_ERROR.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析通用参数
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param defaultRequestId 默认请求 ID
|
||||
* @return 参数映射
|
||||
*/
|
||||
private Map<String, String> parseCommonParams(RoutingContext routingContext, String defaultRequestId) {
|
||||
Map<String, String> params = MapUtil.newHashMap();
|
||||
params.put("productKey", routingContext.pathParam("productKey"));
|
||||
params.put("deviceName", routingContext.pathParam("deviceName"));
|
||||
|
||||
JsonObject body = routingContext.body().asJsonObject();
|
||||
String requestId = ObjUtil.defaultIfNull(body.getString("id"), defaultRequestId);
|
||||
params.put("requestId", requestId);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是属性上报路径
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 是否是属性上报路径
|
||||
*/
|
||||
private boolean isPropertyPostPath(String path) {
|
||||
return StrUtil.endWith(path, IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是事件上报路径
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 是否是事件上报路径
|
||||
*/
|
||||
private boolean isEventPostPath(String path) {
|
||||
return StrUtil.contains(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic())
|
||||
&& StrUtil.endWith(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理属性上报请求
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @param requestId 请求 ID
|
||||
* @param body 请求体
|
||||
*/
|
||||
private void handlePropertyPost(RoutingContext routingContext, String productKey, String deviceName,
|
||||
String requestId, JsonObject body) {
|
||||
// 1.1 构建设备消息
|
||||
String deviceKey = "xxx"; // TODO @芋艿:待支持
|
||||
Long tenantId = 1L; // TODO @芋艿:待支持
|
||||
IotDeviceMessage message = IotDeviceMessage.of(productKey, deviceName, deviceKey,
|
||||
requestId, LocalDateTime.now(),
|
||||
protocol.getServerId(), tenantId)
|
||||
.ofPropertyReport(parsePropertiesFromBody(body));
|
||||
// 1.2 发送消息
|
||||
deviceMessageProducer.sendDeviceMessage(message);
|
||||
|
||||
// 2. 返回响应
|
||||
sendResponse(routingContext, requestId, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理事件上报请求
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @param identifier 事件标识符
|
||||
* @param requestId 请求 ID
|
||||
* @param body 请求体
|
||||
*/
|
||||
private void handleEventPost(RoutingContext routingContext, String productKey, String deviceName,
|
||||
String identifier, String requestId, JsonObject body) {
|
||||
// // 处理事件上报
|
||||
// IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,
|
||||
// requestId, body);
|
||||
//
|
||||
// // 事件上报
|
||||
// CommonResult<Boolean> result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
|
||||
// String method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
|
||||
//
|
||||
// // 返回响应
|
||||
// sendResponse(routingContext, requestId, method, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送响应
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param requestId 请求 ID
|
||||
* @param method 方法名
|
||||
* @param result 结果
|
||||
*/
|
||||
private void sendResponse(RoutingContext routingContext, String requestId, String method,
|
||||
CommonResult<Boolean> result) {
|
||||
// // TODO @芋艿:后续再优化
|
||||
// IotStandardResponse response;
|
||||
// if (result == null ) {
|
||||
// response = IotStandardResponse.success(requestId, method, null);
|
||||
// } else if (result.isSuccess()) {
|
||||
// response = IotStandardResponse.success(requestId, method, result.getData());
|
||||
// } else {
|
||||
// response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());
|
||||
// }
|
||||
// IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误响应
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param requestId 请求 ID
|
||||
* @param method 方法名
|
||||
* @param code 错误代码
|
||||
* @param message 错误消息
|
||||
*/
|
||||
private void sendErrorResponse(RoutingContext routingContext, String requestId, String method, Integer code,
|
||||
String message) {
|
||||
// IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
|
||||
// IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从路径确定方法名
|
||||
*
|
||||
* @param path 路径
|
||||
* @param routingContext 路由上下文
|
||||
* @return 方法名
|
||||
*/
|
||||
private String determineMethodFromPath(String path, RoutingContext routingContext) {
|
||||
if (StrUtil.contains(path, "/property/")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return EVENT_METHOD_PREFIX
|
||||
+ (routingContext.pathParams().containsKey("identifier")
|
||||
? routingContext.pathParam("identifier")
|
||||
: "unknown")
|
||||
+
|
||||
EVENT_METHOD_SUFFIX;
|
||||
}
|
||||
|
||||
// TODO @芋艿:这块在看看
|
||||
/**
|
||||
* 从请求体解析属性
|
||||
*
|
||||
* @param body 请求体
|
||||
* @return 属性映射
|
||||
*/
|
||||
private Map<String, Object> parsePropertiesFromBody(JsonObject body) {
|
||||
Map<String, Object> properties = MapUtil.newHashMap();
|
||||
JsonObject params = body.getJsonObject("params");
|
||||
if (CollUtil.isEmpty(params)) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
// 将标准格式的 params 转换为平台需要的 properties 格式
|
||||
for (String key : params.fieldNames()) {
|
||||
Object valueObj = params.getValue(key);
|
||||
// 如果是复杂结构(包含 value 和 time)
|
||||
if (valueObj instanceof JsonObject) {
|
||||
JsonObject valueJson = (JsonObject) valueObj;
|
||||
properties.put(key, valueJson.containsKey("value") ? valueJson.getValue("value") : valueObj);
|
||||
} else {
|
||||
properties.put(key, valueObj);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 解析事件上报请求
|
||||
// *
|
||||
// * @param productKey 产品 Key
|
||||
// * @param deviceName 设备名称
|
||||
// * @param identifier 事件标识符
|
||||
// * @param requestId 请求 ID
|
||||
// * @param body 请求体
|
||||
// * @return 事件上报请求 DTO
|
||||
// */
|
||||
// private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,
|
||||
// String requestId, JsonObject body) {
|
||||
// // 解析参数
|
||||
// Map<String, Object> params = parseParamsFromBody(body);
|
||||
//
|
||||
// // 构建事件上报请求 DTO
|
||||
// return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
|
||||
// .setRequestId(requestId)
|
||||
// .setProcessId(IotNetComponentCommonUtils.getProcessId())
|
||||
// .setReportTime(LocalDateTime.now())
|
||||
// .setProductKey(productKey)
|
||||
// .setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 从请求体解析参数
|
||||
*
|
||||
* @param body 请求体
|
||||
* @return 参数映射
|
||||
*/
|
||||
private Map<String, Object> parseParamsFromBody(JsonObject body) {
|
||||
Map<String, Object> params = MapUtil.newHashMap();
|
||||
JsonObject paramsJson = body.getJsonObject("params");
|
||||
if (CollUtil.isEmpty(paramsJson)) {
|
||||
return params;
|
||||
}
|
||||
|
||||
for (String key : paramsJson.fieldNames()) {
|
||||
params.put(key, paramsJson.getValue(key));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* TODO 占位
|
||||
* 提供设备接入的各种协议的实现
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol;
|
||||
@@ -0,0 +1,57 @@
|
||||
spring:
|
||||
application:
|
||||
name: iot-gateway-server
|
||||
|
||||
--- #################### 消息队列相关 ####################
|
||||
|
||||
# rocketmq 配置项,对应 RocketMQProperties 配置类
|
||||
rocketmq:
|
||||
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
|
||||
# Producer 配置项
|
||||
producer:
|
||||
group: ${spring.application.name}_PRODUCER # 生产者分组
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
yudao:
|
||||
iot:
|
||||
# 网关配置
|
||||
gateway:
|
||||
# 设备 RPC 配置
|
||||
rpc:
|
||||
url: http://127.0.0.1:48080 # 主程序 API 地址
|
||||
connect-timeout: 30s
|
||||
read-timeout: 30s
|
||||
|
||||
# 协议配置
|
||||
protocol:
|
||||
# ====================================
|
||||
# 针对引入的 HTTP 组件的配置
|
||||
# ====================================
|
||||
http:
|
||||
enabled: true
|
||||
server-port: 8092
|
||||
# ====================================
|
||||
# 针对引入的 EMQX 组件的配置
|
||||
# ====================================
|
||||
emqx:
|
||||
enabled: true
|
||||
mqtt-host: 127.0.0.1
|
||||
mqtt-port: 1883
|
||||
mqtt-username: admin
|
||||
mqtt-password: admin123
|
||||
mqtt-ssl: false
|
||||
mqtt-topics:
|
||||
- "/sys/#"
|
||||
auth-port: 8101
|
||||
|
||||
# 消息总线配置
|
||||
message-bus:
|
||||
type: rocketmq # 消息总线的类型
|
||||
|
||||
# 日志配置
|
||||
# TODO 芋艿:是不是可以删除
|
||||
logging:
|
||||
level:
|
||||
cn.iocoder.yudao: INFO
|
||||
root: INFO
|
||||
Reference in New Issue
Block a user