【功能优化】IoT:

1. DeviceDataApi => IotDeviceUpstreamApi,并新建 upstream 包
2. ThingModelMessage => IotDeviceMessage 设备消息
3. 基于 spring event 异步消费 IotDeviceMessage,并实现 IotDeviceLogMessageConsumer 记录日志
This commit is contained in:
YunaiV 2025-01-27 14:15:07 +08:00
parent f4ad3e9d2d
commit 8089f3a319
46 changed files with 648 additions and 790 deletions

View File

@ -13,7 +13,7 @@ import java.util.Set;
/** /**
* 基于 MyBatis Plus 多租户的功能实现 DB 层面的多租户的功能 * 基于 MyBatis Plus 多租户的功能实现 DB 层面的多租户的功能
* *
* @author * @author 芋道源码
*/ */
public class TenantDatabaseInterceptor implements TenantLineHandler { public class TenantDatabaseInterceptor implements TenantLineHandler {

View File

@ -45,6 +45,7 @@ public class TenantUtils {
* *
* @param tenantId 租户编号 * @param tenantId 租户编号
* @param callable 逻辑 * @param callable 逻辑
* @return 结果
*/ */
public static <V> V execute(Long tenantId, Callable<V> callable) { public static <V> V execute(Long tenantId, Callable<V> callable) {
Long oldTenantId = TenantContextHolder.getTenantId(); Long oldTenantId = TenantContextHolder.getTenantId();
@ -78,6 +79,25 @@ public class TenantUtils {
} }
} }
/**
* 忽略租户执行对应的逻辑
*
* @param callable 逻辑
* @return 结果
*/
public static <V> V executeIgnore(Callable<V> callable) {
Boolean oldIgnore = TenantContextHolder.isIgnore();
try {
TenantContextHolder.setIgnore(true);
// 执行逻辑
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
TenantContextHolder.setIgnore(oldIgnore);
}
}
/** /**
* 将多租户编号添加到 header * 将多租户编号添加到 header
* *

View File

@ -5,49 +5,44 @@ 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.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.enums.ApiConstants; import cn.iocoder.yudao.module.iot.enums.ApiConstants;
import jakarta.annotation.security.PermitAll;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
// TODO 芋艿名字可能看情况改下
/** /**
* 设备数据 API * 设备数据 Upstream 上行 API
*
* 目的设备 -> 插件 -> 服务端
* *
* @author haohao * @author haohao
*/ */
public interface DeviceDataApi { public interface IotDeviceUpstreamApi {
// TODO @芋艿可能会调整 String PREFIX = ApiConstants.PREFIX + "/device/upstream";
String PREFIX = ApiConstants.PREFIX + "/device-data";
/** /**
* 更新设备状态 * 更新设备状态
* *
* @param updateReqDTO 更新请求 * @param updateReqDTO 更新设备状态 DTO
*/ */
@PutMapping(PREFIX + "/update-status") @PutMapping(PREFIX + "/update-status")
@PermitAll // TODO 芋艿后续看看怎么优化下
CommonResult<Boolean> updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqDTO updateReqDTO); CommonResult<Boolean> updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqDTO updateReqDTO);
/**
* 上报设备属性数据
*
* @param reportReqDTO 上报设备属性数据 DTO
*/
@PostMapping(PREFIX + "/report-property")
CommonResult<Boolean> reportDevicePropertyData(@Valid @RequestBody IotDevicePropertyReportReqDTO reportReqDTO);
/** /**
* 上报设备事件数据 * 上报设备事件数据
* *
* @param reportReqDTO 设备事件 * @param reportReqDTO 设备事件
*/ */
@PostMapping(PREFIX + "/report-event") @PostMapping(PREFIX + "/report-event")
@PermitAll // TODO 芋艿后续看看怎么优化下
CommonResult<Boolean> reportDeviceEventData(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO); CommonResult<Boolean> reportDeviceEventData(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO);
/**
* 上报设备属性数据
*
* @param reportReqDTO 设备数据
*/
@PostMapping(PREFIX + "/report-property")
@PermitAll // TODO 芋艿后续看看怎么优化下
CommonResult<Boolean> reportDevicePropertyData(@Valid @RequestBody IotDevicePropertyReportReqDTO reportReqDTO);
} }

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.module.iot.api.device.dto; package cn.iocoder.yudao.module.iot.api.device.dto;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map; import java.util.Map;
@ -12,24 +11,9 @@ import java.util.Map;
* IoT 设备事件数据上报 Request DTO * IoT 设备事件数据上报 Request DTO
*/ */
@Data @Data
@SuperBuilder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
@Builder
public class IotDeviceEventReportReqDTO {
// TODO 芋艿要不要 id
// TODO 芋艿要不要 time
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
/** /**
* 事件标识 * 事件标识

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.module.iot.api.device.dto; package cn.iocoder.yudao.module.iot.api.device.dto;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map; import java.util.Map;
@ -12,28 +11,14 @@ import java.util.Map;
* IoT 设备属性数据上报 Request DTO * IoT 设备属性数据上报 Request DTO
*/ */
@Data @Data
@SuperBuilder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
@Builder
public class IotDevicePropertyReportReqDTO {
// TODO 芋艿要不要 id
// TODO 芋艿要不要 time
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
/** /**
* 属性参数 * 属性参数
*/ */
@NotEmpty(message = "属性参数不能为空") @NotEmpty(message = "属性参数不能为空")
private Map<String, Object> params; private Map<String, Object> properties;
} }

View File

@ -3,33 +3,18 @@ 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.IotDeviceStatusEnum; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/** /**
* IoT 设备状态更新 Request DTO * IoT 设备状态更新 Request DTO
*/ */
@Data @Data
@SuperBuilder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor public class IotDeviceStatusUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
@Builder
public class IotDeviceStatusUpdateReqDTO {
// TODO 芋艿要不要 id
// TODO 芋艿要不要 time
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
/** /**
* 设备状态 * 设备状态
*/ */

View File

@ -0,0 +1,46 @@
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.time.LocalDateTime;
/**
* IoT 设备上行的抽象 Request DTO
*
* @author 芋道源码
*/
@Data
@SuperBuilder
@NoArgsConstructor
public abstract class IotDeviceUpstreamAbstractReqDTO {
/**
* 请求编号
*/
private String requestId;
/**
* 插件标识
*/
private String pluginKey;
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
/**
* 上报时间
*/
private LocalDateTime reportTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.iot.enums.device;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* IoT 设备消息标识符枚举
*/
@Getter
@RequiredArgsConstructor
public enum IotDeviceMessageIdentifierEnum {
PROPERTY_GET("get"),
PROPERTY_SET("set"),
PROPERTY_REPORT("report");
/**
* 标志符
*/
private final String identifier;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.iot.enums.device;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* IoT 设备消息类型枚举
*/
@Getter
@RequiredArgsConstructor
public enum IotDeviceMessageTypeEnum {
STATE("state"), // 设备状态
PROPERTY("property"), // 设备属性
EVENT("event"); // 设备事件
/**
* 属性
*/
private final String type;
}

View File

@ -4,7 +4,7 @@ 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.IotDeviceStatusUpdateReqDTO;
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 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;
@ -13,28 +13,30 @@ import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/** /**
* 设备数据 API 实现类 * * 设备数据 Upstream 上行 API 实现类
*/ */
@RestController @RestController
@Validated @Validated
public class DeviceDataApiImpl implements DeviceDataApi { public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
@Resource @Resource
private IotDevicePropertyDataService deviceDataService; private IotDeviceUpstreamService deviceUpstreamService;
@Override @Override
public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) { public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
return success(true); deviceUpstreamService.updateDeviceStatus(updateReqDTO);
}
@Override
public CommonResult<Boolean> reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO) {
return success(true); return success(true);
} }
@Override @Override
public CommonResult<Boolean> reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) { public CommonResult<Boolean> reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) {
deviceDataService.saveDeviceData(reportReqDTO); deviceUpstreamService.reportDevicePropertyData(reportReqDTO);
return success(true);
}
@Override
public CommonResult<Boolean> reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO) {
deviceUpstreamService.reportDeviceEventData(reportReqDTO);
return success(true); return success(true);
} }

View File

@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService; import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -27,13 +27,13 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
public class IotDeviceDataController { public class IotDeviceDataController {
@Resource @Resource
private IotDevicePropertyDataService deviceDataService; private IotDevicePropertyService deviceDataService;
@Resource @Resource
private IotDeviceLogDataService iotDeviceLogDataService; private IotDeviceLogService iotDeviceLogDataService;
@Resource // TODO @superservice 之间不用空行原因是这样更简洁空行主要是为了间隔提升可读性 @Resource // TODO @superservice 之间不用空行原因是这样更简洁空行主要是为了间隔提升可读性
private IotDeviceLogDataService deviceLogDataService; private IotDeviceLogService deviceLogDataService;
// TODO @浩浩这里的 /latest-list包括方法名 // TODO @浩浩这里的 /latest-list包括方法名
@GetMapping("/latest") @GetMapping("/latest")

View File

@ -36,6 +36,8 @@ public class IotDeviceDO extends BaseDO {
private Long id; private Long id;
/** /**
* 设备唯一标识符全局唯一用于识别设备 * 设备唯一标识符全局唯一用于识别设备
*
* 类似阿里云 <a href="https://help.aliyun.com/zh/iot/developer-reference/api-querydeviceinfo">QueryDeviceInfo</a> IotInstanceId
*/ */
private String deviceKey; private String deviceKey;
/** /**

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device; package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -19,37 +23,51 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class IotDeviceLogDO { public class IotDeviceLogDO {
// TODO @芋艿消息 ID 的生成逻辑
/** /**
* 消息 ID * 日志编号
*
* 通过 {@link IdUtil#fastSimpleUUID()} 生成
*/ */
private String id; private String id;
/**
* 请求编号
*
* 对应 {@link IotDeviceMessage#getRequestId()} 字段
*/
private String requestId;
/** /**
* 产品标识 * 产品标识
* <p> * <p>
* 关联 {@link IotProductDO#getProductKey()} * 关联 {@link IotProductDO#getProductKey()}
*/ */
private String productKey; private String productKey;
/**
* 设备名称
*
* 关联 {@link IotDeviceDO#getDeviceName()}
*/
private String deviceName;
/** /**
* 设备标识 * 设备标识
* <p> * <p>
* 关联 {@link IotDeviceDO#getDeviceKey()}} * 关联 {@link IotDeviceDO#getDeviceKey()}}
*/ */
private String deviceKey; private String deviceKey; // 非存储字段用于 TDengine TAG
// TODO @super枚举类
/** /**
* 日志类型 * 日志类型
*
* 枚举 {@link IotDeviceMessageTypeEnum}
*/ */
private String type; private String type;
// TODO @super枚举类
/** /**
* 标识符用于标识具体的属性事件或服务 * 标识符
*
* 枚举 {@link IotDeviceMessageIdentifierEnum}
*/ */
private String subType; private String identifier;
/** /**
* 数据内容 * 数据内容

View File

@ -1,70 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* 物模型消息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ThingModelMessage {
/**
* 消息ID
*/
private String id;
/**
* 扩展功能的参数
*/
private Object sys;
/**
* 请求方法 例如thing.event.property.post
*/
private String method;
/**
* 请求参数
*/
private Object params;
/**
* 属性上报时间戳
*/
private Long time;
/**
* 设备信息
*/
private String productKey;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备 key
*/
private String deviceKey;
/**
* 转换为 Map 类型
*/
public Map<String, Object> dataToMap() {
Map<String, Object> mapData = new HashMap<>();
if (params instanceof Map) {
((Map<?, ?>) params).forEach((key, value) -> mapData.put(key.toString(), value));
}
return mapData;
}
}

View File

@ -22,21 +22,13 @@ public interface IotDeviceLogDataMapper {
*/ */
void createDeviceLogSTable(); void createDeviceLogSTable();
// TODO @super单个参数不用加 @Param /**
// TODO @芋艿在瞅瞅 * 查询设备日志表是否存在
//讨论艿菇这里有些特殊情况我也学习了一下这块知识 *
// 如果使用的是Java 8及以上版本并且编译器保留了参数名通过编译器选项-parameters启用则可以去掉@Param注解MyBatis会自动使用参数的实际名称 * @return 存在则返回表名不存在则返回 null
// 但在TDengine中 @Param去掉后TDengine会报错以下是大模型的回答 */
// 不用加 @Param在普通的 MySQL 场景下是正确的 - 对于 MyBatis当方法只有一个参数时确实可以不用添加 @Param 注解 String showDeviceLogSTable();
//但是在 TDengine 的场景下情况不同
//TDengine 的特殊性
//TDengine 使用特殊的 SQL 语法
//需要处理超级表(STable)和子表的概念
//参数绑定的方式与普通 MySQL 不同
//为什么这里必须要 @Param
//XML 中使用了 ${log.deviceKey} 这样的参数引用方式
//需要在 SQL 中动态构建表名device_log_${log.deviceKey}
//没有 @Param("log") 的话MyBatis 无法正确解析参数
/** /**
* 插入设备日志数据 * 插入设备日志数据
* *
@ -44,7 +36,7 @@ public interface IotDeviceLogDataMapper {
* *
* @param log 设备日志数据 * @param log 设备日志数据
*/ */
void insert(@Param("log") IotDeviceLogDO log); void insert(IotDeviceLogDO log);
/** /**
* 获得设备日志分页 * 获得设备日志分页
@ -62,12 +54,4 @@ public interface IotDeviceLogDataMapper {
*/ */
Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO); Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO);
// TODO @芋艿这个方法名后续看看叫啥好
/**
* 查询设备日志表是否存在
*
* @return 不存在返回 null
*/
Object checkDeviceLogSTableExists();
} }

View File

@ -1,7 +1,7 @@
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.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 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;
import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttClient;
@ -20,7 +20,7 @@ import org.springframework.scheduling.annotation.Async;
public class EmqxServiceImpl implements EmqxService { public class EmqxServiceImpl implements EmqxService {
@Resource @Resource
private IotDevicePropertyDataService iotDeviceDataService; private IotDevicePropertyService iotDeviceDataService;
// TODO 多线程处理消息 // TODO 多线程处理消息
@Override @Override
@ -35,8 +35,8 @@ public class EmqxServiceImpl implements EmqxService {
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.saveDeviceData(createDTO); iotDeviceDataService.saveDeviceData(createDTO);

View File

@ -9,6 +9,7 @@ import org.springframework.stereotype.Component;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Map; import java.util.Map;
// TODO @haohao这个还需要的么
/** /**
* TaosAspect 是一个处理 Taos 数据库返回值的切面 * TaosAspect 是一个处理 Taos 数据库返回值的切面
*/ */

View File

@ -0,0 +1,29 @@
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();
}
};
}
}

View File

@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.iot.framework.security.core;

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.framework.tdengine.config; package cn.iocoder.yudao.module.iot.framework.tdengine.config;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService; import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
@ -17,7 +17,7 @@ import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor @RequiredArgsConstructor
public class TDengineTableInitConfiguration implements ApplicationRunner { public class TDengineTableInitConfiguration implements ApplicationRunner {
private final IotDeviceLogDataService deviceLogService; private final IotDeviceLogService deviceLogService;
@Override @Override
public void run(ApplicationArguments args) { public void run(ApplicationArguments args) {

View File

@ -8,6 +8,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
// TODO 芋艿后续再看看
/** /**
* 插件实例 Job * 插件实例 Job
* *

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.iot.mq.consumer.device;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 针对 {@link IotDeviceMessage} 的消费者记录设备日志
*
* @author 芋道源码
*/
@Component
@Slf4j
public class IotDeviceLogMessageConsumer {
@Resource
private IotDeviceLogService deviceLogService;
@EventListener
@Async
public void onMessage(IotDeviceMessage message) {
log.info("[onMessage][消息内容({})]", message);
deviceLogService.createDeviceLog(message);
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.iot.mq.consumer.device;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link IotDeviceMessage} 的消费者记录设备属性
*
* @author alwayssuper
*/
@Component
@Slf4j
public class IotDevicePropertyMessageConsumer {
@Resource
private IotDevicePropertyService deviceDataService;
@EventListener
@Async
public void onMessage(IotDeviceMessage message) {
log.info("[onMessage][消息内容({})]", message);
// 设备日志记录
// TODO @芋艿重新写下
// deviceLogDataService.createDeviceLog(message);
}
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.yudao.module.iot.mq.consumer.deviceconsumer;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService;
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link ThingModelMessage} 的消费者
*
* @author alwayssuper
*/
@Component
@Slf4j
public class DeviceConsumer {
@Resource
private IotDeviceLogDataService deviceLogDataService;
@Resource
private IotDevicePropertyDataService deviceDataService;
// TODO @芋艿这块先用ThingModelMessage后续看看用啥替代
@EventListener
@Async
public void onMessage(ThingModelMessage message) {
log.info("[onMessage][消息内容({})]", message);
//TODO:数据插入这块整体写的比较混乱整体借鉴了浩浩哥之前写的逻辑目前是通过模拟设备科插入数据了但之前的逻辑有大量弃用的部分后续看看怎么完善
// 设备数据记录
deviceDataService.saveDeviceDataTest(message);
// 设备日志记录
deviceLogDataService.saveDeviceLog(message);
}
}

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿未来实现一个 IotRuleMessageConsumer
*/
package cn.iocoder.yudao.module.iot.mq.consumer.rule;

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.iot.mq.message;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 设备消息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IotDeviceMessage {
/**
* 请求编号
*/
private String requestId;
/**
* 设备信息
*/
private String productKey;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备标识
*/
private String deviceKey;
/**
* 消息类型
*
* 枚举 {@link IotDeviceMessageTypeEnum}
*/
private String type;
/**
* 标识符
*
* 枚举 {@link IotDeviceMessageIdentifierEnum}
*/
private String identifier;
/**
* 请求参数
*
* 例如说属性上报的 properties事件上报的 params
*/
private Object data;
/**
* 上报时间
*/
private LocalDateTime reportTime;
// TODO @芋艿 code;
}

View File

@ -1,4 +0,0 @@
/**
* 消息队列的消息
*/
package cn.iocoder.yudao.module.iot.mq.message;

View File

@ -1,31 +1,30 @@
package cn.iocoder.yudao.module.iot.mq.producer.simulatesend; package cn.iocoder.yudao.module.iot.mq.producer.device;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
// TODO @芋艿@alwayssuper是不是还没用起来哈Producer 最好属于某个模块
/** /**
* SimulateSend 模拟设备上报 Producer * Iot 设备相关消息 Producer
* *
* @author alwayssuper * @author alwayssuper
* @since 2024/12/17 16:35 * @since 2024/12/17 16:35
*/ */
@Slf4j @Slf4j
@Component @Component
public class SimulateSendProducer { public class IotDeviceProducer {
@Resource @Resource
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
/** /**
* 发送 {@link ThingModelMessage} 消息 * 发送 {@link IotDeviceMessage} 消息
* *
* @param thingModelMessage 物模型消息 * @param thingModelMessage 物模型消息
*/ */
public void sendSimulateMessage(ThingModelMessage thingModelMessage) { public void sendDeviceMessage(IotDeviceMessage thingModelMessage) {
applicationContext.publishEvent(thingModelMessage); applicationContext.publishEvent(thingModelMessage);
} }

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿临时占位
*/
package cn.iocoder.yudao.module.iot.mq.producer;

View File

@ -1,77 +0,0 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* IoT 设备日志数据 Service 实现了
*
* @author alwayssuper
*/
@Service
@Slf4j
@Validated
public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{
@Resource
private IotDeviceLogDataMapper deviceLogDataMapper;
@Override
public void defineDeviceLog() {
if (deviceLogDataMapper.checkDeviceLogSTableExists() != null) {
log.info("[defineDeviceLog][设备日志超级表已存在,跳过创建]");
return;
}
log.info("[defineDeviceLog][设备日志超级表不存在,开始创建]");
deviceLogDataMapper.createDeviceLogSTable();
log.info("[defineDeviceLog][设备日志超级表不存在,创建完成]");
}
@Override
public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) {
// 1. 转换请求对象为 DO
IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class);
// 2. 处理时间字段
// iotDeviceLogDO.setTs(currentTime); // TODO @superTS在SQL中直接NOW 咱们的TS数据获取是走哪一种 now()
// 3. 插入数据
deviceLogDataMapper.insert(iotDeviceLogDO);
}
@Override
public PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) {
// TODO @芋艿增加一个表不存在的 try catch
List<IotDeviceLogDO> list = deviceLogDataMapper.selectPage(pageReqVO);
Long total = deviceLogDataMapper.selectCount(pageReqVO);
return new PageResult<>(list, total);
}
@Override
public void saveDeviceLog(ThingModelMessage message) {
IotDeviceLogDO log = IotDeviceLogDO.builder()
.id(message.getId())
.deviceKey(message.getDeviceKey())
.productKey(message.getProductKey())
.type(message.getMethod()) // 消息类型使用method作为类型 TODO 芋艿在看看
.subType("property") // TODO 芋艿:这块先写死后续优化
.content(JSONUtil.toJsonStr(message)) // TODO 芋艿:后续优化
.reportTime(message.getTime()) // 上报时间 TODO 芋艿在想想时间
.build();
deviceLogDataMapper.insert(log);
}
}

View File

@ -1,17 +1,16 @@
package cn.iocoder.yudao.module.iot.service.device; package cn.iocoder.yudao.module.iot.service.device.data;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
/** /**
* IoT 设备日志数据 Service 接口 * IoT 设备日志数据 Service 接口
* *
* @author alwayssuper * @author alwayssuper
*/ */
public interface IotDeviceLogDataService { public interface IotDeviceLogService {
/** /**
* 初始化 TDengine 超级表 * 初始化 TDengine 超级表
@ -23,11 +22,9 @@ public interface IotDeviceLogDataService {
/** /**
* 插入设备日志 * 插入设备日志
* *
* 当该设备第一次插入日志时自动创建该设备的设备日志子表 * @param message 设备数据
*
* @param simulatorReqVO 设备日志模拟数据
*/ */
void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO); void createDeviceLog(IotDeviceMessage message);
/** /**
* 获得设备日志分页 * 获得设备日志分页
@ -37,11 +34,4 @@ public interface IotDeviceLogDataService {
*/ */
PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO); PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO);
/**
* 插入设备日志
*
* @param message 设备数据
*/
void saveDeviceLog(ThingModelMessage message);
} }

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.iot.service.device.data;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* IoT 设备日志数据 Service 实现类
*
* @author alwayssuper
*/
@Service
@Slf4j
@Validated
public class IotDeviceLogServiceImpl implements IotDeviceLogService {
@Resource
private IotDeviceLogDataMapper deviceLogDataMapper;
@Override
public void defineDeviceLog() {
if (StrUtil.isNotEmpty(deviceLogDataMapper.showDeviceLogSTable())) {
log.info("[defineDeviceLog][设备日志超级表已存在,创建跳过]");
return;
}
log.info("[defineDeviceLog][设备日志超级表不存在,创建开始...]");
deviceLogDataMapper.createDeviceLogSTable();
log.info("[defineDeviceLog][设备日志超级表不存在,创建成功]");
}
@Override
public void createDeviceLog(IotDeviceMessage message) {
IotDeviceLogDO log = BeanUtils.toBean(message, IotDeviceLogDO.class)
.setId(IdUtil.fastSimpleUUID())
.setContent(JsonUtils.toJsonString(message.getData()));
deviceLogDataMapper.insert(log);
}
@Override
public PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) {
// TODO @芋艿增加一个表不存在的 try catch
List<IotDeviceLogDO> list = deviceLogDataMapper.selectPage(pageReqVO);
Long total = deviceLogDataMapper.selectCount(pageReqVO);
return new PageResult<>(list, total);
}
}

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.iot.service.device; package cn.iocoder.yudao.module.iot.service.device.data;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
@ -16,7 +15,7 @@ import java.util.Map;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
public interface IotDevicePropertyDataService { public interface IotDevicePropertyService {
/** /**
* 定义设备属性数据的结构 * 定义设备属性数据的结构
@ -32,13 +31,6 @@ public interface IotDevicePropertyDataService {
*/ */
void saveDeviceData(IotDevicePropertyReportReqDTO createDTO); void saveDeviceData(IotDevicePropertyReportReqDTO createDTO);
/**
* 保存设备数据
*
* @param thingModelMessage 设备数据
*/
void saveDeviceDataTest(ThingModelMessage thingModelMessage);
/** /**
* 模拟设备 * 模拟设备
* *

View File

@ -1,9 +1,8 @@
package cn.iocoder.yudao.module.iot.service.device; package cn.iocoder.yudao.module.iot.service.device.data;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
@ -16,18 +15,15 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO; import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.IotConstants;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
import cn.iocoder.yudao.module.iot.mq.producer.simulatesend.SimulateSendProducer; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService; import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@ -53,7 +49,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_DATA_C
*/ */
@Service @Service
@Slf4j @Slf4j
public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataService { public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
/** /**
* 物模型的数据类型 TDengine 数据类型的映射关系 * 物模型的数据类型 TDengine 数据类型的映射关系
@ -76,25 +72,16 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
@Resource @Resource
private IotDeviceService deviceService; private IotDeviceService deviceService;
@Resource @Resource
private IotThingModelMessageService thingModelMessageService;
@Resource
private IotThingModelService thingModelService; private IotThingModelService thingModelService;
@Resource @Resource
private IotProductService productService; private IotProductService productService;
@Resource
private SimulateSendProducer simulateSendProducer;
@Resource
private TdEngineDMLMapper tdEngineDMLMapper;
@Resource @Resource
private DeviceDataRedisDAO deviceDataRedisDAO; private DeviceDataRedisDAO deviceDataRedisDAO;
@Resource @Resource
private IotDevicePropertyDataMapper devicePropertyDataMapper; private IotDevicePropertyDataMapper devicePropertyDataMapper;
@Override @Override
public void defineDevicePropertyData(Long productId) { public void defineDevicePropertyData(Long productId) {
// 1.1 查询产品和物模型 // 1.1 查询产品和物模型
@ -144,28 +131,8 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
// 1. 根据产品 key 和设备名称获得设备信息 // 1. 根据产品 key 和设备名称获得设备信息
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(createDTO.getProductKey(), createDTO.getDeviceName()); IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(createDTO.getProductKey(), createDTO.getDeviceName());
// 2. 解析消息保存数据 // 2. 解析消息保存数据
JSONObject jsonObject = new JSONObject(createDTO.getParams()); JSONObject jsonObject = new JSONObject(createDTO.getProperties());
log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", createDTO.getProductKey(), createDTO.getDeviceName(), jsonObject); log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", createDTO.getProductKey(), createDTO.getDeviceName(), jsonObject);
ThingModelMessage thingModelMessage = ThingModelMessage.builder()
.id(jsonObject.getStr("id"))
.sys(jsonObject.get("sys"))
.method(jsonObject.getStr("method"))
.params(jsonObject.get("params"))
.time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
.productKey(createDTO.getProductKey())
.deviceName(createDTO.getDeviceName())
.deviceKey(device.getDeviceKey())
.build();
thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
}
//TODO @芋艿:后续捋一捋这块逻辑先借鉴一下目前的代码
@Override
public void saveDeviceDataTest(ThingModelMessage thingModelMessage) {
// 1. 根据产品 key 和设备名称获得设备信息
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(thingModelMessage.getProductKey(), thingModelMessage.getDeviceName());
// 2. 保存数据
thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
} }
//TODO @芋艿:copy saveDeviceData 的逻辑后续看看这块怎么优化 //TODO @芋艿:copy saveDeviceData 的逻辑后续看看这块怎么优化
@ -182,20 +149,17 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
throw exception(DEVICE_DATA_CONTENT_JSON_PARSE_ERROR); throw exception(DEVICE_DATA_CONTENT_JSON_PARSE_ERROR);
} }
// TODO @芋艿后续优化
// 3. 构建物模型消息 // 3. 构建物模型消息
ThingModelMessage thingModelMessage = ThingModelMessage.builder() // IotDeviceMessage thingModelMessage = IotDeviceMessage.builder()
.id(IdUtil.fastSimpleUUID()) // TODO:后续优化 // .params(contentJson) // content 作为 params
.sys(null)// TODO:这块先写死后续优化 // .time(simulatorReqVO.getReportTime()) // 使用上报时间
.method("thing.event.property.post") // TODO:这块先写死后续优化 // .productKey(simulatorReqVO.getProductKey())
.params(contentJson) // content 作为 params // .deviceName(device.getDeviceName())
.time(simulatorReqVO.getReportTime()) // 使用上报时间 // .build();
.productKey(simulatorReqVO.getProductKey())
.deviceName(device.getDeviceName())
.deviceKey(device.getDeviceKey())
.build();
// 4. 发送模拟消息 // 4. 发送模拟消息
simulateSendProducer.sendSimulateMessage(thingModelMessage); // simulateSendProducer.sendDeviceMessage(thingModelMessage);
} }
@Override @Override

View File

@ -0,0 +1,37 @@
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;
/**
* 设备上行 Service 接口
*
* 目的设备 -> 插件 -> 服务端
*
* @author 芋道源码
*/
public interface IotDeviceUpstreamService {
/**
* 更新设备状态
*
* @param updateReqDTO 更新设备状态 DTO
*/
void updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO);
/**
* 上报设备属性数据
*
* @param reportReqDTO 上报设备属性数据 DTO
*/
void reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO);
/**
* 上报设备事件数据
*
* @param reportReqDTO 设备事件
*/
void reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO);
}

View File

@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.iot.service.device.upstream;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.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.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
/**
* 设备上行 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDeviceProducer deviceProducer;
@Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
log.info("[updateDeviceStatus][更新设备状态: {}]", updateReqDTO);
// TODO 芋艿插件状态
}
@Override
public void reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) {
// 1.1 获得设备
log.info("[reportDevicePropertyData][上报设备属性数据: {}]", reportReqDTO);
IotDeviceDO device = getDevice(reportReqDTO);
if (device == null) {
log.error("[reportDevicePropertyData][设备({}/{})不存在]",
reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
return;
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, reportReqDTO);
// 2. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(reportReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.PROPERTY.getType())
.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier())
.setData(reportReqDTO.getProperties());
sendDeviceMessage(message, device);
}
@Override
public void reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO) {
log.info("[reportDeviceEventData][上报设备事件数据: {}]", reportReqDTO);
// TODO 芋艿待实现
}
private IotDeviceDO getDevice(IotDeviceUpstreamAbstractReqDTO reqDTO) {
return TenantUtils.executeIgnore(() -> // 需要忽略租户因为请求时未带租户编号
deviceService.getDeviceByProductKeyAndDeviceName(reqDTO.getProductKey(), reqDTO.getDeviceName()));
}
private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) {
// TODO 芋艿插件状态
// TODO 芋艿操作时间
}
private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
// 1. 完善消息
message.setDeviceKey(device.getDeviceKey());
if (StrUtil.isEmpty(message.getRequestId())) {
message.setRequestId(IdUtil.fastSimpleUUID());
}
if (message.getReportTime() == null) {
message.setReportTime(LocalDateTime.now());
}
// 2. 发送消息
try {
deviceProducer.sendDeviceMessage(message);
log.info("[sendDeviceMessage][message({}) 发送消息成功]", message);
} catch (Exception e) {
log.error("[sendDeviceMessage][message({}) 发送消息失败]", message, e);
}
}
}

View File

@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProduc
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -35,7 +35,7 @@ public class IotProductServiceImpl implements IotProductService {
@Resource @Resource
@Lazy // 延迟加载解决循环依赖 @Lazy // 延迟加载解决循环依赖
private IotDevicePropertyDataService devicePropertyDataService; private IotDevicePropertyService devicePropertyDataService;
@Override @Override
public Long createProduct(IotProductSaveReqVO createReqVO) { public Long createProduct(IotProductSaveReqVO createReqVO) {

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
/**
* 物模型消息 Service
*/
public interface IotThingModelMessageService {
/**
* 保存物模型消息
*
* @param device 设备
* @param thingModelMessage 物模型消息
*/
void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage);
}

View File

@ -1,277 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
import cn.iocoder.yudao.module.iot.enums.IotConstants;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
/**
* 物模型消息 Service 实现类
*/
@Slf4j
@Service
public class IotThingModelMessageServiceImpl implements IotThingModelMessageService {
private static final String TAG_NOTE = "TAG";
private static final String NOTE = "note";
private static final String TIME = "time";
private static final String DEVICE_KEY = "device_key";
private static final String DEVICE_NAME = "device_name";
private static final String PRODUCT_KEY = "product_key";
private static final String DEVICE_TYPE = "device_type";
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
private String url;
@Resource
private IotThingModelService iotThingModelService;
@Resource
private IotDeviceService iotDeviceService;
@Resource
private IotProductService productService;
@Resource
private TdEngineDDLMapper tdEngineDDLMapper;
@Resource
private TdEngineDMLMapper tdEngineDMLMapper;
@Resource
private IotDevicePropertyDataMapper iotDevicePropertyDataMapper;
@Resource
private DeviceDataRedisDAO deviceDataRedisDAO;
// TODO @haohao这个方法可以考虑加下 1. 2. 3. 更有层次感
@Override
@TenantIgnore
public void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage) {
// 1. 判断设备状态如果为未激活状态创建数据表并更新设备状态
if (IotDeviceStatusEnum.INACTIVE.getStatus().equals(device.getStatus())) {
// 1.1 创建设备表
// createDeviceTable(device.getDeviceType(), device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO()
.setId(device.getId()).setStatus(IotDeviceStatusEnum.ONLINE.getStatus()));
}
// 2. 获取设备属性并进行物模型校验过滤非物模型属性
Map<String, Object> params = thingModelMessage.dataToMap();
List<IotThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
if (thingModelList.isEmpty()) {
return;
}
// 3. 过滤并收集有效的属性字段缓存设备属性
List<TdFieldDO> schemaFieldValues = filterAndCollectValidFields(params, thingModelList, device, thingModelMessage.getTime());
if (schemaFieldValues.size() == 0) { // 没有字段无需保存
return;
}
// 4. 构建并保存设备属性数据
// tdEngineDMLMapper.insertData(TdTableDO.builder()
// .dataBaseName(getDatabaseName())
// .tableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()))
// .columns(schemaFieldValues)
// .build());
// TODO:复用了旧逻辑,先过渡一下
iotDevicePropertyDataMapper.insertDevicePropertyData(TdTableDO.builder()
.productKey(device.getProductKey())
.deviceKey(device.getDeviceKey())
.columns(schemaFieldValues)
.build());
}
private List<IotThingModelDO> getValidThingModelList(String productKey) {
return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
}
// @Override
// @TenantIgnore
// public void createSuperTable(Long productId) {
// // 1. 查询产品
// IotProductDO product = productService.getProduct(productId);
// // 2. 创建日志超级表
// tdThingModelMessageMapper.createSuperTable(product.getProductKey());
//
// // 2. 获取超级表的名称和数据库名称
// // TODO @alwayssuper最好 databaseNamesuperTableName 的处理放到 tdThinkModelMessageMapper 可以考虑弄个 default 方法
//// String databaseName = IotTdDatabaseUtils.getDatabaseName(url);
//// String superTableName = IotTdDatabaseUtils.getThingModelMessageSuperTableName(product.getProductKey());
////
//// // 解析物模型获取字段列表
//// List<TdFieldDO> schemaFields = List.of(
//// TdFieldDO.builder().fieldName("time").dataType("TIMESTAMP").build(),
//// TdFieldDO.builder().fieldName("id").dataType("NCHAR").dataLength(64).build(),
//// TdFieldDO.builder().fieldName("sys").dataType("NCHAR").dataLength(2048).build(),
//// TdFieldDO.builder().fieldName("method").dataType("NCHAR").dataLength(256).build(),
//// TdFieldDO.builder().fieldName("params").dataType("NCHAR").dataLength(2048).build()
//// );
//// // 设置超级表的标签
//// List<TdFieldDO> tagsFields = List.of(
//// TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build()
//// );
//// // 3. 创建超级表
//// tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
// }
private List<IotThingModelDO> getValidFunctionList(String productKey) {
return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
}
private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThingModelDO> thingModelList, IotDeviceDO device, Long time) {
// 1. 获取属性标识符集合
Set<String> propertyIdentifiers = convertSet(thingModelList, IotThingModelDO::getIdentifier);
// 2. 构建属性标识符和属性的映射
Map<String, IotThingModelDO> thingModelMap = convertMap(thingModelList, IotThingModelDO::getIdentifier);
// 3. 过滤并收集有效的属性字段
List<TdFieldDO> schemaFieldValues = new ArrayList<>();
//TODO:新版本是使用ts字段
// schemaFieldValues.add(new TdFieldDO(TIME, time));
params.forEach((key, val) -> {
if (propertyIdentifiers.contains(key)) {
schemaFieldValues.add(new TdFieldDO(key.toLowerCase(), val));
// 缓存设备属性
// TODO @haohao这个缓存的写入可以使用的时候 cache 被动读
setDeviceDataCache(device, thingModelMap.get(key), val, time);
}
});
return schemaFieldValues;
}
/**
* 缓存设备属性
*
* @param device 设备信息
* @param iotThingModelDO 物模型属性
* @param val 属性值
* @param time 时间
*/
private void setDeviceDataCache(IotDeviceDO device, IotThingModelDO iotThingModelDO, Object val, Long time) {
IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
.productKey(device.getProductKey())
.deviceName(device.getDeviceName())
.identifier(iotThingModelDO.getIdentifier())
.value(val != null ? val.toString() : null)
.updateTime(DateUtil.toLocalDateTime(new Date(time)))
.deviceId(device.getId())
.thingModelId(iotThingModelDO.getId())
.name(iotThingModelDO.getName())
.dataType(iotThingModelDO.getProperty().getDataType())
.build();
deviceDataRedisDAO.set(deviceData);
}
/**
* 创建设备数据表
*
* @param deviceType 设备类型
* @param productKey 产品 Key
* @param deviceName 设备名称
* @param deviceKey 设备 Key
*/
private void createDeviceTable(Integer deviceType, String productKey, String deviceName, String deviceKey) {
// 1. 获取超级表名和数据库名
String superTableName = getProductPropertySTableName(deviceType, productKey);
String dataBaseName = getDatabaseName();
// 2. 获取超级表的结构信息
List<Map<String, Object>> maps = tdEngineDDLMapper.describeSuperTable(new TdTableDO(dataBaseName, superTableName));
List<TdFieldDO> tagsFieldValues = new ArrayList<>();
if (maps != null) {
// 2.1 过滤出 TAG 类型的字段
List<Map<String, Object>> taggedNotesList = CollectionUtils.filterList(maps, map -> TAG_NOTE.equals(map.get(NOTE)));
// 2.2 解析字段信息
tagsFieldValues = FieldParser.parse(taggedNotesList.stream()
.map(map -> List.of(map.get("field"), map.get("type"), map.get("length")))
.collect(Collectors.toList()));
// 2.3 设置 TAG 字段的值
for (TdFieldDO tagsFieldValue : tagsFieldValues) {
switch (tagsFieldValue.getFieldName()) {
case PRODUCT_KEY -> tagsFieldValue.setFieldValue(productKey);
case DEVICE_KEY -> tagsFieldValue.setFieldValue(deviceKey);
case DEVICE_NAME -> tagsFieldValue.setFieldValue(deviceName);
case DEVICE_TYPE -> tagsFieldValue.setFieldValue(deviceType);
}
}
}
// 3. 创建设备数据表
String tableName = getDeviceTableName(productKey, deviceName);
tdEngineDDLMapper.createTable(TdTableDO.builder().build()
.setDataBaseName(dataBaseName)
.setSuperTableName(superTableName)
.setTableName(tableName)
.setTags(tagsFieldValues));
}
/**
* 获取数据库名称
*
* @return 数据库名称
*/
private String getDatabaseName() {
return StrUtil.subAfter(url, "/", true);
}
/**
* 获取产品属性表名
*
* @param deviceType 设备类型
* @param productKey 产品 Key
* @return 产品属性表名
*/
private static String getProductPropertySTableName(Integer deviceType, String productKey) {
// TODO @haohao枚举下会好点哈
return switch (deviceType) {
case 1 -> String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
case 2 -> String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
default -> String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
};
}
/**
* 获取设备表名
*
* @param productKey 产品 Key
* @param deviceName 设备名称
* @return 设备表名
*/
private static String getDeviceTableName(String productKey, String deviceName) {
return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
}
}

View File

@ -9,8 +9,9 @@
ts TIMESTAMP, ts TIMESTAMP,
id NCHAR(50), id NCHAR(50),
product_key NCHAR(50), product_key NCHAR(50),
device_name NCHAR(50),
type NCHAR(50), type NCHAR(50),
sub_type NCHAR(50), identifier NCHAR(255),
content NCHAR(1024), content NCHAR(1024),
report_time TIMESTAMP report_time TIMESTAMP
) TAGS ( ) TAGS (
@ -18,18 +19,23 @@
) )
</update> </update>
<select id="showDeviceLogSTable" resultType="String">
SHOW STABLES LIKE 'device_log'
</select>
<insert id="insert"> <insert id="insert">
INSERT INTO device_log_${log.deviceKey} (ts, id, product_key, type, subType, content, report_time) INSERT INTO device_log_${deviceKey} (ts, id, product_key, device_name, type, identifier, content, report_time)
USING device_log USING device_log
TAGS ('${log.deviceKey}') TAGS ('${deviceKey}')
VALUES ( VALUES (
NOW, NOW,
#{log.id}, #{id},
#{log.productKey}, #{productKey},
#{log.type}, #{deviceName},
#{log.subType}, #{type},
#{log.content}, #{identifier},
#{log.reportTime} #{content},
#{reportTime}
) )
</insert> </insert>
@ -51,6 +57,7 @@
LIMIT #{reqVO.pageSize} OFFSET #{reqVO.pageNo} LIMIT #{reqVO.pageSize} OFFSET #{reqVO.pageNo}
</select> </select>
<!-- TODO 芋艿:看看能不能复用 mybatis-plus 的 selectCount 方法 -->
<select id="selectCount" resultType="Long"> <select id="selectCount" resultType="Long">
SELECT COUNT(*) SELECT COUNT(*)
FROM device_log_${reqVO.deviceKey} FROM device_log_${reqVO.deviceKey}
@ -67,12 +74,4 @@
</where> </where>
</select> </select>
<select id="checkDeviceLogSTableExists" resultType="Object">
SHOW STABLES LIKE 'device_log'
</select>
<select id="checkDeviceLogTableExists" resultType="Object">
SHOW TABLES LIKE 'device_log_${deviceKey}'
</select>
</mapper> </mapper>

View File

@ -1,99 +1,62 @@
package cn.iocoder.yudao.module.iot.plugin.common.api; package cn.iocoder.yudao.module.iot.plugin.common.api;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; 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.IotDeviceStatusUpdateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/** // TODO @haohao类注释写一下比较好
* 用于通过 {@link RestTemplate} 向远程 IoT 服务发送设备数据相关的请求 // TODO @haohao类名要改下
* 包括设备状态更新事件数据上报属性数据上报等操作
*/
@Slf4j @Slf4j
@RequiredArgsConstructor public class DeviceDataApiClient implements IotDeviceUpstreamApi {
public class DeviceDataApiClient implements DeviceDataApi {
public static final String URL_PREFIX = "/rpc-api/iot/device/upstream";
/**
* 用于发送 HTTP 请求的工具
*/
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
/**
* 远程 IoT 服务的基础 URL
* 例如http://127.0.0.1:8080
*/
private final String deviceDataUrl; private final String deviceDataUrl;
// 可以通过构造器把 RestTemplate baseUrl 注入进来
// TODO @haohao可以用 lombok 简化
public DeviceDataApiClient(RestTemplate restTemplate, String deviceDataUrl) {
this.restTemplate = restTemplate;
this.deviceDataUrl = deviceDataUrl;
}
// TODO @haohao返回结果不用 CommonResult // TODO @haohao返回结果不用 CommonResult
@Override @Override
public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) { public CommonResult<Boolean> updateDeviceStatus(IotDeviceStatusUpdateReqDTO updateReqDTO) {
String url = deviceDataUrl + "/rpc-api/iot/device-data/update-status"; String url = deviceDataUrl + URL_PREFIX + "/update-status";
return doPost(url, updateReqDTO, "updateDeviceStatus"); return doPost(url, updateReqDTO, "updateDeviceStatus");
} }
@Override @Override
public CommonResult<Boolean> reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO) { public CommonResult<Boolean> reportDeviceEventData(IotDeviceEventReportReqDTO reportReqDTO) {
String url = deviceDataUrl + "/rpc-api/iot/device-data/report-event"; String url = deviceDataUrl + URL_PREFIX + "/report-event";
return doPost(url, reportReqDTO, "reportDeviceEventData"); return doPost(url, reportReqDTO, "reportDeviceEventData");
} }
@Override @Override
public CommonResult<Boolean> reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) { public CommonResult<Boolean> reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) {
String url = deviceDataUrl + "/rpc-api/iot/device-data/report-property"; String url = deviceDataUrl + URL_PREFIX + "/report-property";
return doPost(url, reportReqDTO, "reportDevicePropertyData"); return doPost(url, reportReqDTO, "reportDevicePropertyData");
} }
// TODO @haohao未来可能有 get 类型哈
/** /**
* 发送 GET 请求 * 将与远程服务交互的通用逻辑抽取成一个私有方法
*
* @param <T> 请求体类型
* @param url 请求 URL
* @param requestBody 请求体
* @param actionName 操作名称
* @return 响应结果
*/
private <T> CommonResult<Boolean> doGet(String url, T requestBody, String actionName) {
log.info("[{}] Sending request to URL: {}", actionName, url);
try {
CommonResult<?> response = restTemplate.getForObject(url, CommonResult.class);
if (response != null && response.isSuccess()) {
return success(true);
} else {
log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
return CommonResult.error(500, "Request failed");
}
} catch (Exception e) {
log.error("[{}] Error sending request to URL: {}", actionName, url, e);
return CommonResult.error(400, "Request error: " + e.getMessage());
}
}
/**
* 发送 POST 请求
*
* @param <T> 请求体类型
* @param url 请求 URL
* @param requestBody 请求体
* @param actionName 操作名称
* @return 响应结果
*/ */
private <T> CommonResult<Boolean> doPost(String url, T requestBody, String actionName) { private <T> CommonResult<Boolean> doPost(String url, T requestBody, String actionName) {
log.info("[{}] Sending request to URL: {}", actionName, url); log.info("[{}] Sending request to URL: {}", actionName, url);
try { try {
CommonResult<?> response = restTemplate.postForObject(url, requestBody, CommonResult.class); // 这里指定返回类型为 CommonResult<?>根据后台服务返回的实际结构做调整
if (response != null && response.isSuccess()) { restTemplate.postForObject(url, requestBody, CommonResult.class);
// TODO @haohaocheck 结果是否成功
return success(true); return success(true);
} else {
log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
return CommonResult.error(500, "Request failed");
}
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Error sending request to URL: {}", actionName, url, e); log.error("[{}] Error sending request to URL: {}", actionName, url, e);
return CommonResult.error(400, "Request error: " + e.getMessage()); return CommonResult.error(400, "Request error: " + e.getMessage());

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.plugin.common.config; package cn.iocoder.yudao.module.iot.plugin.common.config;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient; import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -43,7 +43,7 @@ public class YudaoDeviceDataApiAutoConfiguration {
* @return DeviceDataApi 实例 * @return DeviceDataApi 实例
*/ */
@Bean @Bean
public DeviceDataApi deviceDataApi(RestTemplate restTemplate) { public IotDeviceUpstreamApi deviceDataApi(RestTemplate restTemplate) {
return new DeviceDataApiClient(restTemplate, deviceDataUrl); return new DeviceDataApiClient(restTemplate, deviceDataUrl);
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.plugin; package cn.iocoder.yudao.module.iot.plugin;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.pf4j.Plugin; import org.pf4j.Plugin;
import org.pf4j.PluginWrapper; import org.pf4j.PluginWrapper;
@ -27,7 +27,7 @@ public class EmqxPlugin extends Plugin {
executorService = Executors.newSingleThreadExecutor(); executorService = Executors.newSingleThreadExecutor();
} }
DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class); IotDeviceUpstreamApi deviceDataApi = SpringUtil.getBean(IotDeviceUpstreamApi.class);
if (deviceDataApi == null) { if (deviceDataApi == null) {
log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
return; return;

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin.http.config;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper; import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPlugin; import org.pf4j.spring.SpringPlugin;
@ -64,7 +64,7 @@ public class HttpVertxPlugin extends SpringPlugin {
protected void prepareRefresh() { protected void prepareRefresh() {
// 在刷新容器前注册主程序中的 Bean // 在刷新容器前注册主程序中的 Bean
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class); IotDeviceUpstreamApi deviceDataApi = SpringUtil.getBean(IotDeviceUpstreamApi.class);
beanFactory.registerSingleton("deviceDataApi", deviceDataApi); beanFactory.registerSingleton("deviceDataApi", deviceDataApi);
super.prepareRefresh(); super.prepareRefresh();
} }

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.plugin.http.config; package cn.iocoder.yudao.module.iot.plugin.http.config;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler; import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import io.vertx.ext.web.Router; import io.vertx.ext.web.Router;
@ -61,7 +61,7 @@ public class HttpVertxPluginConfiguration {
* @return HttpVertxHandler 实例 * @return HttpVertxHandler 实例
*/ */
@Bean @Bean
public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) { public HttpVertxHandler httpVertxHandler(IotDeviceUpstreamApi deviceDataApi) {
return new HttpVertxHandler(deviceDataApi); return new HttpVertxHandler(deviceDataApi);
} }

View File

@ -2,19 +2,21 @@ package cn.iocoder.yudao.module.iot.plugin.http.service;
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.DeviceDataApi; 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 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.util.Map;
@Slf4j @Slf4j
public class HttpVertxHandler implements Handler<RoutingContext> { public class HttpVertxHandler implements Handler<RoutingContext> {
private final DeviceDataApi deviceDataApi; private final IotDeviceUpstreamApi deviceDataApi;
public HttpVertxHandler(DeviceDataApi deviceDataApi) { public HttpVertxHandler(IotDeviceUpstreamApi deviceDataApi) {
this.deviceDataApi = deviceDataApi; this.deviceDataApi = deviceDataApi;
} }
@ -23,6 +25,7 @@ public class HttpVertxHandler implements Handler<RoutingContext> {
String productKey = ctx.pathParam("productKey"); String productKey = ctx.pathParam("productKey");
String deviceName = ctx.pathParam("deviceName"); String deviceName = ctx.pathParam("deviceName");
// TODO @haohaorequestBody.asJsonObject() 貌似天然就是 json 对象哈
RequestBody requestBody = ctx.body(); RequestBody requestBody = ctx.body();
JSONObject jsonData; JSONObject jsonData;
try { try {
@ -43,7 +46,7 @@ public class HttpVertxHandler implements Handler<RoutingContext> {
IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder() IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder()
.productKey(productKey) .productKey(productKey)
.deviceName(deviceName) .deviceName(deviceName)
.params(jsonData) .properties((Map<String, Object>) requestBody.asJsonObject().getMap().get("properties"))
.build(); .build();
deviceDataApi.reportDevicePropertyData(reportReqDTO); deviceDataApi.reportDevicePropertyData(reportReqDTO);