【功能新增】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> <packaging>jar</packaging>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<!-- TODO 芋艿:需要在整理下,特别是 PF4J -->
<description> <description>
物联网 模块 API暴露给其它模块调用 物联网 模块 API暴露给其它模块调用
</description> </description>
@ -42,6 +43,13 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 参数校验 --> <!-- 参数校验 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <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.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO; 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.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 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;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
/** /**
@ -26,8 +25,8 @@ public interface IotDeviceUpstreamApi {
* *
* @param updateReqDTO 更新设备状态 DTO * @param updateReqDTO 更新设备状态 DTO
*/ */
@PutMapping(PREFIX + "/update-status") @PostMapping(PREFIX + "/update-state")
CommonResult<Boolean> updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqDTO updateReqDTO); 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 jakarta.validation.constraints.NotEmpty;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map; import java.util.Map;
@ -11,8 +9,6 @@ import java.util.Map;
* IoT 设备事件数据上报 Request DTO * IoT 设备事件数据上报 Request DTO
*/ */
@Data @Data
@SuperBuilder
@NoArgsConstructor
public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO { 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 jakarta.validation.constraints.NotEmpty;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map; import java.util.Map;
@ -11,8 +9,6 @@ import java.util.Map;
* IoT 设备属性数据上报 Request DTO * IoT 设备属性数据上报 Request DTO
*/ */
@Data @Data
@SuperBuilder
@NoArgsConstructor
public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO { 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.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/** /**
* IoT 设备状态更新 Request DTO * IoT 设备状态更新 Request DTO
*/ */
@Data @Data
@SuperBuilder public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
@NoArgsConstructor
public class IotDeviceStatusUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/** /**
* 设备状态 * 设备状态
*/ */
@NotEmpty(message = "设备状态不能为空") @NotNull(message = "设备状态不能为空")
@InEnum(IotDeviceStateEnum.class) // 只使用在线离线 @InEnum(IotDeviceStateEnum.class) // 只使用在线离线
private Integer status; private Integer state;
} }

View File

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

View File

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

View File

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

View File

@ -395,10 +395,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2.1 情况一属性上报 // 2.1 情况一属性上报
String requestId = IdUtil.fastSimpleUUID(); String requestId = IdUtil.fastSimpleUUID();
if (Objects.equals(reportReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) { if (Objects.equals(reportReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) {
deviceUpstreamService.reportDeviceProperty(IotDevicePropertyReportReqDTO.builder() deviceUpstreamService.reportDeviceProperty(((IotDevicePropertyReportReqDTO)
.requestId(requestId).reportTime(LocalDateTime.now()) new IotDevicePropertyReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())
.productKey(device.getProductKey()).deviceName(device.getDeviceName()) .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
.properties((Map<String, Object>) reportReqVO.getData()).build()); .setProperties((Map<String, Object>) reportReqVO.getData()));
return; return;
} }
// 2.2 情况二事件上报 // 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.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO; 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 接口 * 设备上行 Service 接口
@ -18,7 +18,7 @@ public interface IotDeviceUpstreamService {
* *
* @param updateReqDTO 更新设备状态 DTO * @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; 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.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil; 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.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO; 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.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.api.device.dto.IotDeviceUpstreamAbstractReqDTO;
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;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; 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.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 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 org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Objects;
/** /**
* 设备上行 Service 实现类 * 设备上行 Service 实现类
@ -37,9 +43,39 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
private IotDeviceProducer deviceProducer; private IotDeviceProducer deviceProducer;
@Override @Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) { public void updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
log.info("[updateDeviceStatus][更新设备状态: {}]", updateReqDTO); Assert.isTrue(ObjectUtils.equalsAny(updateReqDTO.getState(),
// TODO 芋艿插件状态 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 @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.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO; 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.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 lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -29,9 +29,9 @@ public class DeviceDataApiClient implements IotDeviceUpstreamApi {
// TODO @haohao返回结果不用 CommonResult // TODO @haohao返回结果不用 CommonResult
@Override @Override
public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) { public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
String url = deviceDataUrl + URL_PREFIX + "/update-status"; String url = deviceDataUrl + URL_PREFIX + "/update-state";
return doPost(url, updateReqDTO, "updateDeviceStatus"); return doPost(url, updateReqDTO, "updateDeviceState");
} }
@Override @Override

View File

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