【功能新增】IoT:设备注册 register 逻辑

This commit is contained in:
YunaiV 2025-02-08 19:31:50 +08:00
parent d718f80108
commit 5f7bb8041f
13 changed files with 146 additions and 35 deletions

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.iot.api.device; package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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.*;
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.enums.ApiConstants; import cn.iocoder.yudao.module.iot.enums.ApiConstants;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -47,6 +44,15 @@ public interface IotDeviceUpstreamApi {
@PostMapping(PREFIX + "/report-event") @PostMapping(PREFIX + "/report-event")
CommonResult<Boolean> reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO); CommonResult<Boolean> reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO);
// TODO @芋艿这个需要 plugins 接入下
/**
* 注册设备
*
* @param registerReqDTO 注册设备 DTO
*/
@PostMapping(PREFIX + "/register")
CommonResult<Boolean> registerDevice(@Valid @RequestBody IotDeviceRegisterReqDTO registerReqDTO);
// ========== 插件相关 ========== // ========== 插件相关 ==========
/** /**

View File

@ -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 {
}

View File

@ -26,7 +26,11 @@ public enum IotDeviceMessageIdentifierEnum {
OTA_UPGRADE("upgrade"), // 下行 OTA_UPGRADE("upgrade"), // 下行
OTA_PULL("pull"), // 上行 OTA_PULL("pull"), // 上行
OTA_PROGRESS("progress"), // 上行 OTA_PROGRESS("progress"), // 上行
OTA_REPORT("report"),; // 上行 OTA_REPORT("report"), // 上行
REGISTER_REGISTER("register"), // 上行
REGISTER_SUB_REGISTER("sub_register"), // 上行
REGISTER_SUB_UNREGISTER("sub_unregister"),; // 下行
/** /**
* 标志符 * 标志符

View File

@ -14,11 +14,12 @@ import java.util.Arrays;
public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> { public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> {
STATE("state"), // 设备状态 STATE("state"), // 设备状态
PROPERTY("property"), // 设备属性 PROPERTY("property"), // 设备属性可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性事件服务
EVENT("event"), // 设备事件 EVENT("event"), // 设备事件可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性事件服务
SERVICE("service"), // 设备服务 SERVICE("service"), // 设备服务可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性事件服务
CONFIG("config"), // 设备配置 CONFIG("config"), // 设备配置可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
OTA("ota"),; // 设备 OTA 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); public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.iot.api.device; package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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.*;
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.service.device.control.IotDeviceUpstreamService; import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -46,6 +43,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
return success(true); return success(true);
} }
@Override
public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
deviceUpstreamService.registerDevice(registerReqDTO);
return success(true);
}
// ========== 插件相关 ========== // ========== 插件相关 ==========
@Override @Override

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
@ -18,6 +19,7 @@ public class IotDeviceSaveReqVO {
private String deviceKey; private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@NotEmpty(message = "设备名称不能为空")
private String deviceName; private String deviceName;
@Schema(description = "备注名称", example = "张三") @Schema(description = "备注名称", example = "张三")

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.product; 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.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@ -19,7 +19,7 @@ import lombok.*;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class IotProductDO extends BaseDO { public class IotProductDO extends TenantBaseDO {
/** /**
* 产品 ID * 产品 ID

View File

@ -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.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
@ -17,13 +18,23 @@ import java.util.List;
public interface IotDeviceService { public interface IotDeviceService {
/** /**
* 创建设备 * 管理员创建设备
* *
* @param createReqVO 创建信息 * @param createReqVO 创建信息
* @return 编号 * @return 编号
*/ */
Long createDevice(@Valid IotDeviceSaveReqVO createReqVO); Long createDevice(@Valid IotDeviceSaveReqVO createReqVO);
/**
* 设备注册创建设备
*
* @param productKey 产品标识
* @param deviceName 设备名称
* @return 设备
*/
IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
@NotEmpty(message = "设备名称不能为空") String deviceName);
/** /**
* 更新设备 * 更新设备
* *

View File

@ -71,7 +71,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
}); });
// 1.3 校验设备名称在同一产品下是否唯一 // 1.3 校验设备名称在同一产品下是否唯一
if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) { if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceName()) != null) {
throw exception(DEVICE_NAME_EXISTS); throw exception(DEVICE_NAME_EXISTS);
} }
// 1.4 校验父设备是否为合法网关 // 1.4 校验父设备是否为合法网关
@ -82,23 +82,54 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 1.5 校验分组存在 // 1.5 校验分组存在
deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds()); deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
// 2.1 转换 VO DO // 2. 插入到数据库
// TODO @芋艿各种 mqtt 是不是可以简化 IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class);
IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> { initDevice(device, product);
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 插入到数据库
deviceMapper.insert(device); deviceMapper.insert(device);
return device.getId(); 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 @Override
public void updateDevice(IotDeviceSaveReqVO updateReqVO) { public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新 updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新

View File

@ -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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; 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.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@ -43,4 +44,11 @@ public interface IotDeviceUpstreamService {
*/ */
void reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO); void reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO);
/**
* 注册设备
*
* @param registerReqDTO 注册设备 DTO
*/
void registerDevice(IotDeviceRegisterReqDTO registerReqDTO);
} }

View File

@ -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.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 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.*;
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.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; 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.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
@ -165,6 +162,29 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
sendDeviceMessage(message, device); 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) { private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
// 1. 异步记录设备与插件实例的映射 // 1. 异步记录设备与插件实例的映射
pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());

View File

@ -45,6 +45,14 @@ public interface IotProductService {
*/ */
IotProductDO getProduct(Long id); IotProductDO getProduct(Long id);
/**
* 根据产品 key 获得产品
*
* @param productKey 产品 key
* @return 产品
*/
IotProductDO getProductByProductKey(String productKey);
/** /**
* 校验产品存在 * 校验产品存在
* *

View File

@ -105,6 +105,11 @@ public class IotProductServiceImpl implements IotProductService {
return productMapper.selectById(id); return productMapper.selectById(id);
} }
@Override
public IotProductDO getProductByProductKey(String productKey) {
return productMapper.selectByProductKey(productKey);
}
@Override @Override
public PageResult<IotProductDO> getProductPage(IotProductPageReqVO pageReqVO) { public PageResult<IotProductDO> getProductPage(IotProductPageReqVO pageReqVO) {
return productMapper.selectPage(pageReqVO); return productMapper.selectPage(pageReqVO);