feat:【IoT 物联网】增加网关 HTTP 协议的鉴权,基于 JWT 轻量级
This commit is contained in:
parent
1498389d26
commit
643cc4cfd2
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.date;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
|
@ -312,4 +313,16 @@ public class LocalDateTimeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
|
||||
*
|
||||
* @param sourceDateTime 需要转换的本地日期时间,不能为空
|
||||
* @return 自 1970-01-01T00:00:00Z 起的秒数(epoch second)
|
||||
* @throws NullPointerException 如果 {@code sourceDateTime} 为 {@code null}
|
||||
* @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
|
||||
*/
|
||||
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
|
||||
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
|
||||
import cn.iocoder.yudao.module.iot.enums.ApiConstants;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* 设备数据 Upstream 上行 API
|
||||
*
|
||||
* 目的:设备 -> 插件 -> 服务端
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
public interface IotDeviceUpstreamApi {
|
||||
|
||||
String PREFIX = ApiConstants.PREFIX + "/device/upstream";
|
||||
|
||||
// TODO @芋艿:考虑 http 认证
|
||||
/**
|
||||
* 认证 Emqx 连接
|
||||
*
|
||||
* @param authReqDTO 认证 Emqx 连接 DTO
|
||||
*/
|
||||
@PostMapping(PREFIX + "/authenticate-emqx-connection")
|
||||
CommonResult<Boolean> authenticateEmqxConnection(@Valid @RequestBody IotDeviceEmqxAuthReqDTO authReqDTO);
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* TODO 芋艿:占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.api.device.dto;
|
|
@ -1,6 +0,0 @@
|
|||
/**
|
||||
* 占位
|
||||
*
|
||||
* TODO 芋艿:后续删除
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.api;
|
|
@ -39,18 +39,6 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
|
||||
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
|
||||
|
||||
// ========== 插件配置 1-050-006-000 ==========
|
||||
ErrorCode PLUGIN_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件配置不存在");
|
||||
ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败");
|
||||
ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败,文件名与原插件id不匹配");
|
||||
ErrorCode PLUGIN_CONFIG_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件");
|
||||
ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
|
||||
ErrorCode PLUGIN_CONFIG_KEY_DUPLICATE = new ErrorCode(1_050_006_005, "插件标识已存在");
|
||||
ErrorCode PLUGIN_START_FAILED = new ErrorCode(1_050_006_006, "插件启动失败");
|
||||
ErrorCode PLUGIN_STOP_FAILED = new ErrorCode(1_050_006_007, "插件停止失败");
|
||||
|
||||
// ========== 插件实例 1-050-007-000 ==========
|
||||
|
||||
// ========== 固件相关 1-050-008-000 ==========
|
||||
|
||||
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
|
||||
|
@ -66,16 +54,10 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
|
||||
ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
|
||||
|
||||
// ========== MQTT 通信相关 1-050-009-000 ==========
|
||||
ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");
|
||||
|
||||
// ========== IoT 数据桥梁 1-050-010-000 ==========
|
||||
ErrorCode DATA_BRIDGE_NOT_EXISTS = new ErrorCode(1_050_010_000, "IoT 数据桥梁不存在");
|
||||
|
||||
// ========== IoT 场景联动 1-050-011-000 ==========
|
||||
ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_011_000, "IoT 场景联动不存在");
|
||||
|
||||
// ========== IoT 产品脚本信息 1-050-012-000 ==========
|
||||
ErrorCode PRODUCT_SCRIPT_NOT_EXISTS = new ErrorCode(1_050_012_000, "IoT 产品脚本信息不存在");
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* IoT 设备 API 实现类
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@Primary // 保证优先匹配,因为 yudao-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
|
||||
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
||||
return success(deviceService.authDevice(authReqDTO));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
|
||||
import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* * 设备数据 Upstream 上行 API 实现类
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@Primary // 保证优先匹配,因为 yudao-module-iot-net-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
|
||||
public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceUpstreamService deviceUpstreamService;
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
||||
boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
/**
|
||||
* 占位
|
||||
*
|
||||
* TODO 芋艿:后续删除
|
||||
* iot API 包,定义并实现提供给其它模块的 API
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.api;
|
|
@ -121,23 +121,10 @@ public class IotDeviceDO extends TenantBaseDO {
|
|||
*/
|
||||
private String firmwareId;
|
||||
|
||||
// TODO @芋艿:【待定 003】:要不要增加 username?目前 tl 有,阿里云之类的没有
|
||||
/**
|
||||
* 设备密钥,用于设备认证,需安全存储
|
||||
* 设备密钥,用于设备认证
|
||||
*/
|
||||
private String deviceSecret;
|
||||
/**
|
||||
* MQTT 客户端 ID
|
||||
*/
|
||||
private String mqttClientId;
|
||||
/**
|
||||
* MQTT 用户名
|
||||
*/
|
||||
private String mqttUsername;
|
||||
/**
|
||||
* MQTT 密码
|
||||
*/
|
||||
private String mqttPassword;
|
||||
/**
|
||||
* 认证类型(如一机一密、动态注册)
|
||||
*/
|
||||
|
|
|
@ -9,14 +9,15 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
|
|||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
// TODO @芋艿:弱化 deviceKey;使用 product_key + device_name 替代
|
||||
/**
|
||||
* 设备属性的数据缓存,采用 HASH 结构
|
||||
* <p>
|
||||
* KEY 格式:device_property:{productKey},${deviceName}
|
||||
* KEY 格式:device_property:{deviceKey}
|
||||
* HASH KEY:identifier 属性标识
|
||||
* VALUE 数据类型:String(JSON) {@link IotDevicePropertyDO}
|
||||
*/
|
||||
String DEVICE_PROPERTY = "iot:device_property:%s,%s";
|
||||
String DEVICE_PROPERTY = "iot:device_property:%s";
|
||||
|
||||
/**
|
||||
* 设备的最后上报时间,采用 ZSET 结构
|
||||
|
@ -26,6 +27,15 @@ public interface RedisKeyConstants {
|
|||
*/
|
||||
String DEVICE_REPORT_TIMES = "iot:device_report_times";
|
||||
|
||||
/**
|
||||
* 设备关联的网关 serverId 缓存,采用 HASH 结构
|
||||
*
|
||||
* KEY 格式:device_server_id
|
||||
* HASH KEY:{productKey},{deviceName}
|
||||
* VALUE 数据类型:String serverId
|
||||
*/
|
||||
String DEVICE_SERVER_ID = "iot:device_server_id";
|
||||
|
||||
/**
|
||||
* 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||
*
|
||||
|
@ -42,13 +52,4 @@ public interface RedisKeyConstants {
|
|||
*/
|
||||
String THING_MODEL_LIST = "iot:thing_model_list";
|
||||
|
||||
/**
|
||||
* 设备关联的网关 serverId 缓存,采用 HASH 结构
|
||||
*
|
||||
* KEY 格式:device_server_id
|
||||
* HASH KEY:{productKey},{deviceName}
|
||||
* VALUE 数据类型:String serverId
|
||||
*/
|
||||
String DEVICE_SERVER_ID = "iot:device_server_id";
|
||||
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
|
||||
import cn.iocoder.yudao.module.iot.enums.ApiConstants;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
|
||||
/**
|
||||
* IoT 模块的 Security 配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false, value = "iotSecurityConfiguration")
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Bean("iotAuthorizeRequestsCustomizer")
|
||||
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
|
||||
return new AuthorizeRequestsCustomizer() {
|
||||
|
||||
@Override
|
||||
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
|
||||
// RPC 服务的安全配置
|
||||
registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.framework.security.core;
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.device;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import jakarta.validation.Valid;
|
||||
|
@ -228,4 +229,12 @@ public interface IotDeviceService {
|
|||
*/
|
||||
List<IotDeviceDO> getDeviceListByProductKeyAndNames(String productKey, List<String> deviceNames);
|
||||
|
||||
/**
|
||||
* 认证设备
|
||||
*
|
||||
* @param authReqDTO 认证信息
|
||||
* @return 是否认证成功
|
||||
*/
|
||||
boolean authDevice(IotDeviceAuthReqDTO authReqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.device;
|
|||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
@ -12,16 +13,16 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
|||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
|
||||
import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -397,15 +398,17 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
|||
return respVO;
|
||||
}
|
||||
|
||||
// TODO @芋艿:改成通用的;
|
||||
@Override
|
||||
public IotDeviceMqttConnectionParamsRespVO getMqttConnectionParams(Long deviceId) {
|
||||
IotDeviceDO device = validateDeviceExists(deviceId);
|
||||
MqttSignResult mqttSignResult = MqttSignUtils.calculate(device.getProductKey(), device.getDeviceName(),
|
||||
device.getDeviceSecret());
|
||||
return new IotDeviceMqttConnectionParamsRespVO()
|
||||
.setMqttClientId(mqttSignResult.getClientId())
|
||||
.setMqttUsername(mqttSignResult.getUsername())
|
||||
.setMqttPassword(mqttSignResult.getPassword());
|
||||
// MqttSignResult mqttSignResult = MqttSignUtils.calculate(device.getProductKey(), device.getDeviceName(),
|
||||
// device.getDeviceSecret());
|
||||
// return new IotDeviceMqttConnectionParamsRespVO()
|
||||
// .setMqttClientId(mqttSignResult.getClientId())
|
||||
// .setMqttUsername(mqttSignResult.getUsername())
|
||||
// .setMqttPassword(mqttSignResult.getPassword());
|
||||
return null;
|
||||
}
|
||||
|
||||
private void deleteDeviceCache(IotDeviceDO device) {
|
||||
|
@ -459,4 +462,29 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
|||
return deviceMapper.selectByProductKeyAndDeviceNames(productKey, deviceNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
||||
// 1. 校验设备是否存在
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authReqDTO.getUsername());
|
||||
if (deviceInfo == null) {
|
||||
log.error("[authDevice][认证失败,username({}) 格式不正确]", authReqDTO.getUsername());
|
||||
return false;
|
||||
}
|
||||
String deviceName = deviceInfo.getDeviceName();
|
||||
String productKey = deviceInfo.getProductKey();
|
||||
IotDeviceDO device = getSelf().getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
|
||||
if (device == null) {
|
||||
log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 校验密码
|
||||
IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, device.getDeviceSecret());
|
||||
if (ObjUtil.notEqual(authInfo.getPassword(), authReqDTO.getPassword())) {
|
||||
log.error("[authDevice][设备({}/{}) 密码不正确]", productKey, deviceName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.module.iot.service.device.control;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
|
@ -48,11 +47,4 @@ public interface IotDeviceUpstreamService {
|
|||
// */
|
||||
// void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
|
||||
|
||||
/**
|
||||
* Emqx 连接认证
|
||||
*
|
||||
* @param authReqDTO Emqx 连接认证 DTO
|
||||
*/
|
||||
boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.service.device.control;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
|
||||
|
@ -12,8 +11,6 @@ import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
|
|||
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
|
||||
import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
|
||||
import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -222,37 +219,4 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
|
|||
// sendDeviceMessage(message, device);
|
||||
}
|
||||
|
||||
// TODO @芋艿:后续需要考虑,http 的认证
|
||||
@Override
|
||||
public boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
||||
log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
|
||||
// 1.1 校验设备是否存在。username 格式:${DeviceName}&${ProductKey}
|
||||
String[] usernameParts = authReqDTO.getUsername().split("&");
|
||||
if (usernameParts.length != 2) {
|
||||
log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
|
||||
return false;
|
||||
}
|
||||
String deviceName = usernameParts[0];
|
||||
String productKey = usernameParts[1];
|
||||
// 1.2 获得设备
|
||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
|
||||
if (device == null) {
|
||||
log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]", productKey, deviceName);
|
||||
return false;
|
||||
}
|
||||
// TODO @haohao:需要记录,记录设备的最后时间
|
||||
|
||||
// 2. 校验密码
|
||||
String deviceSecret = device.getDeviceSecret();
|
||||
String clientId = authReqDTO.getClientId();
|
||||
MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
|
||||
// TODO 建议,先失败,return false;
|
||||
if (StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
|
||||
log.info("[authenticateEmqxConnection][认证成功]");
|
||||
return true;
|
||||
}
|
||||
log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.util;
|
||||
|
||||
import cn.hutool.crypto.digest.HMac;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* MQTT 签名工具类
|
||||
*
|
||||
* 提供静态方法来计算 MQTT 连接参数
|
||||
*/
|
||||
public class MqttSignUtils {
|
||||
|
||||
/**
|
||||
* 计算 MQTT 连接参数
|
||||
*
|
||||
* @param productKey 产品密钥
|
||||
* @param deviceName 设备名称
|
||||
* @param deviceSecret 设备密钥
|
||||
* @return 包含 clientId, username, password 的结果对象
|
||||
*/
|
||||
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
|
||||
return calculate(productKey, deviceName, deviceSecret, productKey + "." + deviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 MQTT 连接参数
|
||||
*
|
||||
* @param productKey 产品密钥
|
||||
* @param deviceName 设备名称
|
||||
* @param deviceSecret 设备密钥
|
||||
* @param clientId 客户端 ID
|
||||
* @return 包含 clientId, username, password 的结果对象
|
||||
*/
|
||||
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
|
||||
String username = deviceName + "&" + productKey;
|
||||
// 构建签名内容
|
||||
StringBuilder signContentBuilder = new StringBuilder()
|
||||
.append("clientId").append(clientId)
|
||||
.append("deviceName").append(deviceName)
|
||||
.append("deviceSecret").append(deviceSecret)
|
||||
.append("productKey").append(productKey);
|
||||
|
||||
// 使用 HMac 计算签名
|
||||
byte[] key = deviceSecret.getBytes(StandardCharsets.UTF_8);
|
||||
String signContent = signContentBuilder.toString();
|
||||
HMac mac = new HMac(HmacAlgorithm.HmacSHA256, key);
|
||||
String password = mac.digestHex(signContent);
|
||||
|
||||
return new MqttSignResult(clientId, username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* MQTT 签名结果类
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class MqttSignResult {
|
||||
|
||||
private final String clientId;
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,15 @@
|
|||
package cn.iocoder.yudao.module.iot.core.biz;
|
||||
|
||||
// TODO @芋艿:待实现
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
|
||||
/**
|
||||
* IoT 设备通用 API
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
public interface IotDeviceCommonApi {
|
||||
|
||||
CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
|
||||
package cn.iocoder.yudao.module.iot.core.biz.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
// TODO @芋艿:要不要继承 IotDeviceUpstreamAbstractReqDTO
|
||||
// TODO @芋艿:@haohao:后续其它认证的设计
|
||||
/**
|
||||
* IoT 认证 Emqx 连接 Request DTO
|
||||
* IoT 设备认证 Request DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceEmqxAuthReqDTO {
|
||||
public class IotDeviceAuthReqDTO {
|
||||
|
||||
/**
|
||||
* 客户端 ID
|
|
@ -0,0 +1,85 @@
|
|||
package cn.iocoder.yudao.module.iot.core.util;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备【认证】的工具类,参考阿里云
|
||||
*
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/how-do-i-obtain-mqtt-parameters-for-authentication">如何计算 MQTT 签名参数</a>
|
||||
*/
|
||||
public class IotDeviceAuthUtils {
|
||||
|
||||
/**
|
||||
* 认证信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AuthInfo {
|
||||
|
||||
/**
|
||||
* 客户端 ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备信息
|
||||
*/
|
||||
@Data
|
||||
public static class DeviceInfo {
|
||||
|
||||
private String productKey;
|
||||
|
||||
private String deviceName;
|
||||
|
||||
}
|
||||
|
||||
public static AuthInfo getAuthInfo(String productKey, String deviceName, String deviceSecret) {
|
||||
String clientId = buildClientId(productKey, deviceName);
|
||||
String username = buildUsername(productKey, deviceName);
|
||||
String content = "clientId" + clientId +
|
||||
"deviceName" + deviceName +
|
||||
"deviceSecret" + deviceSecret +
|
||||
"productKey" + productKey;
|
||||
String password = buildPassword(deviceSecret, content);
|
||||
return new AuthInfo(clientId, username, password);
|
||||
}
|
||||
|
||||
private static String buildClientId(String productKey, String deviceName) {
|
||||
return String.format("%s.%s", productKey, deviceName);
|
||||
}
|
||||
|
||||
private static String buildUsername(String productKey, String deviceName) {
|
||||
return String.format("%s&%s", deviceName, productKey);
|
||||
}
|
||||
|
||||
private static String buildPassword(String deviceSecret, String content) {
|
||||
return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, deviceSecret.getBytes())
|
||||
.digestHex(content);
|
||||
}
|
||||
|
||||
public static DeviceInfo parseUsername(String username) {
|
||||
String[] usernameParts = username.split("&");
|
||||
if (usernameParts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,7 @@ import cn.hutool.system.SystemUtil;
|
|||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
|
||||
/**
|
||||
* IoT 设备消息的工具类
|
||||
* IoT 设备【消息】的工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息队列相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
|
|
|
@ -24,9 +24,8 @@ public class IotGatewayConfiguration {
|
|||
public static class HttpProtocolConfiguration {
|
||||
|
||||
@Bean
|
||||
public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties,
|
||||
IotDeviceMessageProducer deviceMessageProducer) {
|
||||
return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp(), deviceMessageProducer);
|
||||
public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties) {
|
||||
return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.config;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "yudao.iot.gateway")
|
||||
|
@ -15,6 +18,10 @@ public class IotGatewayProperties {
|
|||
* 设备 RPC 服务配置
|
||||
*/
|
||||
private RpcProperties rpc;
|
||||
/**
|
||||
* Token 配置
|
||||
*/
|
||||
private TokenProperties token;
|
||||
|
||||
/**
|
||||
* 协议配置
|
||||
|
@ -27,15 +34,34 @@ public class IotGatewayProperties {
|
|||
/**
|
||||
* 主程序 API 地址
|
||||
*/
|
||||
@NotEmpty(message = "主程序 API 地址不能为空")
|
||||
private String url;
|
||||
/**
|
||||
* 连接超时时间
|
||||
*/
|
||||
private String connectTimeout;
|
||||
@NotNull(message = "连接超时时间不能为空")
|
||||
private Duration connectTimeout;
|
||||
/**
|
||||
* 读取超时时间
|
||||
*/
|
||||
private String readTimeout;
|
||||
@NotNull(message = "读取超时时间不能为空")
|
||||
private Duration readTimeout;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TokenProperties {
|
||||
|
||||
/**
|
||||
* 密钥
|
||||
*/
|
||||
@NotEmpty(message = "密钥不能为空")
|
||||
private String secret;
|
||||
/**
|
||||
* 令牌有效期
|
||||
*/
|
||||
@NotNull(message = "令牌有效期不能为空")
|
||||
private Duration expiration;
|
||||
|
||||
}
|
||||
|
||||
|
@ -60,6 +86,7 @@ public class IotGatewayProperties {
|
|||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
@NotNull(message = "是否开启不能为空")
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 服务端口
|
||||
|
@ -74,6 +101,7 @@ public class IotGatewayProperties {
|
|||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
@NotNull(message = "是否开启不能为空")
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* MQTT 服务器地址
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* iot gateway 错误码枚举类
|
||||
* <p>
|
||||
* iot 系统,使用 1-051-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 设备认证 1-050-001-000 ============
|
||||
ErrorCode DEVICE_AUTH_FAIL = new ErrorCode(1_051_001_000, "设备鉴权失败"); // 对应阿里云 20000
|
||||
ErrorCode DEVICE_TOKEN_EXPIRED = new ErrorCode(1_051_001_002, "token 失效。需重新调用 auth 进行鉴权,获取token"); // 对应阿里云 20001
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
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.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Vertx;
|
||||
|
@ -25,8 +25,6 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle {
|
|||
|
||||
private final IotGatewayProperties.HttpProperties httpProperties;
|
||||
|
||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
private HttpServer httpServer;
|
||||
|
||||
@Override
|
||||
|
@ -38,10 +36,11 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle {
|
|||
router.route().handler(BodyHandler.create());
|
||||
|
||||
// 创建处理器,添加路由处理器
|
||||
IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(
|
||||
this, deviceMessageProducer);
|
||||
IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this);
|
||||
router.post(IotHttpUpstreamHandler.PROPERTY_PATH).handler(upstreamHandler);
|
||||
router.post(IotHttpUpstreamHandler.EVENT_PATH).handler(upstreamHandler);
|
||||
IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this);
|
||||
router.post(IotHttpAuthHandler.PATH).handler(authHandler);
|
||||
|
||||
// 启动 HTTP 服务器
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议的处理器抽象基类:提供通用的前置处理(认证)、全局的异常捕获等
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> {
|
||||
|
||||
private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext context) {
|
||||
try {
|
||||
// 1. 前置处理
|
||||
CommonResult<Object> result = beforeHandle(context);
|
||||
if (result != null) {
|
||||
writeResponse(context, result);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行逻辑
|
||||
result = handle0(context);
|
||||
writeResponse(context, result);
|
||||
} catch (ServiceException e) {
|
||||
writeResponse(context, CommonResult.error(e.getCode(), e.getMessage()));
|
||||
} catch (Exception e) {
|
||||
log.error("[handle][path({}) 处理异常]", context.request().path(), e);
|
||||
writeResponse(context, CommonResult.error(INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract CommonResult<Object> handle0(RoutingContext context);
|
||||
|
||||
private CommonResult<Object> beforeHandle(RoutingContext context) {
|
||||
// 如果不需要认证,则不走前置处理
|
||||
String path = context.request().path();
|
||||
if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解析参数
|
||||
String token = context.request().getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (StrUtil.isEmpty(token)) {
|
||||
throw invalidParamException("token 不能为空");
|
||||
}
|
||||
String productKey = context.pathParam("productKey");
|
||||
if (StrUtil.isEmpty(productKey)) {
|
||||
throw invalidParamException("productKey 不能为空");
|
||||
}
|
||||
String deviceName = context.pathParam("deviceName");
|
||||
if (StrUtil.isEmpty(deviceName)) {
|
||||
throw invalidParamException("deviceName 不能为空");
|
||||
}
|
||||
|
||||
// 校验 token
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token);
|
||||
Assert.notNull(deviceInfo, "设备信息不能为空");
|
||||
// 校验设备信息是否匹配
|
||||
if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey())
|
||||
|| ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
|
||||
throw exception(FORBIDDEN);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void writeResponse(RoutingContext context, Object data) {
|
||||
context.response()
|
||||
.setStatusCode(200)
|
||||
.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
.end(JsonUtils.toJsonString(data));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||
import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议的【认证】处理器
|
||||
*
|
||||
* 参考 <a href="阿里云 IoT —— HTTPS 连接通信">https://help.aliyun.com/zh/iot/user-guide/establish-connections-over-https</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class IotHttpAuthHandler extends IotHttpAbstractHandler {
|
||||
|
||||
public static final String PATH = "/auth";
|
||||
|
||||
private final IotHttpUpstreamProtocol protocol;
|
||||
|
||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
private final IotDeviceTokenService deviceTokenService;
|
||||
|
||||
private final IotDeviceCommonApi deviceClientService;
|
||||
|
||||
public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) {
|
||||
this.protocol = protocol;
|
||||
this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
|
||||
this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
|
||||
this.deviceClientService = SpringUtil.getBean(IotDeviceCommonApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Object> handle0(RoutingContext context) {
|
||||
// 解析参数
|
||||
JsonObject body = context.body().asJsonObject();
|
||||
String clientId = body.getString("clientId");
|
||||
if (StrUtil.isEmpty(clientId)) {
|
||||
throw invalidParamException("clientId 不能为空");
|
||||
}
|
||||
String username = body.getString("username");
|
||||
if (StrUtil.isEmpty(username)) {
|
||||
throw invalidParamException("username 不能为空");
|
||||
}
|
||||
String password = body.getString("password");
|
||||
if (StrUtil.isEmpty(password)) {
|
||||
throw invalidParamException("password 不能为空");
|
||||
}
|
||||
|
||||
// 执行认证
|
||||
CommonResult<Boolean> result = deviceClientService.authDevice(new IotDeviceAuthReqDTO()
|
||||
.setClientId(clientId).setUsername(username).setPassword(password));
|
||||
if (result == null || !result.isSuccess()) {
|
||||
throw exception(DEVICE_AUTH_FAIL);
|
||||
}
|
||||
|
||||
// 生成 Token
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(username);
|
||||
Assert.notNull(deviceInfo, "设备信息不能为空");
|
||||
String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
|
||||
Assert.notBlank(token, "生成 token 不能为空位");
|
||||
|
||||
// TODO @芋艿:发送上线消息;
|
||||
|
||||
// 构建响应数据
|
||||
return success(MapUtil.of("token", token));
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ 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.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
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;
|
||||
|
@ -16,11 +17,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
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 协议的处理器
|
||||
* IoT 网关 HTTP 协议的【上行】处理器
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -54,47 +52,35 @@ public class IotHttpUpstreamHandler implements Handler<RoutingContext> {
|
|||
private static final String EVENT_METHOD_SUFFIX = ".post";
|
||||
|
||||
private final IotHttpUpstreamProtocol protocol;
|
||||
// /**
|
||||
// * 设备上行 API
|
||||
// */
|
||||
// private final IotDeviceUpstreamApi deviceUpstreamApi;
|
||||
/**
|
||||
* 设备消息生产者
|
||||
*/
|
||||
|
||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
public IotHttpUpstreamHandler(IotHttpUpstreamProtocol protocol) {
|
||||
this.protocol = protocol;
|
||||
this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext routingContext) {
|
||||
String path = routingContext.request().path();
|
||||
public void handle(RoutingContext context) {
|
||||
String path = context.request().path();
|
||||
// 1. 解析通用参数
|
||||
Map<String, String> params = parseCommonParams(context);
|
||||
String productKey = params.get("productKey");
|
||||
String deviceName = params.get("deviceName");
|
||||
JsonObject body = context.body().asJsonObject();
|
||||
|
||||
try {
|
||||
// 1. 解析通用参数
|
||||
Map<String, String> params = parseCommonParams(routingContext);
|
||||
String productKey = params.get("productKey");
|
||||
String deviceName = params.get("deviceName");
|
||||
JsonObject body = routingContext.body().asJsonObject();
|
||||
// 2. 根据路径模式处理不同类型的请求
|
||||
if (isPropertyPostPath(path)) {
|
||||
// 处理属性上报
|
||||
handlePropertyPost(context, productKey, deviceName, body);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 根据路径模式处理不同类型的请求
|
||||
if (isPropertyPostPath(path)) {
|
||||
// 处理属性上报
|
||||
handlePropertyPost(routingContext, productKey, deviceName, body);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEventPostPath(path)) {
|
||||
// 处理事件上报
|
||||
String identifier = routingContext.pathParam("identifier");
|
||||
handleEventPost(routingContext, productKey, deviceName, identifier, body);
|
||||
return;
|
||||
}
|
||||
|
||||
// 不支持的请求路径
|
||||
sendErrorResponse(routingContext, "unknown", BAD_REQUEST.getCode(), "不支持的请求路径");
|
||||
} catch (Exception e) {
|
||||
log.error("[handle][处理上行请求异常] path={}", path, e);
|
||||
String method = determineMethodFromPath(path, routingContext);
|
||||
sendErrorResponse(routingContext, method, INTERNAL_SERVER_ERROR.getCode(),
|
||||
INTERNAL_SERVER_ERROR.getMsg());
|
||||
if (isEventPostPath(path)) {
|
||||
// 处理事件上报
|
||||
String identifier = context.pathParam("identifier");
|
||||
handleEventPost(context, productKey, deviceName, identifier, body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +155,6 @@ public class IotHttpUpstreamHandler implements Handler<RoutingContext> {
|
|||
//
|
||||
// // 事件上报
|
||||
// CommonResult<Boolean> result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
|
||||
// String method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
|
||||
//
|
||||
// // 返回响应
|
||||
// sendResponse(routingContext, requestId, method, result);
|
||||
|
@ -195,20 +180,6 @@ public class IotHttpUpstreamHandler implements Handler<RoutingContext> {
|
|||
// IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误响应
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param method 方法名
|
||||
* @param code 错误代码
|
||||
* @param message 错误消息
|
||||
*/
|
||||
private void sendErrorResponse(RoutingContext routingContext, String method, Integer code,
|
||||
String message) {
|
||||
// IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
|
||||
// IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从路径确定方法名
|
||||
*
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.service.auth;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
|
||||
/**
|
||||
* IoT 设备 Token 服务 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDeviceTokenService {
|
||||
|
||||
/**
|
||||
* 创建设备 Token
|
||||
*
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 设备 Token
|
||||
*/
|
||||
String createToken(String productKey, String deviceName);
|
||||
|
||||
/**
|
||||
* 验证设备 Token
|
||||
*
|
||||
* @param token 设备 Token
|
||||
* @return 设备信息
|
||||
*/
|
||||
IotDeviceAuthUtils.DeviceInfo verifyToken(String token);
|
||||
|
||||
/**
|
||||
* 解析用户名
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 设备信息
|
||||
*/
|
||||
IotDeviceAuthUtils.DeviceInfo parseUsername(String username);
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.service.auth;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_TOKEN_EXPIRED;
|
||||
|
||||
/**
|
||||
* IoT 设备 Token Service 实现类:调用远程的 device http 接口,进行设备 Token 生成、解析等逻辑
|
||||
*
|
||||
* 注意:目前仅 HTTP 协议使用
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IotDeviceTokenServiceImpl implements IotDeviceTokenService {
|
||||
|
||||
@Resource
|
||||
private IotGatewayProperties gatewayProperties;
|
||||
|
||||
@Override
|
||||
public String createToken(String productKey, String deviceName) {
|
||||
Assert.notBlank(productKey, "productKey 不能为空");
|
||||
Assert.notBlank(deviceName, "deviceName 不能为空");
|
||||
// 构建 JWT payload
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("productKey", productKey);
|
||||
payload.put("deviceName", deviceName);
|
||||
LocalDateTime expireTime = LocalDateTimeUtils.addTime(gatewayProperties.getToken().getExpiration());
|
||||
payload.put("exp", LocalDateTimeUtils.toEpochSecond(expireTime)); // 过期时间(exp 是 JWT 规范推荐)
|
||||
|
||||
// 生成 JWT Token
|
||||
return JWTUtil.createToken(payload, gatewayProperties.getToken().getSecret().getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDeviceAuthUtils.DeviceInfo verifyToken(String token) {
|
||||
Assert.notBlank(token, "token 不能为空");
|
||||
// 校验 JWT Token
|
||||
boolean verify = JWTUtil.verify(token, gatewayProperties.getToken().getSecret().getBytes());
|
||||
if (!verify) {
|
||||
throw exception(DEVICE_TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
// 解析 Token
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
JSONObject payload = jwt.getPayloads();
|
||||
// 检查过期时间
|
||||
Long exp = payload.getLong("exp");
|
||||
if (exp == null || exp > System.currentTimeMillis() / 1000) {
|
||||
throw exception(DEVICE_TOKEN_EXPIRED);
|
||||
}
|
||||
String productKey = payload.getStr("productKey");
|
||||
String deviceName = payload.getStr("deviceName");
|
||||
Assert.notBlank(productKey, "productKey 不能为空");
|
||||
Assert.notBlank(deviceName, "deviceName 不能为空");
|
||||
return new IotDeviceAuthUtils.DeviceInfo().setProductKey(productKey).setDeviceName(deviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDeviceAuthUtils.DeviceInfo parseUsername(String username) {
|
||||
return IotDeviceAuthUtils.parseUsername(username);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
|
||||
/**
|
||||
* Iot 设备信息 Service 实现类:调用远程的 device http 接口,进行设备认证、设备获取等
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IotDeviceClientServiceImpl implements IotDeviceCommonApi {
|
||||
|
||||
@Resource
|
||||
private IotGatewayProperties gatewayProperties;
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
IotGatewayProperties.RpcProperties rpc = gatewayProperties.getRpc();
|
||||
restTemplate = new RestTemplateBuilder()
|
||||
.rootUri(rpc.getUrl() + "/rpc-api/iot/device/")
|
||||
.readTimeout(rpc.getReadTimeout())
|
||||
.connectTimeout(rpc.getConnectTimeout())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
||||
return doPost("auth", authReqDTO);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
|
||||
try {
|
||||
CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
|
||||
(Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
|
||||
log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
|
||||
return CommonResult.error(INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,10 @@ yudao:
|
|||
url: http://127.0.0.1:48080 # 主程序 API 地址
|
||||
connect-timeout: 30s
|
||||
read-timeout: 30s
|
||||
# 设备 Token 配置
|
||||
token:
|
||||
secret: 1234567890123456789012345678901
|
||||
expiration: 7d
|
||||
|
||||
# 协议配置
|
||||
protocol:
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package cn.iocoder.yudao.module.iot.gateway.service.auth;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link IotDeviceTokenServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class IotDeviceTokenServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private IotGatewayProperties gatewayProperties;
|
||||
|
||||
@InjectMocks
|
||||
private IotDeviceTokenServiceImpl tokenService;
|
||||
|
||||
private IotGatewayProperties.TokenProperties tokenProperties;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 初始化 Token 配置
|
||||
tokenProperties = new IotGatewayProperties.TokenProperties();
|
||||
tokenProperties.setSecret("1234567890123456789012345678901");
|
||||
tokenProperties.setExpiration(Duration.ofDays(7));
|
||||
|
||||
when(gatewayProperties.getToken()).thenReturn(tokenProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateToken_Success() {
|
||||
// 准备参数
|
||||
String productKey = "testProduct";
|
||||
String deviceName = "testDevice";
|
||||
|
||||
// 调用方法
|
||||
String token = tokenService.createToken(productKey, deviceName);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(token);
|
||||
assertFalse(token.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateToken_WithBlankParameters() {
|
||||
// 测试空白参数
|
||||
assertNull(tokenService.createToken("", "deviceName"));
|
||||
assertNull(tokenService.createToken("productKey", ""));
|
||||
assertNull(tokenService.createToken(null, "deviceName"));
|
||||
assertNull(tokenService.createToken("productKey", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateToken_WithoutConfig() {
|
||||
// 模拟配置为空
|
||||
when(gatewayProperties.getToken()).thenReturn(null);
|
||||
|
||||
// 调用方法
|
||||
String token = tokenService.createToken("productKey", "deviceName");
|
||||
|
||||
// 验证结果
|
||||
assertNull(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifyToken_Success() {
|
||||
// 准备参数
|
||||
String productKey = "testProduct";
|
||||
String deviceName = "testDevice";
|
||||
|
||||
// 创建 Token
|
||||
String token = tokenService.createToken(productKey, deviceName);
|
||||
assertNotNull(token);
|
||||
|
||||
// 验证 Token
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = tokenService.verifyToken(token);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(deviceInfo);
|
||||
assertEquals(productKey, deviceInfo.getProductKey());
|
||||
assertEquals(deviceName, deviceInfo.getDeviceName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifyToken_WithBlankToken() {
|
||||
// 测试空白 Token
|
||||
assertNull(tokenService.verifyToken(""));
|
||||
assertNull(tokenService.verifyToken(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifyToken_WithInvalidToken() {
|
||||
// 测试无效 Token
|
||||
assertNull(tokenService.verifyToken("invalid.token.here"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifyToken_WithoutConfig() {
|
||||
// 模拟配置为空
|
||||
when(gatewayProperties.getToken()).thenReturn(null);
|
||||
|
||||
// 调用方法
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = tokenService.verifyToken("any.token.here");
|
||||
|
||||
// 验证结果
|
||||
assertNull(deviceInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTokenRoundTrip() {
|
||||
// 测试完整的 Token 创建和验证流程
|
||||
String productKey = "myProduct";
|
||||
String deviceName = "myDevice";
|
||||
|
||||
// 1. 创建 Token
|
||||
String token = tokenService.createToken(productKey, deviceName);
|
||||
assertNotNull(token);
|
||||
|
||||
// 2. 验证 Token
|
||||
IotDeviceAuthUtils.DeviceInfo deviceInfo = tokenService.verifyToken(token);
|
||||
assertNotNull(deviceInfo);
|
||||
assertEquals(productKey, deviceInfo.getProductKey());
|
||||
assertEquals(deviceName, deviceInfo.getDeviceName());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.net.component.core.config;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.net.component.core.upstream.IotDeviceUpstreamClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* IoT 网络组件的通用自动配置类
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@EnableConfigurationProperties(IotNetComponentCommonProperties.class)
|
||||
public class IotNetComponentCommonAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 创建设备上行客户端
|
||||
*/
|
||||
@Bean
|
||||
public IotDeviceUpstreamClient deviceUpstreamClient() {
|
||||
return new IotDeviceUpstreamClient();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.net.component.core.upstream;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
/**
|
||||
* 设备数据 Upstream 上行客户端
|
||||
* <p>
|
||||
* 直接调用 IotDeviceUpstreamApi 接口
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@Slf4j
|
||||
public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceUpstreamApi deviceUpstreamApi;
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
||||
return deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.net.component.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* IoT 网络组件的通用工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class IotNetComponentCommonUtils {
|
||||
|
||||
/**
|
||||
* 将对象转换为JSON字符串后写入HTTP响应
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param data 数据对象
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void writeJsonResponse(RoutingContext routingContext, Object data) {
|
||||
routingContext.response()
|
||||
.setStatusCode(200)
|
||||
.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
.end(JsonUtils.toJsonString(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse)
|
||||
* <p>
|
||||
* 推荐使用此方法,统一 MQTT 和 HTTP 的响应格式。使用方式:
|
||||
*
|
||||
* <pre>
|
||||
* // 成功响应
|
||||
* IotStandardResponse response = IotStandardResponse.success(requestId, method, data);
|
||||
* IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
|
||||
*
|
||||
* // 错误响应
|
||||
* IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
|
||||
* IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
|
||||
* </pre>
|
||||
*
|
||||
* @param routingContext 路由上下文
|
||||
* @param response IotStandardResponse 响应对象
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) {
|
||||
routingContext.response()
|
||||
.setStatusCode(200)
|
||||
.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
.end(JsonUtils.toJsonString(response));
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonAutoConfiguration
|
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceEmqxAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.net.component.server.upstream;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
|
||||
/**
|
||||
* 组件上行客户端,用于向主程序上报设备数据
|
||||
* <p>
|
||||
* 通过 HTTP 调用远程的 IotDeviceUpstreamApi 接口
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class IotComponentUpstreamClient {
|
||||
|
||||
public static final String URL_PREFIX = "/rpc-api/iot/device/upstream";
|
||||
|
||||
private final IotNetComponentServerProperties properties;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
// @Override
|
||||
// public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
|
||||
// String url = properties.getUpstreamUrl() + URL_PREFIX + "/update-state";
|
||||
// return doPost(url, updateReqDTO);
|
||||
// }
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
|
||||
try {
|
||||
CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
|
||||
(Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
|
||||
log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
|
||||
return CommonResult.error(INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue