【功能新增】IoT:设备状态上传的部分实现

This commit is contained in:
YunaiV 2025-01-29 11:38:51 +08:00
parent f6366d9b55
commit f5f8c418dc
14 changed files with 96 additions and 52 deletions

View File

@ -12,6 +12,7 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<!-- TODO 芋艿:需要在整理下,特别是 PF4J -->
<description>
物联网 模块 API暴露给其它模块调用
</description>
@ -42,6 +43,13 @@
</exclusions>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -3,11 +3,10 @@ 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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
@ -26,8 +25,8 @@ public interface IotDeviceUpstreamApi {
*
* @param updateReqDTO 更新设备状态 DTO
*/
@PutMapping(PREFIX + "/update-status")
CommonResult<Boolean> updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqDTO updateReqDTO);
@PostMapping(PREFIX + "/update-state")
CommonResult<Boolean> updateDeviceState(@Valid @RequestBody IotDeviceStateUpdateReqDTO updateReqDTO);
/**
* 上报设备属性数据

View File

@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.iot.api.device.dto;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@ -11,8 +9,6 @@ import java.util.Map;
* IoT 设备事件数据上报 Request DTO
*/
@Data
@SuperBuilder
@NoArgsConstructor
public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**

View File

@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.iot.api.device.dto;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@ -11,8 +9,6 @@ import java.util.Map;
* IoT 设备属性数据上报 Request DTO
*/
@Data
@SuperBuilder
@NoArgsConstructor
public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**

View File

@ -2,24 +2,20 @@ package cn.iocoder.yudao.module.iot.api.device.dto;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* IoT 设备状态更新 Request DTO
*/
@Data
@SuperBuilder
@NoArgsConstructor
public class IotDeviceStatusUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**
* 设备状态
*/
@NotEmpty(message = "设备状态不能为空")
@NotNull(message = "设备状态不能为空")
@InEnum(IotDeviceStateEnum.class) // 只使用在线离线
private Integer status;
private Integer state;
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.iot.api.device.dto;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.time.LocalDateTime;
@ -13,8 +13,6 @@ import java.time.LocalDateTime;
* @author 芋道源码
*/
@Data
@SuperBuilder
@NoArgsConstructor
public abstract class IotDeviceUpstreamAbstractReqDTO {
/**
@ -41,6 +39,7 @@ public abstract class IotDeviceUpstreamAbstractReqDTO {
/**
* 上报时间
*/
@JsonSerialize(using = TimestampLocalDateTimeSerializer.class) // 解决 iot plugins 序列化 LocalDateTime 是数组导致无法解析的问题
private LocalDateTime reportTime;
}

View File

@ -12,7 +12,11 @@ public enum IotDeviceMessageIdentifierEnum {
PROPERTY_GET("get"),
PROPERTY_SET("set"),
PROPERTY_REPORT("report");
PROPERTY_REPORT("report"),
STATE_ONLINE("online"),
STATE_OFFLINE("offline");
/**
* 标志符

View File

@ -3,7 +3,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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.service.device.upstream.IotDeviceUpstreamService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@ -23,8 +23,8 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
private IotDeviceUpstreamService deviceUpstreamService;
@Override
public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
deviceUpstreamService.updateDeviceStatus(updateReqDTO);
public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
deviceUpstreamService.updateDeviceState(updateReqDTO);
return success(true);
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.iot.emq.service;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@ -34,11 +33,11 @@ public class EmqxServiceImpl implements EmqxService {
String productKey = topic.split("/")[2];
String deviceName = topic.split("/")[3];
String message = new String(mqttMessage.getPayload());
IotDevicePropertyReportReqDTO createDTO = IotDevicePropertyReportReqDTO.builder()
// IotDevicePropertyReportReqDTO createDTO = IotDevicePropertyReportReqDTO.builder()
// .productKey(productKey)
// .deviceName(deviceName)
// .properties(message) // TODO 芋艿临时去掉看看
.build();
// .build();
// iotDeviceDataService.saveDeviceProperty(createDTO);
}
}

View File

@ -395,10 +395,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2.1 情况一属性上报
String requestId = IdUtil.fastSimpleUUID();
if (Objects.equals(reportReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) {
deviceUpstreamService.reportDeviceProperty(IotDevicePropertyReportReqDTO.builder()
.requestId(requestId).reportTime(LocalDateTime.now())
.productKey(device.getProductKey()).deviceName(device.getDeviceName())
.properties((Map<String, Object>) reportReqVO.getData()).build());
deviceUpstreamService.reportDeviceProperty(((IotDevicePropertyReportReqDTO)
new IotDevicePropertyReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())
.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
.setProperties((Map<String, Object>) reportReqVO.getData()));
return;
}
// 2.2 情况二事件上报

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.service.device.upstream;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
/**
* 设备上行 Service 接口
@ -18,7 +18,7 @@ public interface IotDeviceUpstreamService {
*
* @param updateReqDTO 更新设备状态 DTO
*/
void updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO);
void updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO);
/**
* 上报设备属性数据

View File

@ -1,15 +1,20 @@
package cn.iocoder.yudao.module.iot.service.device.upstream;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceUpstreamAbstractReqDTO;
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.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
@ -19,6 +24,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 设备上行 Service 实现类
@ -37,9 +43,39 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
private IotDeviceProducer deviceProducer;
@Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
log.info("[updateDeviceStatus][更新设备状态: {}]", updateReqDTO);
// TODO 芋艿插件状态
public void updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
Assert.isTrue(ObjectUtils.equalsAny(updateReqDTO.getState(),
IotDeviceStateEnum.ONLINE.getState(), IotDeviceStateEnum.OFFLINE.getState()),
"状态不合法");
// 1.1 获得设备
log.info("[updateDeviceState][更新设备状态: {}]", updateReqDTO);
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
updateReqDTO.getProductKey(), updateReqDTO.getDeviceName());
if (device == null) {
log.error("[updateDeviceState][设备({}/{}) 不存在]",
updateReqDTO.getProductKey(), updateReqDTO.getDeviceName());
return;
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, updateReqDTO);
// 1.3 当前状态一致不处理
if (Objects.equals(device.getState(), updateReqDTO.getState())) {
return;
}
// 2. 更新设备状态
TenantUtils.executeIgnore(() ->
deviceService.updateDeviceState(device.getId(), updateReqDTO.getState()));
// 3. TODO 芋艿子设备的关联
// 4. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(updateReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.STATE.getType())
.setIdentifier(ObjUtil.equals(updateReqDTO.getState(), IotDeviceStateEnum.ONLINE.getState())
? IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()
: IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier());
sendDeviceMessage(message, device);
}
@Override

View File

@ -4,7 +4,7 @@ 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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate;
@ -29,9 +29,9 @@ public class DeviceDataApiClient implements IotDeviceUpstreamApi {
// TODO @haohao返回结果不用 CommonResult
@Override
public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
String url = deviceDataUrl + URL_PREFIX + "/update-status";
return doPost(url, updateReqDTO, "updateDeviceStatus");
public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
String url = deviceDataUrl + URL_PREFIX + "/update-state";
return doPost(url, updateReqDTO, "updateDeviceState");
}
@Override

View File

@ -1,14 +1,18 @@
package cn.iocoder.yudao.module.iot.plugin.http.service;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import io.vertx.core.Handler;
import io.vertx.ext.web.RequestBody;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.util.Map;
@Slf4j
@ -43,20 +47,27 @@ public class HttpVertxHandler implements Handler<RoutingContext> {
String id = jsonData.getStr("id");
try {
IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder()
.productKey(productKey)
.deviceName(deviceName)
.properties((Map<String, Object>) requestBody.asJsonObject().getMap().get("properties"))
.build();
// TODO @haohaopluginKey 需要设置
// 设备上线
deviceDataApi.updateDeviceState(((IotDeviceStateUpdateReqDTO)
new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID())
.setPluginKey("http")
.setReportTime(LocalDateTime.now())
.setProductKey(productKey).setDeviceName(deviceName))
.setState(IotDeviceStateEnum.ONLINE.getState()));
deviceDataApi.reportDeviceProperty(reportReqDTO);
// 属性上报
deviceDataApi.reportDeviceProperty(((IotDevicePropertyReportReqDTO)
new IotDevicePropertyReportReqDTO().setRequestId(IdUtil.fastSimpleUUID())
.setPluginKey("http").setReportTime(LocalDateTime.now())
.setProductKey(productKey).setDeviceName(deviceName))
.setProperties((Map<String, Object>) requestBody.asJsonObject().getMap().get("properties")));
ctx.response()
.setStatusCode(200)
.putHeader("Content-Type", "application/json; charset=UTF-8")
.end(createResponseJson(200, new JSONObject(), id, "success",
"thing.event.property.post", "1.0").toString());
} catch (Exception e) {
log.error("[HttpVertxHandler] 上报属性数据失败", e);
ctx.response()