diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java index a6601d551b..a69ea180cc 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java @@ -1,10 +1,7 @@ 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.IotDeviceEventReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; +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; @@ -47,6 +44,15 @@ public interface IotDeviceUpstreamApi { @PostMapping(PREFIX + "/report-event") CommonResult reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO); + // TODO @芋艿:这个需要 plugins 接入下 + /** + * 注册设备 + * + * @param registerReqDTO 注册设备 DTO + */ + @PostMapping(PREFIX + "/register") + CommonResult registerDevice(@Valid @RequestBody IotDeviceRegisterReqDTO registerReqDTO); + // ========== 插件相关 ========== /** diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java new file mode 100644 index 0000000000..a627cba421 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream; + +import lombok.Data; + +/** + * IoT 设备【注册】注册 Request DTO + * + * @author 芋道源码 + */ +@Data +public class IotDeviceRegisterReqDTO extends IotDeviceUpstreamAbstractReqDTO { +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java index f8eca51eda..6dd10a3865 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java @@ -26,7 +26,11 @@ public enum IotDeviceMessageIdentifierEnum { OTA_UPGRADE("upgrade"), // 下行 OTA_PULL("pull"), // 上行 OTA_PROGRESS("progress"), // 上行 - OTA_REPORT("report"),; // 上行 + OTA_REPORT("report"), // 上行 + + REGISTER_REGISTER("register"), // 上行 + REGISTER_SUB_REGISTER("sub_register"), // 上行 + REGISTER_SUB_UNREGISTER("sub_unregister"),; // 下行 /** * 标志符 diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java index 2ee0a8acc0..000af57d46 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java @@ -14,11 +14,12 @@ import java.util.Arrays; public enum IotDeviceMessageTypeEnum implements ArrayValuable { STATE("state"), // 设备状态 - PROPERTY("property"), // 设备属性 - EVENT("event"), // 设备事件 - SERVICE("service"), // 设备服务 - CONFIG("config"), // 设备配置 - OTA("ota"),; // 设备 OTA + PROPERTY("property"), // 设备属性:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 + EVENT("event"), // 设备事件:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 + SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 + CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置 + OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级 + REGISTER("register"),; // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册 public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java index 6a437dfca0..21b6c542a4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java @@ -1,10 +1,7 @@ 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.IotDeviceEventReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*; import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService; import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; import org.springframework.validation.annotation.Validated; @@ -46,6 +43,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi { return success(true); } + @Override + public CommonResult registerDevice(IotDeviceRegisterReqDTO registerReqDTO) { + deviceUpstreamService.registerDevice(registerReqDTO); + return success(true); + } + // ========== 插件相关 ========== @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java index 7d9ac6a0d0..5da2f501b6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; import lombok.Data; @@ -18,6 +19,7 @@ public class IotDeviceSaveReqVO { private String deviceKey; @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "设备名称不能为空") private String deviceName; @Schema(description = "备注名称", example = "张三") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java index 34fd35706b..37c7be44be 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.product; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -19,7 +19,7 @@ import lombok.*; @Builder @NoArgsConstructor @AllArgsConstructor -public class IotProductDO extends BaseDO { +public class IotProductDO extends TenantBaseDO { /** * 产品 ID diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index f77ae3a375..9ff0e81c29 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -4,6 +4,7 @@ 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.dal.dataobject.device.IotDeviceDO; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import javax.annotation.Nullable; import java.util.Collection; @@ -17,13 +18,23 @@ import java.util.List; public interface IotDeviceService { /** - * 创建设备 + * 【管理员】创建设备 * * @param createReqVO 创建信息 * @return 编号 */ Long createDevice(@Valid IotDeviceSaveReqVO createReqVO); + /** + * 【设备注册】创建设备 + * + * @param productKey 产品标识 + * @param deviceName 设备名称 + * @return 设备 + */ + IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey, + @NotEmpty(message = "设备名称不能为空") String deviceName); + /** * 更新设备 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 2680f47d13..6e20d724f9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -71,7 +71,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { } }); // 1.3 校验设备名称在同一产品下是否唯一 - if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) { + if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceName()) != null) { throw exception(DEVICE_NAME_EXISTS); } // 1.4 校验父设备是否为合法网关 @@ -82,23 +82,54 @@ public class IotDeviceServiceImpl implements IotDeviceService { // 1.5 校验分组存在 deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds()); - // 2.1 转换 VO 为 DO - // TODO @芋艿:各种 mqtt 是不是可以简化! - IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> { - o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType()); - // 生成并设置必要的字段 - o.setDeviceSecret(generateDeviceSecret()) - .setMqttClientId(generateMqttClientId()) - .setMqttUsername(generateMqttUsername(o.getDeviceName(), o.getProductKey())) - .setMqttPassword(generateMqttPassword()); - // 设置设备状态为未激活 - o.setState(IotDeviceStateEnum.INACTIVE.getState()); - }); - // 2.2 插入到数据库 + // 2. 插入到数据库 + IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class); + initDevice(device, product); deviceMapper.insert(device); return device.getId(); } + @Override + public IotDeviceDO createDevice(String productKey, String deviceName) { + // 1.1 校验产品是否存在 + IotProductDO product = TenantUtils.executeIgnore(() -> + productService.getProductByProductKey(productKey)); + if (product == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + // 1.2 校验设备标识是否唯一 + String deviceKey = generateDeviceKey(); + TenantUtils.executeIgnore(() -> { + if (deviceMapper.selectByDeviceKey(deviceKey) != null) { + throw exception(PRODUCT_KEY_EXISTS); + } + }); + return TenantUtils.execute(product.getTenantId(), () -> { + // 1.3 校验设备名称在同一产品下是否唯一 + if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), deviceName) != null) { + throw exception(DEVICE_NAME_EXISTS); + } + + // 2. 插入到数据库 + IotDeviceDO device = new IotDeviceDO().setDeviceName(deviceName).setDeviceKey(deviceKey); + initDevice(device, product); + deviceMapper.insert(device); + return device; + }); + } + + private void initDevice(IotDeviceDO device, IotProductDO product) { + device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType()); + // 生成并设置必要的字段 + // TODO @芋艿:各种 mqtt 是不是可以简化! + device.setDeviceSecret(generateDeviceSecret()) + .setMqttClientId(generateMqttClientId()) + .setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey())) + .setMqttPassword(generateMqttPassword()); + // 设置设备状态为未激活 + device.setState(IotDeviceStateEnum.INACTIVE.getState()); + } + @Override public void updateDevice(IotDeviceSaveReqVO updateReqVO) { updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java index 8bc0ffd697..330444e345 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.device.control; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceRegisterReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; import jakarta.validation.Valid; @@ -43,4 +44,11 @@ public interface IotDeviceUpstreamService { */ void reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO); + /** + * 注册设备 + * + * @param registerReqDTO 注册设备 DTO + */ + void registerDevice(IotDeviceRegisterReqDTO registerReqDTO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java index 1cbf3cd2a0..e3cd43bad0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java @@ -7,10 +7,7 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceUpstreamAbstractReqDTO; +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; @@ -165,6 +162,29 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { sendDeviceMessage(message, device); } + @Override + public void registerDevice(IotDeviceRegisterReqDTO registerReqDTO) { + // 1.1 注册设备 + log.info("[registerDevice][注册设备: {}]", registerReqDTO); + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache( + registerReqDTO.getProductKey(), registerReqDTO.getDeviceName()); + boolean register = device == null; + if (device == null) { + device = deviceService.createDevice(registerReqDTO.getProductKey(), registerReqDTO.getDeviceName()); + log.info("[registerDevice][请求({}) 成功注册设备({})]", registerReqDTO, device); + } + // 1.2 记录设备的最后时间 + updateDeviceLastTime(device, registerReqDTO); + + // 2. 发送设备消息 + if (register) { + IotDeviceMessage message = BeanUtils.toBean(registerReqDTO, IotDeviceMessage.class) + .setType(IotDeviceMessageTypeEnum.REGISTER.getType()) + .setIdentifier(IotDeviceMessageIdentifierEnum.REGISTER_REGISTER.getIdentifier()); + sendDeviceMessage(message, device); + } + } + private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { // 1. 【异步】记录设备与插件实例的映射 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java index 9a2e96f671..a317cacae9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java @@ -45,6 +45,14 @@ public interface IotProductService { */ IotProductDO getProduct(Long id); + /** + * 根据产品 key 获得产品 + * + * @param productKey 产品 key + * @return 产品 + */ + IotProductDO getProductByProductKey(String productKey); + /** * 校验产品存在 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index 3fa0bcf0b1..fc2300b7cb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -105,6 +105,11 @@ public class IotProductServiceImpl implements IotProductService { return productMapper.selectById(id); } + @Override + public IotProductDO getProductByProductKey(String productKey) { + return productMapper.selectByProductKey(productKey); + } + @Override public PageResult getProductPage(IotProductPageReqVO pageReqVO) { return productMapper.selectPage(pageReqVO);